前端

Javascript:核心基础原理

js 代码执行过程、执行上下文、作用域、变量提升、This的绑定

执行上下文

  1. 全局执行上下文:任何不在函数内部的都是全局执行上下文,它首先会创建一个全局的window对象,并且设置this的值等于这个全局对象,一个程序中只有一个全局执行上下文。
  2. 函数执行上下文:当一个函数被调用时,就会为该函数创建一个新的执行上下文,函数的上下文可以有任意多个。
  3. eval函数执行上下文:执行在eval函数中的代码会有属于他自己的执行上下文。

执行上下文栈(Execution context stack,ECS):JavaScript引擎使用执行上下文栈来管理执行上下文。当JavaScript执行代码时,首先遇到全局代码,会创建一个全局执行上下文并且压入执行栈中,每当遇到一个函数调用,就会为该函数创建一个新的执行上下文并压入栈顶,引擎会执行位于执行上下文栈顶的函数,当函数执行完成之后,执行上下文从栈中弹出,继续执行下一个上下文。当所有的代码都执行完毕之后,从栈中弹出全局执行上下文。

每个执行上下文,都有三个重要属性:

  • 变量对象 / 活动对象 (Variable object, VO ) / (activation object, AO)在进入执行上下文时会给变量对象添加形参、函数声明、变量声明等初始的属性值。
    • 全局上下文的变量对象初始化是全局对象
    • 函数上下文的变量对象(活动对象)初始化只包括 Arguments 对象
    • 在进入执行上下文时会给变量对象添加形参、函数声明、变量声明等初始的属性值
    • 在代码执行阶段,会再次修改变量对象的属性值 function foo(a) {
         var b = 2;
         function c() {}
         var d = function() {};
         b = 3;
       }
       foo(1);
       //进入执行上下文后,活动对象AO:
       AO = {
           arguments: {
               0: 1,
               length: 1
          },
           a: 1,
           b: undefined,
           c: reference to function c(){},
           d: undefined
       }
       //当代码执行完后,这时候的 AO 是:
       AO = {
           arguments: {
               0: 1,
               length: 1
          },
           a: 1,
           b: 3,
           c: reference to function c(){},
           d: reference to FunctionExpression “d”
       }
  • 作用域链(Scope chain)
  • this

作用域、作用域链

作用域:是指程序源代码中定义变量的区域,确定当前执行代码对变量的访问权限,JS采用词法作用域(lexical scoping),也就是静态作用域。 作用域链: 在当前作用域中查找所需变量,但是该作用域没有这个变量,那这个变量就是自由变量。如果在自己作用域找不到该变量就去父级作用域查找,依次向上级作用域查找,直到访问到window对象就被终止,这一层层的关系就是作用域链。作用域链的作用是保证对执行环境有权访问的所有变量和函数的有序访问,通过作用域链,可以访问到外层环境的变量和函数。

静态作用域 / 词法作用域:函数的作用域在函数定义时候就决定了。这是因为函数有一个内部属性 [[scope]],当函数创建的时候,就会保存所有父变量对象到其中,你可以理解 [[scope]] 就是所有父变量对象的层级链,但是注意:[[scope]] 并不代表完整的作用域链 动态作用域:函数的作用域是在函数调用的时候才决定的。

 var value = 1;
 function foo() {
     console.log(value);
 }
 function bar() {
     var value = 2;
     foo();
 }
 bar();//1
 //因为输出了1,证明是JS静态作用域,在定义时就决定了
 //如果输出了2,说明是动态作用域,在函数调用的时候才决定(bash就是动态作用域)
 function foo() {
     function bar() {
         ...
    }
 }
 //函数创建定义时,各自的[[scope]]为:
 foo.[[scope]] = [
   globalContext.VO
 ];
 ​
 bar.[[scope]] = [
     fooContext.AO,
     globalContext.VO
 ];
   
 //函数激活:当函数激活时,进入函数上下文,创建 VO/AO 后,就会将活动对象添加到作用链的前端。这时候执行上下文的作用域链,我们命名为 Scope:
 Scope = [AO].concat([[Scope]]);
 //至此,作用域链创建完毕。
  1. 全局作用域
    • 最外层函数和最外层函数外面定义的变量拥有全局作用域
    • 所有未定义直接赋值的变量自动声明为全局作用域
    • 所有window对象的属性拥有全局作用域
    • 全局作用域有很大的弊端,过多的全局作用域变量会污染全局命名空间,容易引起命名冲突
  2. 函数作用域
    • 函数作用域声明在函数内部的变零,一般只有固定的代码片段可以访问到
    • 作用域是分层的,内层作用域可以访问外层作用域,反之不行
  3. 块级作用域
    • 使用ES6中新增的letconst指令可以声明块级作用域,块级作用域可以在函数中创建也可以在一个代码块中的创建(由{ }包裹的代码片段)
    • let和const声明的变量不会有变量提升,也不可以重复声明
    • 在循环中比较适合绑定块级作用域,这样就可以把声明的计数器变量限制在循环内部。

变量提升

变量提升的表现是,无论在函数中何处位置声明的变量,好像都被提升到了函数的首部,可以在变量声明前访问到而不会报错。造成变量声明提升的本质原因是 js 引擎在代码执行前有一个解析的过程,创建了执行上下文,初始化了一些代码执行时需要用到的对象。当访问一个变量时,会到当前执行上下文中的作用域链中去查找,而作用域链的首端指向的是当前执行上下文的变量对象,这个变量对象是执行上下文的一个属性,它包含了函数的形参、所有的函数和变量声明,这个对象的是在代码解析的时候创建的。

变量提升的原因

  1. 提高性能 在JS代码执行之前,会进行语法检查和预编译,并且这一操作只进行一次。这么做就是为了提高性能,如果没有这一步,那么每次执行代码前都必须重新解析一遍该变量(函数),而这是没有必要的,因为变量(函数)的代码并不会改变,解析一遍就够了。在解析的过程中,还会为函数生成预编译代码。在预编译时,会统计声明了哪些变量、创建了哪些函数,并对函数的代码进行压缩,去除注释、不必要的空白等。这样做的好处就是每次执行函数时都可以直接为该函数分配栈空间(不需要再解析一遍去获取代码中声明了哪些变量,创建了哪些函数),并且因为代码压缩的原因,代码执行也更快了。
  2. 容错性更好变量提升可以在一定程度上提高JS的容错性,看下面的代码: a = 1;var a;console.log(a);如果没有变量提升,这两行代码就会报错,但是因为有了变量提升,这段代码就可以正常执行。虽然,在可以开发过程中,可以完全避免这样写,但是有时代码很复杂的时候。可能因为疏忽而先使用后定义了,这样也不会影响正常使用。由于变量提升的存在,而会正常运行。

js 代码执行过程:解析和执行

1. 解析阶段:

在JS解析阶段,会检查语法,并对函数进行预编译。解析的时候会先创建全局执行上下文环境,先把代码中即将执行的变量、函数声明都拿出来,变量先赋值为undefined,再函数先声明好可使用(变量提升)。在一个函数执行之前,也会创建一个函数执行上下文环境,跟全局执行上下文类似,不过函数执行上下文会多出this、arguments和函数的参数。函数声明优先级比变量声明高

  • 全局上下文:变量定义,函数声明
  • 函数上下文:变量定义,函数声明,this,arguments
this绑定
  • 全局执行上下文中this指向全局对象(window对象)
  • this指向取决于函数如何调用(按优先级排序):
    1. 构造器调用:this 指向这个新创建的对象
    2. call、apply、bind调用:绑定到指定对象
    3. 方法调用模式:被引用对象调用,那么 this 会被设置成那个对象
    4. 函数调用:否则 this 的值被设置为全局对象或者 undefined
添加形参
  • 由名称和对应值组成的一个变量对象的属性被创建
  • 没有实参,属性值设为 undefined
创建词法环境组件(函数声明)
  • 词法环境是一种有标识符 : 变量映射的数据结构,标识符是指变量/函数名,变量是对实际对象或原始数据的引用
  • 如果变量对象已经存在相同名称的属性,则完全替换这个属性。
  • 词法环境的内部有两个组件:
    1. 环境记录器:用来储存变量个函数声明的实际位置
    2. 外部环境的引用:可以访问父级作用域
创建变量环境组件(变量声明)
  • 变量环境也是一个词法环境,其环境记录器持有变量声明语句在执行上下文中创建的绑定关系。
  • 由名称和对应值(undefined)组成一个变量对象的属性被创建;
  • 如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性

2. 执行阶段:

会完成对变量的分配,最后执行完代码

总结案例:

结合着之前讲的变量对象(VO/AO)、执行上下文栈(ECStack),我们来总结一下函数执行上下文中作用域链和变量对象的创建过程:

 //示例1
 var scope = "global scope";
 function checkscope(){
     var scope2 = 'local scope';
     return scope2;
 }
 checkscope();
 ​
 //1.函数定义:checkscope 函数被创建,保存作用域链到 内部属性[[scope]]
 checkscope.[[scope]] = [ globalContext.VO ];
 ​
 //checkscope()开始运行:
 //2.函数解析:创建 checkscope 函数执行上下文,checkscope 函数执行上下文被压入执行上下文栈
 ECStack = [
     checkscopeContext,
     globalContext
 ];
 ​
 //checkscope 函数并不立刻执行,开始做准备工作:
 //3.第一步:复制函数[[scope]]属性创建作用域链
 checkscopeContext = {
     Scope: checkscope.[[scope]],
 }
 //4.第二步:用 arguments 创建活动对象AO,随后初始化活动对象,加入形参、函数声明、变量声明
 checkscopeContext = {
     AO: {
         arguments: {
             length: 0
        },
         scope2: undefined
    },
     Scope: checkscope.[[scope]],
 }
 //5.第三步:将活动对象AO压入 checkscope 作用域链顶端
 checkscopeContext = {
     AO: {
         arguments: {
             length: 0
        },
         scope2: undefined
    },
     Scope: [AO, checkscope.[[Scope]]]
 }
 ​
 //准备工作做完
 //6.开始执行函数,随着函数的执行,修改 AO 的属性值
 checkscopeContext = {
     AO: {
         arguments: {
             length: 0
        },
         scope2: 'local scope'
    },
     Scope: [AO, checkscope.[[Scope]]]
 }
 ​
 //查找到 scope2 的值,返回后函数执行完毕,函数上下文从执行上下文栈中弹出
 ECStack = [
     globalContext
 ];
 //示例2
 var scope = "global scope";
 function checkscope(){
     var scope = "local scope";
     function f(){
         return scope;
    }
     return f();
 }
 checkscope();//local scope
 ​
 //执行上下文栈:
 //ECStack.push(<checkscope> functionContext);
 //ECStack.push(<f> functionContext);
 //ECStack.pop();
 //ECStack.pop();
 ​
 //详细运行过程:
 //1.执行全局代码,创建全局执行上下文,全局上下文被压入执行上下文栈
 ECStack = [ globalContext ];
 //2.全局上下文初始化,同时,checkscope 函数被创建,保存作用域链到函数的内部属性[[scope]]
 globalContext = {
     VO: [global],
     Scope: [globalContext.VO],
     this: globalContext.VO
 }
 checkscope.[[scope]] = [
   globalContext.VO
 ];
 ​
 //执行checkscope()
 //3.执行 checkscope 函数,创建 checkscope 函数执行上下文,checkscope 函数执行上下文被压入执行上下文栈
 ECStack = [ checkscopeContext , globalContext ];
 //4.checkscope 函数执行上下文初始化:
 // 4.1复制函数 [[scope]] 属性创建作用域链,
 // 4.2用 arguments 创建活动对象,
 // 4.3初始化活动对象,即加入形参、函数声明、变量声明,
 // 4.4将活动对象压入 checkscope 作用域链顶端。
 // 同时 f 函数被创建,保存作用域链到 f 函数的内部属性[[scope]]
 checkscopeContext = {
   AO: {
     arguments: {
       length: 0
    },
     scope: undefined,
     f: reference to function f(){}
  },
   Scope: [AO, globalContext.VO],
   this: undefined
 }
 //运行代码:checkscopeContext.AO.scope 赋值为 "local scope"
 ​
 //执行 f()
 //5.执行 f 函数,创建 f 函数执行上下文,f 函数执行上下文被压入执行上下文栈
 ECStack = [ fContext , checkscopeContext , globalContext ];
 //6.f 函数执行上下文初始化, 以下跟第 4 步相同:
 // 6.1复制函数 [[scope]] 属性创建作用域链
 // 6.2用 arguments 创建活动对象
 // 6.3初始化活动对象,即加入形参、函数声明、变量声明
 // 6.4将活动对象压入 f 作用域链顶端
 fContext = {
   AO: {
     arguments: {
       length: 0
    }
  },
   Scope: [AO, checkscopeContext.AO, globalContext.VO],
   this: undefined
 }
 //7.f 函数执行,沿着作用域链查找 scope 值,返回 scope 值(checkscopeContext.AO.scope="local scope")
 //8.f 函数执行完毕,f 函数上下文从执行上下文栈中弹出
 ECStack = [ checkscopeContext , globalContext ];
 //9.checkscope 函数执行完毕,checkscope 执行上下文从执行上下文栈中弹出
 ECStack = [ globalContext ];
 ​
 ​
 //示例3
 var scope = "global scope";
 function checkscope(){
     var scope = "local scope";
     function f(){
         return scope;
    }
     return f;
 }
 checkscope()();//local scope
 //执行上下文栈:
 //ECStack.push(<checkscope> functionContext);
 //ECStack.pop();
 //ECStack.push(<f> functionContext);
 //ECStack.pop();

call() 和 apply() 和 bind() 的区别

这三个方法都可以显示的指定调用函数的 this 指向。

  • apply 接受两个参数,第一个参数指定了函数体内 this 对象的指向,第二个参数为一个带下标的集合,这个集合可以为数组,也可以为类数组,apply 方法把这个集合中的元素作为参数传递给被调用的函数。
  • call 传入的参数数量不固定,跟 apply 相同的是,第一个参数也是代表函数体内的 this 指向,从第二个参数开始往后,每个参数被依次传入函数。
  • bind 和 call 差不多,从第二个参数开始往后,每个参数被依次传入函数,区别是bind不会立即执行,而是返回一个绑定了this的函数供以后执行

call 函数的实现

 Function.prototype.myCall = function(context) {
   // 判断调用对象
   if (typeof this !== "function") console.error("type error");
   // 获取参数
   let args = [...arguments].slice(1),
     result = null;
   // 判断 context 是否传入,如果未传入则设置为 window
   context = (context==null || context===undefined) ? window || Object(context);
   // 将调用函数设为对象的方法
   context.fn = this;
   // 调用函数
   result = context.fn(...args);
   // 将属性删除
   delete context.fn;
   return result;
 };

apply 函数的实现步骤:

 Function.prototype.myApply = function(context) {
   // 判断调用对象是否为函数
   if (typeof this !== "function") {
     throw new TypeError("Error");
  }
   let result = null;
   // 判断 context 是否存在,如果未传入则为 window
   context = (context==null || context===undefined) ? window || Object(context);
   // 将函数设为对象的方法
   context.fn = this;
   // 调用方法
   if (arguments[1]) {
     result = context.fn(...arguments[1]);
  } else {
     result = context.fn();
  }
   // 将属性删除
   delete context.fn;
   return result;
 };

bind 函数的实现步骤

 Function.prototype.myBind = function(context) {
   // 判断调用对象是否为函数
   if (typeof this !== "function") {
     throw new TypeError("Error");
  }
   // 获取参数
   var args = [...arguments].slice(1),
     fn = this;
   return function Fn() {
     // 根据调用方式,传入不同绑定值
     return fn.apply(
       this instanceof Fn ? this : context,
       args.concat(...arguments)
    );
  };
 };
 ​

尾调用

尾调用指的是函数的最后一步调用另一个函数。代码执行是基于执行栈的,所以当在一个函数里调用另一个函数时,会保留当前的执行上下文,然后再新建另外一个执行上下文加入栈中。使用尾调用的话,因为已经是函数的最后一步,所以这时可以不必再保留当前的执行上下文,从而节省了内存,这就是尾调用优化。但是 ES6 的尾调用优化只在严格模式下开启,正常模式是无效的。

强类型语言和弱类型语言

  • 强类型语言:强类型语言也称为强类型定义语言,是一种总是强制类型定义的语言,要求变量的使用要严格符合定义,所有变量都必须先定义后使用。Java和C++等语言都是强制类型定义的,也就是说,一旦一个变量被指定了某个数据类型,如果不经过强制转换,那么它就永远是这个数据类型了。例如你有一个整数,如果不显式地进行转换,你不能将其视为一个字符串。
  • 弱类型语言:弱类型语言也称为弱类型定义语言,与强类型定义相反。JavaScript语言就属于弱类型语言。简单理解就是一种变量类型可以被忽略的语言。比如JavaScript是弱类型定义的,在JavaScript中就可以将字符串’12’和整数3进行连接得到字符串’123’,在相加的时候会进行强制类型转换

两者对比:强类型语言在速度上可能略逊色于弱类型语言,但是强类型语言带来的严谨性可以有效地帮助避免许多错误。

解释性语言和编译型语言

解释型语言:使用专门的解释器对源程序逐行解释成特定平台的机器码并立即执行。是代码在执行时才被解释器一行行动态翻译和执行,而不是在执行之前就完成翻译。解释型语言不需要事先编译,其直接将源代码解释成机器码并立即执行,所以只要某一平台提供了相应的解释器即可运行该程序。其特点总结如下

  • 解释型语言每次运行都需要将源代码解释称机器码并执行,效率较低
  • 只要平台提供相应的解释器,就可以运行源代码,所以可以方便源程序移植
  • JavaScript、Python等属于解释型语言。

编译型语言:使用专门的编译器,针对特定的平台,将高级语言源代码一次性的编译成可被该平台硬件执行的机器码,并包装成该平台所能识别的可执行性程序的格式。在编译型语言写的程序执行之前,需要一个专门的编译过程,把源代码编译成机器语言的文件,如exe格式的文件,以后要再运行时,直接使用编译结果即可,如直接运行exe文件。因为只需编译一次,以后运行时不需要编译,所以编译型语言执行效率高。其特点总结如下:

  • 一次性的编译成平台相关的机器语言文件,运行时脱离开发环境,运行效率高
  • 与特定平台相关,一般无法移植到其他平台;
  • C、C++等属于编译型语言。

两者主要区别在于: 前者源程序编译后即可在该平台运行,后者是在运行期间才编译。所以前者运行速度快,后者跨平台性好。

闭包

闭包就是有权读取其他函数内部变量的函数。例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。

  • 闭包的第一个用途是使我们在函数外部能够访问到函数内部的变量。通过使用闭包,可以通过在外部调用闭包函数,从而在外部访问到函数内部的变量,可以使用这种方法来创建私有变量
  • 闭包的另一个用途是使已经运行结束的函数上下文中的变量对象继续留在内存中,因为闭包函数保留了这个变量对象的引用,所以这个变量对象不会被回收

柯里化

在数学和计算机科学中,柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。

关键点

1.利用闭包来缓存原函数传递参数 2.如果传递的参数足够原函数使用(传递参数达到形参数量 fun.length )就执行,否则继续合并且缓存参数,返回柯里化函数。

function curry(fun,preArgs=[]){
return function(...args){
let curArgs = [...preArgs,...args]
if(curArgs.length>=fun.length){
return fun.apply(this,curArgs)
}else{
return curry.call(this,fun,curArgs)
}
}
}

function add(a,b,c,d,e) {
console.log('a,b,c,d,e:',a,b,c,d,e)
console.log('this.name:',this.name)
return a+b+c+d+e
}

var obj={
addFun:curry(add),
name:'Obj'
}
var name = 'Win'

var test1 = obj.addFun(1,2,3)()(4)(5)
//a,b,c,d,e: 1 2 3 4 5
//this.name: Win

var test2 = obj.addFun(1,2,3,4,5)
//a,b,c,d,e: 1 2 3 4 5
//this.name: Obj

var temp = obj.addFun(1,2,3)()(4)
var test3 = temp(5)
//a,b,c,d,e: 1 2 3 4 5
//this.name: Win

console.log(test1,test2,test3)
//15 15 15

垃圾回收与内存泄漏

垃圾回收:JavaScript代码运行时,需要分配内存空间来储存变量和值。当变量不在参与运行时,就需要系统收回被占用的内存空间,这就是垃圾回收。

内存生命周期:内存分配 -> 内存使用 -> 内存释放

内存泄漏:如果内存不需要时,没有经过生命周期的释放期,那么就存在内存泄漏

回收机制

  • Javascript 具有自动垃圾回收机制,会定期对那些不再使用的变量、对象所占用的内存进行释放,原理就是找到不再使用的变量,然后释放掉其占用的内存。
  • JavaScript中存在两种变量:局部变量全局变量全局变量的生命周期会持续要页面卸载;而局部变量声明在函数中,它的生命周期从函数执行开始,直到函数执行结束,在这个过程中,局部变量会在堆或栈中存储它们的值,当函数执行结束后,这些局部变量不再被使用,它们所占有的空间就会被释放。
  • 不过,当局部变量被外部函数使用时,其中一种情况就是闭包,在函数执行结束后,函数外部的变量依然指向函数内部的局部变量,此时局部变量依然在被使用,所以不会回收

V8引擎的GC垃圾回收机制

V8 实现了准确式 GC,GC 算法采用了分代式垃圾回收机制。因此,V8 将内存(堆)主要分为新生代和老生代两部分,还有其他部分。

  1. 新生代(new_space):大多数的对象开始都会被分配在这里,这个区域相对较小但是垃圾回收特别频繁,新生代中的对象一般存活时间较短,使用 Scavenge GC 算法。 在新生代空间中,内存空间分为两部分,分别为 From 空间To 空间。在这两个空间中,必定有一个空间是使用的,另一个空间是空闲的。新分配的对象会被放入 From 空间中,当 From 空间被占满时,新生代 GC 就会启动了。算法会检查 From 空间中存活的对象并复制到 To 空间中,如果有失活的对象就会销毁。当复制完成后将 From 空间和 To 空间互换,这样 GC 就结束了。
  2. 老生代(old_space):新生代中的对象在存活一段时间后就会被转移到老生代内存区,相对于新生代该内存区域的垃圾回收频率较低。老生代又分为老生代指针区老生代数据区,前者包含大多数可能存在指向其他对象的指针的对象,后者只保存原始数据对象,这些对象没有指向其他对象的指针。老生代中的对象一般存活时间较长数量也多。为了避免循环引用导致的内存泄漏问题,截至2012年所有的现代浏览器均放弃了引用计数算法,转为使用两个算法,分别是标记清除算法和标记压缩算法。对象晋升:当一个对象在经过多次复制之后依旧存活,那么它会被认为是一个生命周期较长的对象,在下一次进行垃圾回收时,该对象会被直接转移到老生代中,晋升条件有:
    1. 新生代中的对象是否已经经历过一次 Scavenge 算法,如果经历过的话,会将对象从新生代空间移到老生代空间中。
    2. To 空间的对象占比大小超过 25 %。在这种情况下,为了不影响到内存分配,会将对象从新生代空间移到老生代空间中。
    标记清除(Mark-Sweep):在老生代中,以下情况会先启动标记清除算法
    • 某一个空间没有分块的时候
    • 空间中被对象超过一定限制
    • 空间不能保证新生代中的对象移动到老生代中
    标记清除算法分为两个阶段:标记 => 清除 ,具体步骤如下
    1. 垃圾回收器会在内部构建一个根列表,用于从根节点出发去寻找那些可以被访问到的变量。比如在JavaScript中,window全局对象、本地函数的局部变量和参数、当前嵌套调用链上的其他函数的变量和参数 可以看成一个根节点。
    2. 垃圾回收器从所有根节点出发,遍历其可以访问到的子节点,并将其标记为活动的,根节点不能到达的地方即为非活动的,将会被视为垃圾。
    3. 最后,垃圾回收器将会释放所有非活动的内存块,并将其归还给操作系统。
    在标记大型对内存时,可能需要几百毫秒才能完成一次标记。JS是单线程,在垃圾回收过程中会阻塞主线程,即全停顿(stop-the-world),这就会导致性能上的问题。为了解决这个问题,2011 年,V8 从 stop-the-world 标记切换到增量标志(Incremental Marking)。在增量标记期间,GC 将标记工作分解为更小的模块,可以让 JS 应用逻辑在模块间隙执行一会,从而不至于让应用出现停顿情况。同时还引入延迟清理(lazy sweeping)。在 2018 年,GC 技术引入了并行标记并行清理。该技术可以让 GC 扫描和标记对象时,同时允许 JS 运行压缩算法(Mark-Compact):清除对象后会造成堆内存出现碎片的情况,当碎片超过一定限制后会启动压缩算法。在压缩过程中,将活的对象向一端移动,直到所有对象都移动完成然后清理掉不需要的内存。
  3. 大对象区(large_object_space):存放体积超越其他区域大小的对象,每个对象都会有自己的内存,垃圾回收不会移动大对象区。
  4. 代码区(code_space):代码对象,会被分配在这里,唯一拥有执行权限的内存区域。
  5. map区(map_space):存放Cell和Map,每个区域都是存放相同大小的元素,结构简单
img

垃圾回收的方式

  • 标记清除(主流)
    • 标记清除是浏览器常见的垃圾回收方式,当变量进入执行环境时,就标记这个变量“进入环境”,被标记为“进入环境”的变量是不能被回收的,因为他们正在被使用。当变量离开环境时,就会被标记为“离开环境”,被标记为“离开环境”的变量会被内存释放
    • 垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记。然后,它会去掉环境中的变量以及被环境中的变量引用的标记。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后。垃圾收集器完成内存清除工作,销毁那些带标记的值,并回收他们所占用的内存空间。
  • 引用计数(已放弃)
    • 这个用的相对较少。引用计数就是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型赋值给该变量时,则这个值的引用次数就是+1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数就-1。当这个引用次数变为0时,说明这个变量已经没有价值,因此,在在机回收期下次再运行时,这个变量所占有的内存空间就会被释放出来。
    • 这种方法会引起循环引用的问题:例如:obj1obj2通过属性进行相互引用,两个对象的引用次数都是2。当使用循环计数时,由于函数执行完后,两个对象都离开作用域,函数执行结束,obj1obj2还将会继续存在,因此它们的引用次数永远不会是0,就会引起循环引用。这种情况下,就要手动释放变量占用的内存。
    • ES6 把引用有区分为强引用弱引用,这个目前只有再 Set 和 Map 中才有。强引用才会有引用计数叠加,只有引用计数为 0 的对象的内存才会被回收,所以一般需要手动回收内存(手动回收的前提在于标记清除法还没执行,还处于当前执行环境)。而弱引用没有触发引用计数叠加,只要引用计数为 0,弱引用就会自动消失,无需手动回收内存。

减少垃圾回收的方式

  • 对数组进行优化: 在清空一个数组时,最简单的方法就是给其赋值为[ ],但是与此同时会创建一个新的空对象,可以将数组的长度设置为0,以此来达到清空数组的目的。
  • 对object进行优化: 对象尽量复用,对于不再使用的对象,就将其设置为null,尽快被回收。
  • 对Map、Set进行优化:需要记得set.delete()或者map.delete()进行释放,或者用WeakSet,WeakMap弱引用。
  • 对函数进行优化: 在循环中的函数表达式,如果可以复用,尽量放在函数的外面。

哪些情况会导致内存泄漏

  • 意外的全局变量: 由于使用未声明的变量,而意外的创建了一个全局变量,而使这个变量一直留在内存中无法被回收。
  • 被遗忘的计时器或回调函数: 设置了 setInterval 定时器,而忘记取消它,如果循环函数有对外部变量的引用的话,那么这个变量会被一直留在内存中,而无法被回收。
  • 被遗忘的Map和Set:需要记得set.delete()或者map.delete()进行释放,或者用WeakSet,WeakMap弱引用。
  • 被遗忘的订阅发布事件监听器:组件销毁时忘记取消订阅,导致一直监听
  • 脱离 DOM 的引用: 获取一个 DOM 元素的引用,而后面这个元素被删除,由于一直保留了对这个元素的引用,所以它也无法被回收。
  • 闭包: 不合理的使用闭包,从而导致某些变量一直被留在内存当中。

如何发现内存泄漏

内存泄漏时,内存一般都是会周期性的增长,我们可以借助谷歌浏览器的开发者工具进行判别。 Chrome => F12开发者工具 => 性能 => 内存 => 录制 => 运行一段时间 => 停止录制 => 分析周期内存走势

查找内存泄漏出现的位置

Chrome => F12开发者工具 => 内存 => 录制 => 运行一段时间 => 停止录制 => 分析内存较大的记录查看是哪个对象,对应哪一行代码

原型链

Javascript:异步编程

异步编程

回调函数 的方式,使用回调函数的方式有一个缺点是,多个回调函数嵌套的时候会造成回调函数地狱,上下两层的回调函数间的代码耦合度太高,不利于代码的可维护。

Promise 的方式,使用 Promise 的方式可以将嵌套的回调函数作为链式调用。但是使用这种方法,有时会造成多个 then 的链式调用,可能会造成代码的语义不够明确。

generator 的方式,它可以在函数的执行过程中,将函数的执行权转移出去,在函数外部还可以将执行权转移回来(让出线程)。当遇到异步函数执行的时候,将函数执行权转移出去,当异步函数执行完毕时再将执行权给转移回来。因此在 generator 内部对于异步操作的方式,可以以同步的顺序来书写。使用这种方式需要考虑的问题是何时将函数的控制权转移回来,因此需要有一个自动执行 generator 的机制,比如说 co 模块等方式来实现 generator 的自动执行。

async 函数 的方式,async 函数是 generator + promise 实现的一个自动执行的语法糖,它内部自带执行器,当函数内部执行到一个 await 语句的时候,如果语句返回一个 promise 对象,那么函数将会等待 promise 对象的状态变为 resolve 后再继续向下执行。因此可以将异步逻辑,转化为同步的顺序来书写,并且这个函数可以自动执行。

回调地狱的弊端

  1. 嵌套函数存在耦合性,一旦有所改动,就会牵一发而动全身
  2. 嵌套函数一多,就很难处理错误,不能使用 try catch 捕获错误,不能直接 return

Promise

Promise是异步编程的一种解决方案,它是一个对象,可以获取异步操作的消息,他避免了传统回调函数地狱回调问题,简单说就是一个容器,里面可以获取异步操作的消息,保存着异步操作结果。

状态Pending 进行中、Resolved 已完成、Rejected 已拒绝

过程

  • pending -> fulfilled : Resolved(已完成)
  • pending -> rejected:Rejected(已拒绝)

特点

  • 在构造 Promise 的时候,构造函数内部的代码是立即执行的(同步)
  • 一旦状态改变就不会再变

优点:解决了回调地狱

缺点

  • 无法取消Promise
  • 不设置回调函数,Promise内部抛出的错误
  • 当处于pending状态时,无法得知目前进展到哪一个阶段

创建Promise对象

 const promise = new Promise(function(resolve, reject) {
   // ... some code
   if (/* 异步操作成功 */){
     resolve(value);
  } else {
     reject(error);
  }
 });
 ​
 // promise.then:接受两个参数:成功的回调、失败的回调
 promise.then(function(value) {
   // success
 }, function(error) {
   // failure
 });
 ​
 //promise.catch:接受失败的回调
 promise.catch(function(error) {
   // failure
 });
 ​
 //promise.finally:无论成功失败都最终执行
 promise.catch(function(error) {
   // some end function
 });
 ​
  • Promise.resolve promise = Promise.resolve(value)
     //相当于
     promise = new Promise((resolve) => resolve(value));
  • Promise.reject promise = Promise.reject(value)
     //相当于
     promise = new Promise((resolve,reject) => reject(value));

Promise方法

Promise常用的方法:all()、race()、any()。下面就来看一下这些方法。

  • Promise.all只有当该数组中的所有Promise完成后才会由pendding状态变为resolve执行then里面的回调函数,若数组中有任意一个promise被拒绝则会执行失败回调,catch方法会捕获到首个被执行的 reject函数 Promise.all([promise1,promise2,…]).then(res => {
      //res:一个数组,这个数组按顺序保存着每一个promise对象resolve执行时的值
     }).catch(err => {
       //err:首个被执行的reject的结果
     })
  • Promise.race当promise数组中任意一个promise被拒绝或者成功,则会采用第一个promise作为他的返回值。若为成功的执行then,若失败则执行catch。 Promise.any([promise1,promise2,…]).then(res => {
      //res:返回第一个结果(如果成功)
     }).catch(err => {
       //err:返回第一个结果(如果失败)
     })
  • Promise.any当传入的promise数组中有任意一个完成时就会终止,会忽略到所有被拒绝掉的promise,直到第一个promise完成。若传入所有的promise被拒绝则会执行拒绝回调。 Promise.race([promise1,promise2,…]).then(res => {
      //res:返回第一个成功结果
     }).catch(err => {
       //err:AggregateError: All promises were rejected(所有结果都失败了)
     })

Promise 实现

 const PENDING = "pending" //待解决的
 const FULFILLED = "fulfilled" //已实现
 const REJECTED = "rejected" //已失败
 ​
 class MyPromise {
     state = PENDING
     value = undefined
     callbacks = []
 ​
     /*
    * 把MyPromise的_resolve和_reject绑定了本MyPromise的this的方法传给参数
    * new MyPromise((resolve,reject)=>{
    *   //executor要执行的函数
    * })
    * */
     constructor(executor) {
         try {
             //(resolve,reject)=>{some function}
             executor(this._resolve.bind(this), this._reject.bind(this))
        } catch (error) {
             this._reject(error)
        }
    }
 ​
     /*
    * 用户觉得成功后,来执行resolve(value)
    * */
     _resolve(value) {
         if (this.state !== PENDING) return;//确保Promise的状态确定不变性
         if (typeof value === 'object' && value instanceof MyPromise) {
             //传来的是MyPromise实例:如果它成功了则让它执行本实例的_resolve(),否则_reject()
             /*
            * resolve(new MyPromise((resolve,reject)=>{   }))
            * */
             value.then(this._resolve.bind(this), this._reject.bind(this))
        } else {
             //把本MyPromise实例置为成功态,并触发后续回调(之前绑定在本实例的then(回调函数))
             this.state = FULFILLED;
             this.value = value
             this.callbacks.forEach(callback => this._handle(callback))//交给_handle去调用callback
        }
    }
 ​
     /*
    * 用户觉得失败后,来执行reject(reason)
    * */
     _reject(reason) {
         if (this.state !== PENDING) return;//确保Promise的状态确定不变性
         if (typeof reason === 'object' && reason instanceof MyPromise) {
             //传来的是MyPromise实例:如果它成功了则让它执行本实例的_resolve(),否则_reject()
             /*
            * reject(new MyPromise((resolve,reject)=>{   }))
            * */
             reason.then(this._resolve.bind(this), this._reject.bind(this));
        } else {
             this.state = REJECTED;
             this.value = reason
             this.callbacks.forEach(callback => this._handle(callback))//交给_handle去调用callback
        }
    }
 ​
     /*
    * 回调函数处理器
    * _handle(callback)
    * 参数的 callback = {onFulfilled, onRejected, resolve, reject}
    * 参数的 callback = {.then(res=>{ onFulfilled执行内容 }), .catch(err=>{onRejected执行内容}), resolve, reject}
    * _handle({即将要执行的成功回调, 即将要执行的失败回调, 下一个MyPromise实例的成功调用, 下一个MyPromise实例的失败调用})
    * */
     _handle(callback) {
         if (this.state === PENDING) {
             //还没执行完,先放到回调队列
             this.callbacks.push(callback)
        } else {
             try {
                 if (this.state === FULFILLED) {
                     //成功态调用成功回调,把本MyPromise的resolve执行时传过来的参数传给onFulfilled
                     let res = (typeof callback.onFulfilled) === 'function' ? callback.onFulfilled(this.value) : this.value
                     // 如果onFulfilled是函数则把他的结果返回给下一个MyPromise实例,如果不是函数就传 本MyPromise的resolve执行时传过来的参数
                     callback.resolve(res)
                } else if (this.state === REJECTED) {
                     //失败态调用失败回调
                     if (typeof callback.onRejected === 'function') {
                         //有处理Rejected的回调函数则处理,并返回它的返回值,把下一个MyPromise实例置为成功态
                         let res = callback.onRejected(this.value)
                         callback.resolve(res)
                    } else {
                         //没有则说明还没处理,继续报错下去,把下一个MyPromise实例置为失败态
                         callback.reject(this.value)
                    }
                }
            } catch (error) {
                 //失败了,去调用下一个MyPromise的reject方法通知失败了
                 callback.reject(error)
            }
        }
    }
 ​
     /*
    * promise2 = promise.then(res=>{
    *   //成功的回调
    * },err=>{
    *   //失败的回调
    * })
    * 返回了新的MyPromise实例(promise2),并且马上触发本实例(promise)的_handle
    * */
     then(onFulfilled, onRejected) {
         return new MyPromise((resolve, reject) => {
             this._handle({onFulfilled, onRejected, resolve, reject})
        })
    }
 ​
     //同上,与then的第二个回调一样
     catch(onError) {
         return this.then(null, onError)
    }
 ​
     //其实就是通过本实例的.then()生成新的MyPromise实例,但无论onDone回调返回什么值,是否报错都无法获取或捕获,只负责把当前状态和值转给下一个MyPromise实例
     finally(onDone) {
         if (typeof onDone !== 'function') {
             return this.then()
        } else {
             return this.then(
                 value => MyPromise.resolve(onDone()).then(() => value),
                 reason => MyPromise.resolve(onDone()).then(() => {
                     throw reason
                })
            );
        }
    }
 ​
     //静态函数区域
     //MyPromise.resolve()
     static resolve(value) {
         if (typeof value === 'object' && value instanceof MyPromise) {
             //如果已经是MyPromise实例就不用包装了
             return value
        } else if (value && typeof value === 'object' && typeof value.then == 'function') {
             //如果是对象类型,且有自带的then方法 返回一个新的MyPromise实例并马上置为成功态且
             /*
            * MyPromise.resolve({
            *   then(resolve,reject){
            *       ...,
            *       resolve(someVal) //或者reject(someVal)
            *   }
            * })
            * */
             return new MyPromise((resolve,reject) => value.then(resolve,reject))
        } else {
             //最常用的是只传了值,就直接返回新的MyPromise实例并置为成功态且把它传过去
             return new MyPromise(resolve => resolve(value))
        }
    }
 ​
     //MyPromise.reject()
     static reject(reason) {
         return new MyPromise((resolve,reject) => reject(reason))
    }
 ​
  //MyPromise.all()
     static all(promiseList) {
         return new MyPromise((resolve, reject) => {
             let res = []
             let fulfilledCount = 0
             promiseList.forEach((promise, index) => {
                 MyPromise.resolve(promise).then(result => {
                     res[index] = result
                     fulfilledCount++
                     if (onFulfilledNum === promiseList.length) resolve(res)
                }).catch(err => {
                     res[index] = err
                     reject(err)
                })
            })
        })
    }
 ​
  //MyPromise.race()
     static race(promiseList) {
         return new MyPromise((resolve, reject) => {
             promiseList.forEach((promise, index) => {
                 MyPromise.resolve(promise).then(result => {
                     resolve(result)
                }).catch(err => {
                     reject(err)
                })
            })
        })
    }
 }

Generator函数

Generator函数:是ES6提供的解决异步编程的方案之一,它是一个状态机,内部封装了不同状态的数据,用来生成遍历器对象,可暂停函数(惰性求值)yield暂停next 方法可启动。每次返回的是yield后的表达式结果。

特点:

  • function与函数名之间有一个星号
  • 内部用yield表达式来定义不同的状态
  • generator函数返回指针对象(iterator),而不会执行函数内部逻辑
  • 调用next方法函数内部逻辑开始执行,遇到yield表达式停止, 返回 { value: yield 后的表达式结果 }
  • 再次调用next方法会从上一次停止时的yield处开始,直到最后
  • yield语句返回结果通常为undefined,当调用next方法时传参内容会作为启动时yield语句的返回值。
 //function* myGenerator()
 //function * myGenerator()
 //function *myGenerator() 都可以
 function* myGenerator() {
   console.log('开始执行');
   let result = yield 'Hello';
   console.log(result);
   console.log('暂停后再次执行');
   yield 'generator';
   console.log('遍历完毕');
   return '返回结果';
 }
 console.log('Generator函数:');
 let MG = myGenerator(); // 返回的是指针对象
 console.log(MG);//myGenerator {<suspended>}
 console.log(MG.next());//开始执行 {value: 'Hello', done: false}
 console.log(MG.next('aaaaaaaaaaa'));//aaaaaaaaaaa 暂停后再次执行 {value: 'generator', done: false}
 console.log(MG.next());//遍历完毕 {value: '返回结果', done: true}
 console.log(MG.next());//{value: undefined, done: true}
 console.log(MG.next());//undefined 最后一个用return返回,所以是undefined,如果最后是用yield,就一直返回{value: undefined, done: true}
 ​
 let o = {}
 o[Symbol.iterator] = function* myFun(){
   yield 1;
   yield 2;
   yield 3;
 }
 for(let item of o){
  console.log(item)
 }
 //1 2 3

async/await

async/await其实是 Generator 的语法糖,它能实现的效果都能用then链来实现,它是为优化then链而开发出来的。async函数返回的是一个 Promise 对象,如果在函数中 return 一个直接量,会通过Promise.resolve()封装成 Promise 对象,之后可通过then来把它拿出来。

 async function testAsy(){
    return 'hello world'
 }
 let result = testAsy()
 console.log(result) // Promise {<fulfilled>: 'hello world'}
 result.then(v=>{
     console.log(v)   // hello world
 })

await:await暂停当前async的执行,让出当前async函数线程,等待一个 promise 完成 或者 其它值

  • 如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西。
  • 如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。
 async function testAsync() {
     return new Promise((resolve,reject)=>{
       console.log('promise start')
  setTimeout(()=>resolve('promise ok'),1000)
  })
 }
 async function test() {
  console.log('async start');
     const v1 = await "OK";
     console.log(v1); //OK
     const v2 = await testAsync();
     console.log(v2); //promise ok
 }
 test();
 console.log('script end')
 //输出顺序:async start、script end、OK、promise start、promise ok

优点

  • 优化then链:代码读起来更加同步,Promise虽然摆脱了回调地狱,但是then的链式调⽤也会带来额外的阅读负担
  • 中间值传递写法优雅:Promise传递中间值⾮常麻烦,⽽async/await⼏乎是同步的写法,⾮常优雅
  • 错误处理友好:async/await可以⽤成熟的try/catch,Promise的错误捕获⾮常冗余
  • 调试友好:Promise的调试很差,由于没有代码块,你不能在⼀个返回表达式的箭头函数中设置断点,如果你在⼀个.then代码块中使⽤调试器的步进(step-over)功能,调试器并不会进⼊后续的.then代码块,因为调试器只能跟踪同步代码的每⼀步。

async/await 如何捕获异常

 async function fn(){
     try{
         let a = await Promise.reject('error')
    }catch(error){
         console.log(error)
    }
 }

JavaScript:手撕Promise

const PENDING = "pending" //待解决的
const FULFILLED = "fulfilled" //已实现
const REJECTED = "rejected" //已失败

class MyPromise {
    state = PENDING
    value = undefined
    callbacks = []

    /*
    * 把MyPromise的_resolve和_reject绑定了本MyPromise的this的方法传给参数
    * new MyPromise((resolve,reject)=>{
    *   //executor要执行的函数
    * })
    * */
    constructor(executor) {
        try {
            //(resolve,reject)=>{some function}
            executor(this._resolve.bind(this), this._reject.bind(this))
        } catch (error) {
            this._reject(error)
        }
    }

    /*
    * 用户觉得成功后,来执行resolve(value)
    * */
    _resolve(value) {
        if (this.state !== PENDING) return;//确保Promise的状态确定不变性
        if (typeof value === 'object' && value instanceof MyPromise) {
            //传来的是MyPromise实例:如果它成功了则让它执行本实例的_resolve(),否则_reject()
            /*
            * resolve(new MyPromise((resolve,reject)=>{   }))
            * */
            value.then(this._resolve.bind(this), this._reject.bind(this))
        } else {
            //把本MyPromise实例置为成功态,并触发后续回调(之前绑定在本实例的then(回调函数))
            this.state = FULFILLED;
            this.value = value
            this.callbacks.forEach(callback => this._handle(callback))//交给_handle去调用callback
        }
    }

    /*
    * 用户觉得失败后,来执行reject(reason)
    * */
    _reject(reason) {
        if (this.state !== PENDING) return;//确保Promise的状态确定不变性
        if (typeof reason === 'object' && reason instanceof MyPromise) {
            //传来的是MyPromise实例:如果它成功了则让它执行本实例的_resolve(),否则_reject()
            /*
            * reject(new MyPromise((resolve,reject)=>{   }))
            * */
            reason.then(this._resolve.bind(this), this._reject.bind(this));
        } else {
            this.state = REJECTED;
            this.value = reason
            this.callbacks.forEach(callback => this._handle(callback))//交给_handle去调用callback
        }
    }

    /*
    * 回调函数处理器
    * _handle(callback)
    * 参数的 callback = {onFulfilled, onRejected, resolve, reject}
    * 参数的 callback = {.then(res=>{ onFulfilled执行内容 }), .catch(err=>{onRejected执行内容}), resolve, reject}
    * _handle({即将要执行的成功回调, 即将要执行的失败回调, 下一个MyPromise实例的成功调用, 下一个MyPromise实例的失败调用})
    * */
    _handle(callback) {
        if (this.state === PENDING) {
            //还没执行完,先放到回调队列
            this.callbacks.push(callback)
        } else {
            try {
                if (this.state === FULFILLED) {
                    //成功态调用成功回调,把本MyPromise的resolve执行时传过来的参数传给onFulfilled
                    let res = (typeof callback.onFulfilled) === 'function' ? callback.onFulfilled(this.value) : this.value
                    // 如果onFulfilled是函数则把他的结果返回给下一个MyPromise实例,如果不是函数就传 本MyPromise的resolve执行时传过来的参数
                    callback.resolve(res)
                } else if (this.state === REJECTED) {
                    //失败态调用失败回调
                    if (typeof callback.onRejected === 'function') {
                        //有处理Rejected的回调函数则处理,并返回它的返回值,把下一个MyPromise实例置为成功态
                        let res = callback.onRejected(this.value)
                        callback.resolve(res)
                    } else {
                        //没有则说明还没处理,继续报错下去,把下一个MyPromise实例置为失败态
                        callback.reject(this.value)
                    }
                }
            } catch (error) {
                //失败了,去调用下一个MyPromise的reject方法通知失败了
                callback.reject(error)
            }
        }
    }

    /*
    * promise2 = promise.then(res=>{
    *   //成功的回调
    * },err=>{
    *   //失败的回调
    * })
    * 返回了新的MyPromise实例(promise2),并且马上触发本实例(promise)的_handle
    * */
    then(onFulfilled, onRejected) {
        return new MyPromise((resolve, reject) => {
            this._handle({onFulfilled, onRejected, resolve, reject})
        })
    }

    //同上,与then的第二个回调一样
    catch(onError) {
        return this.then(null, onError)
    }

    //其实就是通过本实例的.then()生成新的MyPromise实例,但无论onDone回调返回什么值,是否报错都无法获取或捕获,只负责把当前状态和值转给下一个MyPromise实例
    finally(onDone) {
        if (typeof onDone !== 'function') {
            return this.then()
        } else {
            return this.then(
                value => MyPromise.resolve(onDone()).then(() => value),
                reason => MyPromise.resolve(onDone()).then(() => {
                    throw reason
                })
            );
        }
    }

    //静态函数区域
    //MyPromise.resolve()
    static resolve(value) {
        if (typeof value === 'object' && value instanceof MyPromise) {
            //如果已经是MyPromise实例就不用包装了
            return value
        } else if (value && typeof value === 'object' && typeof value.then == 'function') {
            //如果是对象类型,且有自带的then方法 返回一个新的MyPromise实例并马上置为成功态且
            /*
            * MyPromise.resolve({
            *   then(resolve,reject){
            *       ...,
            *       resolve(someVal) //或者reject(someVal)
            *   }
            * })
            * */
            return new MyPromise((resolve,reject) => value.then(resolve,reject))
        } else {
            //最常用的是只传了值,就直接返回新的MyPromise实例并置为成功态且把它传过去
            return new MyPromise(resolve => resolve(value))
        }
    }

    //MyPromise.reject()
    static reject(reason) {
        return new MyPromise((resolve,reject) => reject(reason))
    }

  	//MyPromise.all()
    static all(promiseList) {
        return new MyPromise((resolve, reject) => {
            let res = []
            let fulfilledCount = 0
            promiseList.forEach((promise, index) => {
                MyPromise.resolve(promise).then(result => {
                    res[index] = result
                    fulfilledCount++
                    if (onFulfilledNum === promiseList.length) resolve(res)
                }).catch(err => {
                    res[index] = err
                    reject(err)
                })
            })
        })
    }

  	//MyPromise.race()
    static race(promiseList) {
        return new MyPromise((resolve, reject) => {
            promiseList.forEach((promise, index) => {
                MyPromise.resolve(promise).then(result => {
                    resolve(result)
                }).catch(err => {
                    reject(err)
                })
            })
        })
    }
}

参考链接:
https://zhuanlan.zhihu.com/p/58428287
https://juejin.cn/post/6844904063570542599

JavaScript:函数柯里化

定义

柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。

关键点

1.利用闭包来缓存原函数传递参数

2.如果传递的参数足够原函数使用(传递参数达到形参数量 fun.length )就执行,否则继续合并且缓存参数,返回柯里化函数。

代码

function curry(fun,preArgs=[]){
  return function(...args){
    let curArgs = [...preArgs,...args]
    if(curArgs.length>=fun.length){
      return fun.apply(this,curArgs)
    }else{
      return curry.call(this,fun,curArgs)
    }
  }
}

function add(a,b,c,d,e) {
  console.log('a,b,c,d,e:',a,b,c,d,e)
  console.log('this.name:',this.name)
  return a+b+c+d+e
}

var obj={
  addFun:curry(add),
  name:'Obj'
}
var name = 'Win'

var test1 = obj.addFun(1,2,3)()(4)(5)
//a,b,c,d,e: 1 2 3 4 5
//this.name: Win

var test2 = obj.addFun(1,2,3,4,5)
//a,b,c,d,e: 1 2 3 4 5
//this.name: Obj

var temp = obj.addFun(1,2,3)()(4)
var test3 = temp(5)
//a,b,c,d,e: 1 2 3 4 5
//this.name: Win

console.log(test1,test2,test3)
//15 15 15

文章引用来源:https://github.com/mqyqingfeng/Blog/issues/42

JS数组、对象常用函数汇总

数组常用函数:

函数名(Arrary原型链上)是否改变原数组返回值参数描述
push新的长度新元素1,新元素2…向末尾添加多个元素
unshift新的长度新元素1,新元素2…向开头添加多个元素
pop最后一个元素删除最后一个元素
shift第一个元素删除第一个元素
slice新数组开始的索引,结束的索引(不包含)提取指定的元素
splice被删除的元素数组开始的索引,删除的数量,新元素1,新元素2…删除指定元素,并可添加新元素(插入替换)
concat新数组需要连接的数组/元素1,需要连接的数组/元素2…连接多个数组
toString连接后的字符串数组转字符串(用,隔开)
join连接后的字符串连接符(字符串)数组转字符串
reverse修改后的数组本身颠倒顺序
sort排序回调函数(对比元素1,对比元素2)用于数组排序,若返回大于0的值会交换位置
map新数组回调函数((item,index,array) => newItem),thisArg映射
filter新数组回调函数((item,index,array) => true(保留)/false(剔除)),thisArg过滤器
reduce汇总结果lastTemp回调函数((temp,item,index,array) => newTemp),initialValue汇总
reduceRight汇总结果lastTemp回调函数((temp,item,index,array) => newTemp),initialValue汇总(从右到左)
forEach回调函数((item,index,array) => {…}),thisArg循环迭代 遍历
every检查结果(Boolean)回调函数((item,index,array) => true(继续)/false(退出循环)),thisArg检查是否所有值都满足要求
some检查结果(Boolean)回调函数((item,index,array) => true(退出循环)/false(继续)),thisArg检查是否有任意值都满足要求
indexOf元素下标检索的元素值,开始查找的位置检索数组中的某个元素值
lastIndexOf元素下标检索的元素值,开始查找的位置检索数组中的某个元素值(右往左)
find第一个满足条件返回true的元素回调函数((item,index,arr) => true(返回item)/false(继续搜索)),thisArg查找元素
findIndex第一个满足条件返回true的元素下标回调函数((item,index,arr) => true(返回item)/false(继续搜索)),thisArg查找元素下标
includes检查结果(Boolean)检索的元素值,开始查找的位置判断数组中是否包含指定的value
values一个新的 Array 迭代对象返回Array Iterator对象(含数组每个索引的值),不兼容IE
entries一个新的 Array 迭代器对象返回Array Iterator对象(含数组每个索引的键值对),不兼容IE
flat新数组递归深度(默认1)扁平化嵌套数组,不兼容IE
flatMap新数组和map一样的回调函数((item,index,array) => newItem),thisArg使用映射函数映射每个元素后将结果压缩成一个新数组
copyWithin复制后的数组目标位置(复制到此),复制起始下标,复制结束下标(不包含)浅复制数组的一部分到同一数组中的另一个位置,不兼容IE
Array.from新数组字符串或类数组或对象(具有length属性和数字下标键),map回调函数,thisArg将一个类数组或对象转换成一个真正的数组,不兼容IE
Array.of新数组新元素1,新元素2…将一系列值转换成数组
Array.isArray检查结果(Boolean)被测对象检查对象是否为数组

对象常用函数:

函数名(蓝色Arrary原型链上)是否改变原对象返回值参数描述
create带指定原型对象和属性的新对象原型对象,{属性名:{描述符…},…}指定对象为原型创建新的对象(创建一个干净的对象)
defineProperty被修改对象被修改对象,属性名,描述符添加或更改对象属性
defineProperties被修改对象被修改对象,{属性名:{描述符…},…}添加或更改多个对象属性
preventExtensions处理后的对象被处理的对象拓展性,防止属性拓展(不可拓展属性)
isExtensible检查结果(Boolean)被处理的对象判断是否可拓展
seal处理后的对象被处理的对象密封,防止更改对象属性(不可拓展/删除属性)
isSealed检查结果(Boolean)被处理的对象判断是否密封
freeze处理后的对象被处理的对象冻结,防止进行任何更改(不可拓展/删除属性/修改值)
isFrozen检查结果(Boolean)被处理的对象判断是否冻结
getOwnPropertyDescriptors{属性名:{描述符…},…}对象返回所有属性描述符(不含原型链),不兼容IE
getOwnPropertyDescriptor其属性描述符对象{描述符…}对象,属性名访问属性
getOwnPropertyNames自身对象的所有属性名数组对象以数组返回对象自身所有属性名(不包括Symbol和原型链上属性,包含非枚举属性)
getPrototypeOf给定对象的原型对象访问原型,相当于对象.__proto__
getOwnPropertySymbols自身对象的所有Symbol属性数组对象返回一个给定对象自身的所有Symbol属性的数组。不兼容IE
keys属性名数组(可枚举)对象返回给定对象的自身可枚举属性组成的数组
values值数组(可枚举)对象返回一个给定对象自身的所有可枚举属性值的数组,不兼容IE
entries键值对[属性名,值]数组(可枚举)对象返回一个给定对象自身可枚举属性的键值对[key,value]数组,不兼容IE
fromEntries新对象可迭代对象把键值对列表转换为一个对象。不支持IE、安卓Opera
is检查结果(Boolean)被比较value1,被比较value2判断两个值是否为同一个值
assign目标对象(被改原对象)目标对象,源对象1,源对象2…将源对象的属性复制到目标对象上
hasOwn检查结果(Boolean)对象,属性名判断是否含有指定属性(不包括继承原型链上),仅火狐支持
setPrototypeOf被设置对象被设置对象,新的原型对象设置一个指定的对象的原型
hasOwnProperty检查结果(Boolean)对象,属性名判断是否含有指定属性(不包括继承原型链上)
isPrototypeOf检查结果(Boolean)被测的另一个对象测试一个对象是否存在于另一个对象的原型链上
propertyIsEnumerable检查结果(Boolean)被测属性测试指定的属性是否可枚举
toString字符串返回一个表示该对象的字符串
valueOf该对象的原始值返回指定对象的原始值

http-axios请求分析

原文件(表格)下载:

请求方法 | 参数类型分析:

请求方法URL请求参数类型请求体请求体内容content-typerequest$_GET$_REQUEST$_POST$_FILES备注
gethttp://localhost:8085/devApi/test00000
posthttp://localhost:8086/devApi/test00000
deletehttp://localhost:8087/devApi/test00000
puthttp://localhost:8088/devApi/test00000
patchhttp://localhost:8089/devApi/test00000
headhttp://localhost:8090/devApi/test
optionshttp://localhost:8091/devApi/test
connect  请求失败        
trace  请求失败        
            
gethttp://localhost:8080/devApi/test?a=1&b=2paramsQuery String Parametersa=1&b=2&abc=311100params请求
posthttp://localhost:8080/devApi/test?a=1&b=2paramsQuery String Parametersa=1&b=2&abc=311100
deletehttp://localhost:8080/devApi/test?a=1&b=2paramsQuery String Parametersa=1&b=2&abc=311100
puthttp://localhost:8080/devApi/test?a=1&b=2paramsQuery String Parametersa=1&b=2&abc=311100
patchhttp://localhost:8080/devApi/test?a=1&b=2paramsQuery String Parametersa=1&b=2&abc=311100
headhttp://localhost:8080/devApi/test?a=1&b=2paramsQuery String Parametersa=1&b=2&abc=3
optionshttp://localhost:8080/devApi/test?a=1&b=2paramsQuery String Parametersa=1&b=2&abc=3
            
gethttp://localhost:8080/devApi/testdataapplication/json;charset=utf-800000get不能带上data,data默认以json方式请求
posthttp://localhost:8080/devApi/testdataRequest Payload{“aa”:11,”bb”:22,”ab”:4}application/json;charset=utf-810000
deletehttp://localhost:8081/devApi/testdataRequest Payload{“aa”:11,”bb”:22,”ab”:4}application/json;charset=utf-810000
puthttp://localhost:8082/devApi/testdataRequest Payload{“aa”:11,”bb”:22,”ab”:4}application/json;charset=utf-810000
patchhttp://localhost:8083/devApi/testdataRequest Payload{“aa”:11,”bb”:22,”ab”:4}application/json;charset=utf-810000
headhttp://localhost:8084/devApi/testdataapplication/json;charset=utf-8head与get相似且不返回内容
optionshttp://localhost:8085/devApi/testdataRequest Payload{“aa”:11,”bb”:22,”ab”:4}application/json;charset=utf-8options与post相似且不返回内容
            
gethttp://localhost:8080/devApi/test?a=1&b=2paramsQuery String Parametersa=1&b=2&abc=3application/json;charset=utf-811100
posthttp://localhost:8080/devApi/test?a=1&b=2params、dataQuery String Parameters、Request Payloada=1&b=2&abc=3 和 {“aa”:11,”bb”:22,”ab”:4}application/json;charset=utf-8{a: “1”, b: “2”, abc: 4, aa: 11, bb: 22}{a: “1”, b: “2”, abc: “3”}{a: “1”, b: “2”, abc: “3”}00data优先级比params高
deletehttp://localhost:8080/devApi/test?a=1&b=2params、dataQuery String Parameters、Request Payloada=1&b=2&abc=3 和 {“aa”:11,”bb”:22,”ab”:4}application/json;charset=utf-8{a: “1”, b: “2”, abc: 4, aa: 11, bb: 22}{a: “1”, b: “2”, abc: “3”}{a: “1”, b: “2”, abc: “3”}00
puthttp://localhost:8080/devApi/test?a=1&b=2params、dataQuery String Parameters、Request Payloada=1&b=2&abc=3 和 {“aa”:11,”bb”:22,”ab”:4}application/json;charset=utf-8{a: “1”, b: “2”, abc: 4, aa: 11, bb: 22}{a: “1”, b: “2”, abc: “3”}{a: “1”, b: “2”, abc: “3”}00
patchhttp://localhost:8080/devApi/test?a=1&b=2params、dataQuery String Parameters、Request Payloada=1&b=2&abc=3 和 {“aa”:11,”bb”:22,”ab”:4}application/json;charset=utf-8{a: “1”, b: “2”, abc: 4, aa: 11, bb: 22}{a: “1”, b: “2”, abc: “3”}{a: “1”, b: “2”, abc: “3”}00
headhttp://localhost:8080/devApi/test?a=1&b=2paramsQuery String Parametersa=1&b=2&abc=3application/json;charset=utf-8
optionshttp://localhost:8080/devApi/test?a=1&b=2params、dataQuery String Parameters、Request Payloada=1&b=2&abc=3 和 {“aa”:11,”bb”:22,”ab”:4}application/json;charset=utf-8
            
            
            
params为URL上的参数;data为请求体内的参数

请求体 | 内容格式 分析:

请求方式请求参数类型请求体请求内容请求内容解析参数转化(URL: paramsSerializer,Body: transformRequest)content-typerequest$_GET$_REQUEST$_POST$_FILES备注
getparamsQuery String Parametersa=1&arr%5B0%5D=1&arr%5B1%5D=a&obj%5Bq%5D=q&obj%5Bw%5D=w&mix1%5B0%5D%5Ba%5D=1&mix1%5B1%5D%5B0%5D=2&mix1%5B2%5D=3&mix2%5Ba%5D=1&mix2%5Bb%5D%5B0%5D=1&mix2%5Bc%5D%5Ba%5D=1a: 1
arr[0]: 1
arr[1]: a
obj[q]: q
obj[w]: w
mix1[0][a]: 1
mix1[1][0]: 2
mix1[2]: 3
mix2[a]: 1
mix2[b][0]: 1
mix2[c][a]: 1
data => qs.stringify(data)正常解析Arr/Obj,num为str正常解析Arr/Obj,num为str正常解析Arr/Obj,num为str00
getparamsQuery String Parametersa=1&arr[]=1&arr[]=a&obj=%7B%22q%22:%22q%22,%22w%22:%22w%22%7D&mix1[]=%7B%22a%22:1%7D&mix1[]=[2]&mix1[]=3&mix2=%7B%22a%22:1,%22b%22:[1],%22c%22:%7B%22a%22:1%7D%7Da: 1
arr[]: 1
arr[]: a
obj: {“q”:”q”,”w”:”w”}
mix1[]: {“a”:1}
mix1[]: [2]
mix1[]: 3
mix2: {“a”:1,”b”:[1],”c”:{“a”:1}}
解析第一层析Arr/Obj后面为JSON解析第一层析Arr/Obj后面为JSON解析第一层析Arr/Obj后面为JSON00
getparamsQuery String Parameters{%22a%22:1,%22arr%22:[1,%22a%22],%22obj%22:{%22q%22:%22q%22,%22w%22:%22w%22},%22mix1%22:[{%22a%22:1},[2],3],%22mix2%22:{%22a%22:1,%22b%22:[1],%22c%22:{%22a%22:1}}}{“a”:1,”arr”:[1,”a”],”obj”:{“q”:”q”,”w”:”w”},”mix1″:[{“a”:1},[2],3],”mix2″:{“a”:1,”b”:[1],”c”:{“a”:1}}}: data => JSON.stringify(data)解析异常解析异常解析异常00
             
postdataRequest Payload{“a2″:1,”arr2″:[1,”a”],”obj2″:{“q”:”q”,”w”:”w”},”mix12″:[{“a”:1},[2],3],”mix22″:{“a”:1,”b”:[1],”c”:{“a”:1}}}正常解析的Arr/Obj默认 application/json;charset=utf-8正常解析Arr/Obj0000
postdataForm Dataa2=1&arr2%5B0%5D=1&arr2%5B1%5D=a&obj2%5Bq%5D=q&obj2%5Bw%5D=w&mix12%5B0%5D%5Ba%5D=1&mix12%5B1%5D%5B0%5D=2&mix12%5B2%5D=3&mix22%5Ba%5D=1&mix22%5Bb%5D%5B0%5D=1&mix22%5Bc%5D%5Ba%5D=1a2: 1
arr2[0]: 1
arr2[1]: a
obj2[q]: q
obj2[w]: w
mix12[0][a]: 1
mix12[1][0]: 2
mix12[2]: 3
mix22[a]: 1
mix22[b][0]: 1
mix22[c][a]: 1
data => qs.stringify(data)默认 application/x-www-form-urlencoded正常解析Arr/Obj,num为str0正常解析Arr/Obj,num为str正常解析Arr/Obj,num为str0
postdataForm Data{“a2″:1,”arr2″:[1,”a”],”obj2″:{“q”:”q”,”w”:”w”},”mix12″:[{“a”:1},[2],3],”mix22″:{“a”:1,”b”:[1],”c”:{“a”:1}}}{“a2″:1,”arr2″:[1,”a”],”obj2″:{“q”:”q”,”w”:”w”},”mix12″:[{“a”:1},[2],3],”mix22″:{“a”:1,”b”:[1],”c”:{“a”:1}}}: data => JSON.stringify(data)默认 application/x-www-form-urlencoded解析异常0解析异常解析异常0
postdataRequest Payload{“a2″:1,”arr2″:[1,”2″,”a”],”obj2″:{“q”:”q”,”w”:”w”},”mix12″:[{“a”:1},[2],3],”mix22″:{“a”:1,”b”:[1],”c”:{“a”:1}}}正常解析的Arr/Objdata => JSON.stringify(data)application/json;charset=utf-8正常解析Arr/Obj0000
postdataRequest Payloada2=1&arr2%5B0%5D=1&arr2%5B1%5D=2&arr2%5B2%5D=a&obj2%5Bq%5D=q&obj2%5Bw%5D=w&mix12%5B0%5D%5Ba%5D=1&mix12%5B1%5D%5B0%5D=2&mix12%5B2%5D=3&mix22%5Ba%5D=1&mix22%5Bb%5D%5B0%5D=1&mix22%5Bc%5D%5Ba%5D=1无法解析data => qs.stringify(data)application/json;charset=utf-800000
postdataForm Data{“a2″:1,”arr2″:[1,”2″,”a”],”obj2″:{“q”:”q”,”w”:”w”},”mix12″:[{“a”:1},[2],3],”mix22″:{“a”:1,”b”:[1],”c”:{“a”:1}}}{“a2″:1,”arr2″:[1,”2″,”a”],”obj2″:{“q”:”q”,”w”:”w”},”mix12″:[{“a”:1},[2],3],”mix22″:{“a”:1,”b”:[1],”c”:{“a”:1}}}:  application/x-www-form-urlencoded解析异常0解析异常解析异常0
postdataRequest Payload{“a2″:1,”arr2″:[1,”2″,”a”],”obj2″:{“q”:”q”,”w”:”w”},”mix12″:[{“a”:1},[2],3],”mix22″:{“a”:1,”b”:[1],”c”:{“a”:1}}}正常解析的Arr/Objmultipart/form-data00000
postdataRequest Payloada2=1&arr2%5B0%5D=1&arr2%5B1%5D=2&arr2%5B2%5D=a&obj2%5Bq%5D=q&obj2%5Bw%5D=w&mix12%5B0%5D%5Ba%5D=1&mix12%5B1%5D%5B0%5D=2&mix12%5B2%5D=3&mix22%5Ba%5D=1&mix22%5Bb%5D%5B0%5D=1&mix22%5Bc%5D%5Ba%5D=1无法解析data => qs.stringify(data)multipart/form-data00000
postdataRequest Payload{“a2″:1,”arr2″:[1,”2″,”a”],”obj2″:{“q”:”q”,”w”:”w”},”mix12″:[{“a”:1},[2],3],”mix22″:{“a”:1,”b”:[1],”c”:{“a”:1}}}正常解析的Arr/Objdata => JSON.stringify(data)multipart/form-data00000
             
postformForm Data——WebKitFormBoundaryXHfVashxAsB3Ur7O
Content-Disposition: form-data; name=”file”; filename=”QQ截图20210126215039.png”
Content-Type: image/png


——WebKitFormBoundaryXHfVashxAsB3Ur7O
Content-Disposition: form-data; name=”text”

a
—-C21–WebKitFormBoundaryXHfVashxAsB3Ur7O
Content-Disposition: form-data; name=”num”

1
——WebKitFormBoundaryXHfVashxAsB3Ur7O
Content-Disposition: form-data; name=”obj”

[object Object]
——WebKitFormBoundaryXHfVashxAsB3Ur7O
Content-Disposition: form-data; name=”arr”

1,a,1,2
——WebKitFormBoundaryXHfVashxAsB3Ur7O–E19
file: (binary)
text: a
num: 1
obj: [object Object]
arr: 1,a,1,2
不转化只能默认 multipart/form-data; boundary=—-WebKitFormBoundaryXHfVashxAsB3Ur7O展开解析数组,不解析对象,num为str,有文件不存在‘file’的键,无文件则为file:’null’0展开解析数组,不解析对象,有文件不存在‘file’的键,无文件则为file:’null’展开解析数组,不解析对象,有文件不存在‘file’的键,无文件则为file:’null’文件列表对象
postformForm Data{}{}:data => qs.stringify(data)默认 application/x-www-form-urlencoded解析异常0解析异常解析异常0
postformForm Data{}{}:data => JSON.stringify(data)默认 application/x-www-form-urlencoded解析异常0解析异常解析异常0
postformdata => qs.stringify(data)application/json00000
postformRequest Payload{}{}data => JSON.stringify(data)application/json00000
postformObjForm Data——WebKitFormBoundaryZ92KqK0WZgqO9PzU
Content-Disposition: form-data; name=”a2″

1
——WebKitFormBoundaryZ92KqK0WZgqO9PzU
Content-Disposition: form-data; name=”arr2[0]”

1
——WebKitFormBoundaryZ92KqK0WZgqO9PzU
Content-Disposition: form-data; name=”arr2[1]”

2
——WebKitFormBoundaryZ92KqK0WZgqO9PzU
Content-Disposition: form-data; name=”arr2[2]”

a
——WebKitFormBoundaryZ92KqK0WZgqO9PzU
Content-Disposition: form-data; name=”obj2[q]”

q
——WebKitFormBoundaryZ92KqK0WZgqO9PzU
Content-Disposition: form-data; name=”obj2[w]”

w
——WebKitFormBoundaryZ92KqK0WZgqO9PzU
Content-Disposition: form-data; name=”mix12[0][a]”

1
——WebKitFormBoundaryZ92KqK0WZgqO9PzU
Content-Disposition: form-data; name=”mix12[1][0]”

2
——WebKitFormBoundaryZ92KqK0WZgqO9PzU
Content-Disposition: form-data; name=”mix12[2]”

3
——WebKitFormBoundaryZ92KqK0WZgqO9PzU
Content-Disposition: form-data; name=”mix22[a]”

1
——WebKitFormBoundaryZ92KqK0WZgqO9PzU
Content-Disposition: form-data; name=”mix22[b][0]”

1
——WebKitFormBoundaryZ92KqK0WZgqO9PzU
Content-Disposition: form-data; name=”mix22[c][a]”

1
——WebKitFormBoundaryZ92KqK0WZgqO9PzU
Content-Disposition: form-data; name=”files[]”; filename=”QQ截图20210127214015.png”
Content-Type: image/png


——WebKitFormBoundaryZ92KqK0WZgqO9PzU
Content-Disposition: form-data; name=”files[]”; filename=”QQ截图20210127214015.png”
Content-Type: image/png


——WebKitFormBoundaryZ92KqK0WZgqO9PzU
Content-Disposition: form-data; name=”file”; filename=”QQ截图20210127214015.png”
Content-Type: image/png


——WebKitFormBoundaryZ92KqK0WZgqO9PzU–
a2: 1
arr2[0]: 1
arr2[1]: 2
arr2[2]: a
obj2[q]: q
obj2[w]: w
mix12[0][a]: 1
mix12[1][0]: 2
mix12[2]: 3
mix22[a]: 1
mix22[b][0]: 1
mix22[c][a]: 1
files[]: (binary)
files[]: (binary)
file: (binary)
data => {
            let formData = new FormData;
            qs.stringify(data,{encode:false,skipNulls:true}).split(‘&’).forEach(item=>{
              let arr = item.split(‘=’)
              formData.append(arr[0],arr[1])
            })
            //遍历data对象
            for (let k in data){
              //处理单文件
              if (data[k] instanceof File){
                formData.append(k,data[k])
              }else if (Array.isArray(data[k]) && data[k].length>0 &&(data[k][0] instanceof File)){
                //处理多文件
                data[k].forEach(item=>{
                  formData.append(k+'[]’,item)
                })
              }
            }
            return formData
          }
默认 multipart/form-data; boundary=—-WebKitFormBoundaryZ92KqK0WZgqO9PzU正常解析Arr/Obj,num为str0正常解析Arr/Obj,num为str正常解析Arr/Obj,num为str文件列表对象
             

前端学习路线汇总

一、参考手册
①W3school代码手册:https://www.w3school.com.cn/
②MDN参考手册: https://developer.mozilla.org/zh-CN/
③菜鸟教程:https://www.runoob.com/html/html-tutorial.html
④Vue官方文档:https://cn.vuejs.org/v2/guide/
⑤Vue-Cli脚手架官方文档:https://cli.vuejs.org/zh/guide/
⑥JQuery:https://api.jquery.com/
⑦Typescript (TS):https://www.typescriptlang.org/zh/
⑧Webpack:https://webpack.docschina.org/concepts/
⑨Nuxt.js:https://nuxtjs.org/
⑩SSR:https://ssr.vuejs.org/zh/

二、教学视频
第一阶段:学习HTML5+CSS3
李南江的HTML5+CSS3零基础教程(看这个就够了)
https://www.bilibili.com/video/av21212670

第二阶段:学习JavaScript(JS)脚本语言
尚硅谷的JavaScript零基础入门(1-140集为入门):
https://www.bilibili.com/video/BV1YW411T7GX

第三阶段:学习移动端设计(Flex布局是重点)
黑马程序员pink老师前端入门教程(1-390查缺补漏选看,重点看390集后)
https://www.bilibili.com/video/BV14J4114768?p=390

第四阶段:学习ES6+新版本(Promise Async 箭头函数等都是重点):
https://www.bilibili.com/video/BV1uK411H7on

第五阶段:学习JS原理和高级部分:
https://www.bilibili.com/video/BV14s411E7qf

选看:JQuery (用了Vue后基本用不着,有时间可以看)
https://www.bilibili.com/video/BV17W41137jn

=================================================================

第六阶段:框架学习 【Vue】:
https://www.bilibili.com/video/BV15741177Eh

第七阶段:UNI-APP【手机App、微信小程序开发、H5等混合式开发】(基于Vue,半天上手)
https://www.bilibili.com/video/BV1BJ411W7pX

第八阶段:
一 )、Typescript(TS,选看)
https://www.bilibili.com/video/BV1Xy4y1v7S2
二 )、新版Vue3(选看)
https://www.bilibili.com/video/BV1ra4y1H7ih
三)、Webpack
https://www.bilibili.com/video/BV1e7411j7T5

第九阶段:搜索引擎SEO优化
一 )、【全套SEO优化学习教程】 SEO基础:
https://www.bilibili.com/video/BV1fE411J7ya
https://www.bilibili.com/video/BV1m4411A79a
二 )、VUE的SSR服务端渲染(快速过一遍即可)
https://www.bilibili.com/video/BV1eE41187ny
三)、Nuxt框架(基于Vue)
https://www.bilibili.com/video/BV13Z4y1T74J

=================================================================

第十阶段:全栈开发路线(PHP、Node.js等)

一)、PHP:
黑马程序员28天PHP零基础入门到精通教程(P1基础6天)
https://www.bilibili.com/video/BV18x411H7qD
28天PHP零基础入门到精通教程(P2 mysql数据库5天)
https://www.bilibili.com/video/BV1Vx411g7uJ
28天PHP零基础入门到精通教程(P3 核心编程技术)
https://www.bilibili.com/video/BV1jx411M7B7

【李炎恢】【ThinkPHP6.x / PHP框架】【十天精品课堂系列】
https://www.bilibili.com/video/BV12E411y7u8

二)、Node.js:
https://www.bilibili.com/video/BV1ca4y1n7u3?from=search&seid=1042259761293661091