ES6知识点整理

复习ES6笔记。

1、变量

1)let

  • 不能重复声明;
  • 支持块级作用域;
  • 没有变量提升。
1
2
3
4
5
6
    for (let i=0; I<3; I++) {
    setTimeout(()=>{
        console.log(i);
    }, 1000);
};
// 0 1 2

实现原理:

1
2
3
4
5
6
7
8
var _loop = function(i) {
    setTimeout(function(){
        console.log(i);
    }, 1000);
};
for (var i=0; i<3; i++) {
    _loop(i);
}

1
2
3
4
5
6
7
for (var i=0; i<3; i++) {
    (function(i) {
        setTimeout(function(){
            console.log(i);
        }, 1000);
    })(i);
}

2)const

  • 可以定义常量;
  • 重复赋值会报错,不同作用域可以重新声明或赋值;
  • 引用类型除外如:const obj = {name: ‘xx’}; obj.name = ‘yy’。
如何实现一个const
  • ES5没有块级的概念,我们只能大概模拟一下const的定义。
  • 我们将const 声明的变量绑定在全局对象上,借助 Object.defineProperty()劫持该对象,配置相关描述符实现const的特性。
  • 关键字和数字不能作为对象属性

图片 图片


2、解构

1
2
    let arr = [1,2,3];
    let [a,b,b] = arr;

编译成

1
2
    var arr = [1,2,3];
    var a = arr[0],b=arr[1],c=arr[2];

默认解构,如果能取出来值就用,取不出来就用默认值。 解构的原理:(https://juejin.cn/post/6844903764772519943)


3、模板字符串

实现方法:

1
2
3
4
5
6
7
8
9
    let name = 'name', age=9;
    let desc = `${name}|||||${age}`;
    function replaceStr(str) {
        return str.replace(/\$\{([^}]+)\}/g, function(matched, key){
            console.log(arguments);
            return eval(key);
        });
    }
    replaceStr(desc);

其特点:

1)可以折行

2)前面可以加函数,如:

1
2
3
4
    function desc () {
        console.log(arguments);
    }
    let str = desc`${name}|||||${age}`;

带标签的模版字符串,就像函数调用,参数就是文本拆出来的数组。

字符串其他新方法: startsWith endsWith Includes->indexOf === -1 repeat

indexOf的实现代码: 图片 includes的实现代码: 图片


4、扩展运算符:…rest

  • 只能作为最后一个参数;
  • 负责收集所有剩余数组。
  • 同arguments相似,但是区别在: 1)arguments对象是类数组,而rest是Array 的实例,可以直接调用其sort、map、forEach等方法; 2)rest只包含没有对应形参的实参,而arguments对象包含了传给函数的所有实参; 3)arguments对象还有一些附加属性,比如callee。

扩展运算符内部调用的是iterator接口,因此只要有iterator接口的对象,都可以使用扩展运算符。 任何iterator接口的对象,都可以用扩展运算符转为真正的数组。

1
2
3
    var nodeList = document.querySelectorAll(div);
    var array = [nodealist];
    // 原因在于nodelist对象实现了iterator接口,即遵循了iterator协议。

5、函数

1) 默认参数

指定了默认参数后,函数的length属性将失真,是以你为length属性的含义:该函数预期传入的参数个数。 某个参数指定默认值后,预期传入的参数个数就不包括这个参数了,同理,rest参数也不会计入length属性。

1
    (function(args){}).length     // 0

2) 展开操作符:

使用concat实现.

1
2
3
    Math.max([1,2,3]);
    // 编译成es5为如下代码
    Math.max.apply(null, [1,2,3]);

3)箭头函数

  • 函数体内的this对象,是定义时所在的对象,而不是使用时所在的对象。或者理解成箭头函数没有this。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
      function foo() {
          setTimeout(() => {
              console.log('id:', this.id);
          }, 100);
      }
      var id = 21;
      foo.call({ id: 42 });
      // id: 42
    
      function foo() {
          setTimeout(function() {
              console.log('id:', this.id);
          }, 100);
      }
      var id = 21;
      foo.call({ id: 42 });
      // id: 21
    

    由于箭头函数没有自己的this,所以当然也就不能用call()、apply()、bind()这些方法去改变this的指向。

  • 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
  • 没有arguments。
  • 没有yield命令,因此箭头函数不能作为generator函数。

4)尾调用优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    function f() {
        let m = 1;
        let n = 2;
        return g(m + n);
    }
    f();
    // 等同于
    function f() {
        return g(3);
    }
    f();

    // 等同于
    g(3);

主要作用是:防止爆栈(死循环那个报错)。

1
2
3
4
5
6
7
    function addOne(a){
        var one = 1;
        function inner(b){
            return b + one;
        }
        return inner(a);
    }

上面的函数不会进行尾调用优化,因为内层函数inner用到了外层函数addOne的内部变量one。

5)reduce实现

1
2
3
4
5
6
7
8
9
10
Array.prototype.reduce = function(reducer, initialVal) {
	for(let I=0; I<this.length; I++) {
		initialVal = reducer(initialVal, this[I]);
	}
	return initialVal;
};
let res = [1,2,3].reduce(function(val, item){
	return val + item;
}, 0);
console.log(res);

6、遍历器iterator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var it = makeIterator(['a', 'b']);

it.next() // { value: "a", done: false }
it.next() // { value: "b", done: false }
it.next() // { value: undefined, done: true }

function makeIterator(array) {
  var nextIndex = 0;
  return {
    next: function() {
      return nextIndex < array.length ?
        {value: array[nextIndex++]} :
        {done: true};
    }
  };
}

Iterator接口的目的,就是为所有数据结构,提供了一种统一的访问机制,即for…of循环。一种数据结构只要部署了Iterator接口,我们就称这种数据结构是”可遍历的“(iterable)。

1) 在ES6中,有三类数据结构原生具备Iterator接口:数组、某些类似数组的对象(nodeList、arguments、字符串)、Set和Map结构。

对象(Object)之所以没有默认部署Iterator接口,是因为对象的哪个属性先遍历,哪个属性后遍历是不确定的,需要开发者手动指定。

类似数组的对象(存在数值键名和length属性),部署Iterator接口,有一个简便方法,就是Symbol.iterator方法直接引用数组的Iterator接口。

1
2
3
4
5
NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
// 或者
NodeList.prototype[Symbol.iterator] = [][Symbol.iterator];

[...document.querySelectorAll('div')] // 可以执行了

2) 调用iterator接口的场合:

  • 解构赋值
  • 扩展运算符
  • yield*
  • for of
  • Array.form
  • Map、Set、WeakMap、WeakSet
  • Promise.all
  • Promise.race

3) iterator的最简单实现:

1
2
3
4
5
6
7
8
9
10
11
12
let obj = {
  * [Symbol.iterator]() {
    yield 'hello';
    yield 'world';
  }
};

for (let x of obj) {
  console.log(x);
}
// hello
// world

4) 并不是所有类似数组的对象都具有iterator接口,一个简便的解决方法,就是使用Array.from方法将其转为数组。

1
2
3
4
5
6
7
8
9
10
11
let arrayLike = { length: 2, 0: 'a', 1: 'b' };

// 报错
for (let x of arrayLike) {
  console.log(x);
}

// 正确
for (let x of Array.from(arrayLike)) {
  console.log(x);
}

7、generator

跟普通函数的区别: 有*、有yield关键字,执行函数后并不会返回值,而是要执行next,才会返回第一个yield语句后面跟随的表达式的值。

yield语句与return语句:

  • 相似之处在于,都能返回紧跟在语句后面的那个表达式的值。
  • 区别在于每次遇到yield,函数暂停执行,下一次再从该位置继续向后执行,而return语句不具备位置记忆的功能。一个函数里面,只能执行一次(或者说一个)return语句,但是可以执行多次(或者说多个)yield语句。正常函数只能返回一个值,因为只能执行一次return;Generator函数可以返回一系列的值,因为可以有任意多个yield。

yield语句不能用在普通函数中,会报错。

由于Generator函数就是遍历器生成函数,因此可以把Generator赋值给对象的Symbol.iterator属性,从而使得该对象具有Iterator接口。

1
2
3
4
5
6
7
8
var myIterable = {};
myIterable[Symbol.iterator] = function* () {
  yield 1;
  yield 2;
  yield 3;
};

[...myIterable] // [1, 2, 3]

再来看一个例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function* a(){
    console.log(23423423)
    var a= yield 1
    console.log(a)
    
    var b= yield 2
    console.log(b)
    var c= yield 3
    console.log(c)
}
var m = a();

m.next();

从上述代码可以看出:每执行一次next,代码就会按顺序找相应的yeild,执行两个yeild之间的代码,并返回当前yeild语句后面的表达式的值。也就是说,next和yield是一一对应的。

yield句本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield语句的返回值。

1
2
3
4
5
6
7
8
9
10
11
12
function* f() {
  for(var i=0; true; i++) {
    var reset = yield i;
    if(reset) { i = -1; }
  }
}

var g = f();

g.next() // { value: 0, done: false }
g.next() // { value: 1, done: false }
g.next(true) // { value: 0, done: false }

这个功能有很重要的语法意义。Generator函数从暂停状态到恢复运行,它的上下文状态(context)是不变的。通过next方法的参数,就有办法在Generator函数开始运行之后,继续向函数体内部注入值。也就是说,可以在Generator函数运行的不同阶段,从外部向内部注入不同的值,从而调整函数行为。

for…of循环可以自动遍历Generator函数时生成的Iterator对象,且此时不再需要调用next方法。

下面是一个利用Generator函数和for…of循环,实现斐波那契数列的例子:

1
2
3
4
5
6
7
8
9
10
11
12
function* fibonacci() {
  let [prev, curr] = [0, 1];
  for (;;) {
    [prev, curr] = [curr, prev + curr];
    yield curr;
  }
}

for (let n of fibonacci()) {
  if (n > 1000) break;
  console.log(n);
}

利用for…of循环,可以写出遍历任意对象(object)的方法。原生的JavaScript对象没有遍历接口,无法使用for…of循环,通过Generator函数为它加上这个接口,就可以用了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function* objectEntries(obj) {
  let propKeys = Reflect.ownKeys(obj);

  for (let propKey of propKeys) {
    yield [propKey, obj[propKey]];
  }
}

let jane = { first: 'Jane', last: 'Doe' };

for (let [key, value] of objectEntries(jane)) {
  console.log(`${key}: ${value}`);
}
// first: Jane
// last: Doe

上述代码通过generator和for of结合方式,为对象数组增加了遍历器接口。

generator嵌套,利用yield*实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
function* foo() {
  yield 'a';
  yield 'b';
}
function* bar() {
  yield 'x';
  yield* foo();
  yield 'y';
}
// 等同于
function* bar() {
  yield 'x';
  yield 'a';
  yield 'b';
  yield 'y';
}

// 等同于
function* bar() {
  yield 'x';
  for (let v of foo()) {
    yield v;
  }
  yield 'y';
}

for (let v of bar()){
  console.log(v);
}
// "x"
// "a"
// "b"
// "y"

从语法角度看,如果yield命令后面跟的是一个遍历器对象,需要在yield命令后面加上星号,表明它返回的是一个遍历器对象。这被称为yield*语句。

实际上,任何数据结构只要有Iterator接口,就可以被yield*遍历。

1
2
3
4
5
6
7
let read = (function* () {
  yield 'hello';
  yield* 'hello';
})();

read.next().value // "hello"
read.next().value // "h"

yield*命令可以很方便地取出嵌套数组的所有成员。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function* iterTree(tree) {
  if (Array.isArray(tree)) {
    for(let i=0; i < tree.length; i++) {
      yield* iterTree(tree[i]);
    }
  } else {
    yield tree;
  }
}

const tree = [ 'a', ['b', 'c'], ['d', 'e'] ];

for(let x of iterTree(tree)) {
  console.log(x);
}
// a
// b
// c
// d
// e

generator函数的this。

1
2
3
4
5
6
function* g() {
  this.a = 11;
}

let obj = g();
obj.a // undefined

上面代码中,Generator函数g在this对象上面添加了一个属性a,但是obj对象拿不到这个属性。

1
2
3
4
5
6
7
8
9
10
function* g() {}

g.prototype.hello = function () {
  return 'hi!';
};

let obj = g();

obj instanceof g // true
obj.hello() // 'hi!'

上面代码表明,Generator函数g返回的遍历器obj,是g的实例,而且继承了g.prototype。但是,如果把g当作普通的构造函数,并不会生效,因为g返回的总是遍历器对象,而不是this对象。

generator函数不能跟new一起用,会报错。

generator状态机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var clock = function*() {
  while (true) {
    console.log('Tick!');
    yield;
    console.log('Tock!');
    yield;
  }
};
var ccc = clock();
ccc.next();
ccc.next();
ccc.next();
ccc.next();
// Tick!
// {value: undefined, done: false}
// Tock!
// {value: undefined, done: false}
// Tick!
// {value: undefined, done: false}
// Tock!
// {value: undefined, done: false}

Generator函数的暂停执行的效果,意味着可以把异步操作写在yield语句里面,等到调用next方法时再往后执行。这实际上等同于不需要写回调函数了,因为异步操作的后续操作可以放在yield语句下面,反正要等到调用next方法时再执行。所以,Generator函数的一个重要实际意义就是用来处理异步操作,改写回调函数。

Ajax是典型的异步操作,通过Generator函数部署Ajax操作,可以用同步的方式表达。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function* main() {
  var result = yield request("http://some.url");
  var resp = JSON.parse(result);
    console.log(resp.value);
}

function request(url) {
  // 这里的makeAjaxCall想象成fetch
  makeAjaxCall(url, function(response){
	// 第二次执行yield response作为next的参数传给generator,根据generator特性,request(‘’)表达式的返回值就是response,因此将写法由异步转换成同步
	// 通过makeAjaxCall自动调用it.next方法,自动完成同步请求这一过程
    it.next(response);
  });
}

var it = main();
it.next(); // 这里先执行一次yield 通过request函数发起请求

8、Promise

ES6 规定,Promise对象是一个构造函数,用来生成Promise实例。 用来解决多个回调嵌套和多个错误处理。

  • 1、实现了回调函数的延迟绑定。
  • 2、将回调函数onResolve的返回值穿透到最外层。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const promise = new Promise(function(resolve, reject) {
  // ... some code
  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

promise.then(function(value) {
  // success
  // 如果promise里面的resolve没有执行,那么这里的then的回调一直不会执行,原因是要通过resolve执行后确定当前实例的状态,必须要从pending变成resolved或者rejected才行
  // 且value值和resolve的参数值一致
}, function(error) {
  // failure
});

再看一个经典的一步接口请求案例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const getJSON = function(url) {
  const promise = new Promise(function(resolve, reject){
    const handler = function() {
      if (this.readyState !== 4) {
        return;
      }
      if (this.status === 200) {
        resolve(this.response);
      } else {
        reject(new Error(this.statusText));
      }
    };
    const client = new XMLHttpRequest();
    client.open("GET", url);
    client.onreadystatechange = handler;
    client.responseType = "json";
    client.setRequestHeader("Accept", "application/json");
    client.send();

  });

  return promise;
};

getJSON("/posts.json").then(function(json) {
  console.log('Contents: ' + json);
}, function(error) {
  console.error('出错了', error);
});

上述代码中,Promise在新建的时候,会立即执行,比如console.log数据,但是resolve或者reject是在handler被调用时才去执行的,并且在resolve或reject执行完后,后面的代码也会执行。在resolve或reject执行后实例有了确定的状态后再去执行then方法。

1
2
3
4
5
getJSON("/posts.json").then(function(json) {
  return json.post;
}).then(function(post) {
  // ...
});

这里第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。

这里前一个回调函数,有可能返回的还是一个Promise对象(即有异步操作),这时后一个回调函数,就会等待该Promise对象的状态发生变化,才会被调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var p1 = new Promise((resolve, reject) => {
  // 单纯模拟一个异步操作
  setTimeout(() => {
    resolve(123);
  }, 100);
});

var p2 = new Promise((resolve, reject) => {
  // resolve方法既能接收值作为参数,也能接收Promise对象作为参数。
  // 如果接收p1这个实例,p2的resolve就会自动从p1中取执行resolve时获取的参数,作为参数传给p2的then的完成态的方法
  // 这里就相当于把p1和p2的promise操作变成“链式调用”串起来了
  resolve(p1);
  // 这里相当于是
  /**
    p1.then(val)=>{
      resolve(val) 
    })
   */
});

在这还需要注意的是,p1的状态决定p2的状态。

catch方法 Promise.prototype.catch()方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。

reject()方法的作用,等同于抛出错误。 如果 Promise 状态已经变成resolved,再抛出错误是无效的。因为Promise的状态一经更改,就不会再改变了。

Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。(代码如下)

1
2
3
4
5
6
7
getJSON('/post/1.json').then(function(post) {
  return getJSON(post.commentURL);
}).then(function(comments) {
  // some code
}).catch(function(error) {
  // 处理前面三个Promise产生的错误
});

简单实现一个Promise构造函数。

主要根据一下几点来作为实现原理:

  1. 有一个初始状态pending;
  2. 构造函数接收一个function作为参数,该function包含resolve喝reject两个函数作为参数;
    • resolve执行之后,状态改成resolved;
    • reject执行之后,状态改成rejected。
  3. 实例继承了then和catch方法;
  4. 每个then执行之后,结果作为下一个then方法的回调参数,并且会隐式返回一个新的promise实例;
  5. catch捕获错误,并且只执行一次。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// 构造函数体
class PromisePoly {
  data;// 存储resolve参数 好给then的回调参数赋值,因为二者相同
  err;// 存储reject参数 同上
  constructor(fn) {
    this.status = 'pending';
    // 这里面借用了bind只能生效一次的特性,防止实例化的时候本该指向实例的this被篡改
    fn(this.resolve.bind(this), this.reject.bind(this));
  }
  resolve(data) {
    if (this.status === 'pending') {
      this.status = 'resolved';
      this.data = data;// 如果不传,就是undefined 这里要保存一下,方便实例获取该值
    }
  }
  reject(err) {
    if (this.status === 'pending') {
      this.status = 'rejected';
      this.err = err;// 如果不传,就是undefined 这里要保存一下,方便实例获取该值
    }
  }
}
PromisePoly.prototype.then = function(successCb, failCb) {
  let successRes, failRes;
  // 在这里 实例已经有了一个最终状态,要么是resolved 要么是rejected
  //如果是resolved,执行对应的成功回调、否则执行失败回调并记录好返回值,作为下一次then或者catch的回调参数
  if (this.status === 'resolved') {
    successRes = successCb(this.data);
  }
  if (this.status === 'rejected') {
    failRes = failCb(this.err);
  }
  const _status = this.status
  // 每一个then都会隐式返回一个新的promise实例,并且会获取到上一次then执行的返回结果作为这个新的实例then方法的回调参数
  return new PromisePoly(function(resolve, reject){
    console.log(successRes,_status)
    if (_status === 'resolved') {
      resolve(successRes);// 这里执行之后 会给自己的实例的私有属性data赋值
    } else {
      reject(failRes);// 这里执行之后 会给自己的实例的私有属性err赋值
    }
  });
};

PromisePoly.prototype.catch = function(failCb) {
  if (this.hasRun) {
    new Error('不能调用多次catch!');
  }
  let failRes = failCb(this.err);
  this.hasRun = true;
};

增加捕获错误和传递Promise实例为参数的后代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
class PromisePoly {
    status = 'pending';
    constructor(executor) {
        try {
            // ???为啥要用bind
            executor(this.resolve.bind(this), this.reject.bind(this)) 
        } catch (e) {
            this.reject(e)
        }
    }
    resolve(data) {
        if (this.status === 'pending') {
            console.log('resolved')
            this.data = data;
            this.status = 'resolved'
        }
    }

    reject(error) {
        if (this.status === 'pending') {
            console.log('resolved')
            this.data = error;
            this.status = 'rejected'
        }
    }
}
PromisePoly.prototype.then = function(resolved, rejected) {
    console.log('then');
    //返回新的Promise对象 ??? 这里看不太懂 promise.then到底返回了个啥玩意
    if (this.status === 'resolved') {
        return new PromisePoly((resolve, reject) => {
            try {
                var result = resolved(this.data)
                if (result instanceof PromisePoly) { // 如果resolved的返回值是一个Promise对象,直接取它的结果做为新promise的结果
                    result.then(resolve, reject)
                } else {
                    resolve(result) // 否则,以它的返回值做为新promise的结果
                }
            } catch (e) {
                reject(e) // 如果出错,以捕获到的错误做为新promise的结果
            }
        })
    }

    if (this.status === 'rejected') {
        return new PromisePoly((resolve, reject) => {
            try {
                var result = rejected(this.data)
                if (result instanceof PromisePoly) { // 如果rejected的返回值是一个Promise对象,直接取它的结果做为新promise的结果
                    result.then(resolve, reject)
                }
            } catch (e) {
                reject(e) // 如果出错,以捕获到的错误做为新promise的结果
            }
        })
    }
}

但以上代码也只是实现了promise的同步逻辑,没有实现异步。

异步的实现可参考:Promise实现原理(附源码)

Promise.all实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function all(promises) {
  let len = promises.length, res = [];
  // 修正
  let count = len
  if (len) {
    return new Promise(function (resolve, reject) {
        for(let i=0; i<len; i++) {
          let promise = promises[i];
          promise.then(response => {
            res[i] = response;

            // 当返回结果为最后一个时
            // 修正
            // if (res.length === len) {
            if (!--count) {
              resolve(res)
            }
          }, error => {
            reject(error)
          })
        }
    })
  }
}

9、异步和async

在ES6诞生以前,我们用得最多的异步就是以下几种:

  • 回调函数
  • 事件监听
  • 发布/订阅
  • Promise对象

回调函数存在的弊端是:多个回调函数嵌套,使得代码横向发展,形成回调地狱,无法管理和维护。Promise就是为了解决这个问题而被踢出来的。它是一种新的写法,允许将多个回调函数嵌套改写成链式调用。

1
2
3
4
5
6
7
8
9
10
11
12
var readFile = require('fs-readfile-promise');

readFile(fileA)
.then(function(data){
  console.log(data.toString());
})
.then(function(){
  return readFile(fileB);
})
.catch(function(err) {
  console.log(err);
});

Promise的then方法提供回调,catch捕捉执行过程中的错误。但是Promise也只是一种改进写法,它最大的问题事代码冗余,不管什么操作,一眼看过去都是then,语义并不清晰。于是便有了generator。

generator函数最大的特点就是可以叫出函数的执行权(即由yield实现的暂停执行)。

1)async

async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。

async函数的返回值是 Promise 对象。你可以用then方法指定下一步的操作。

进一步说,async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖。

看一下两段代码:

1
2
3
4
5
6
7
8
9
async function foo() {
    console.log(1)
    let a = await 100
    console.log(a)
    console.log(2)
     let b = await 300
    console.log(b)
    console.log(4)
}

等同于

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function foo() {
  console.log(1)
  new Promise((resolve)=>{
    resolve(100);
   }).then((a)=>{
    console.log(a)
    console.log(2)
    return new Promise((rel)=>{
      rel(300)
      }).then((b)=>{
        console.log(b)
        console.log(4)
      });
    });
}

也就是说,await后的代码都放在一整个promise。then里,并且出现多个await,就代表多个promise嵌套。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
async function foo() {
    console.log('foo')
}
async function bar() {
    console.log('bar start')
    await foo()
    console.log(100)
    console.log('bar end')
}
console.log('script start')
setTimeout(function () {
    console.log('setTimeout')
}, 0)
bar();
new Promise(function (resolve) {
    console.log('promise executor')
    resolve();
}).then(function () {
    console.log('promise then')
})
console.log('script end')

以上代码的输出是:

1
2
3
4
5
6
7
8
9
  'script start'
  'bar start'
  'foo'
  'promise executor'
  'script end'
  '100'
  'bar end'
  'promise then'
  'setTimeout'

10、bind实现

1)先来看call的实现

1
2
3
4
5
  const b = {name: 'hhh'};
  function test(){
    console.log(this.name);
  };
  test.call(b); // 输出hhh 其实就相当于执行了b.test();

具体实现:

1
2
3
4
5
6
7
  function call1(context){ // context相当于b
    const fn = this; // this相当于上面的test.call中的test
    context['someFn'] = fn; //最重要能调用b.test 因此要把test赋值给b
    const res = context['someFn'](); // 执行 获取其结果然后返回
    delete context['someFn'];
    return res;
  }

完整版:

1
2
3
4
5
6
7
8
9
10
11
function call1(context){
  var context = context || window; // 兼容context是null的情况
  context.fn = this;
  var args = [];// 处理call接收的参数
  for(var i = 1, len = arguments.length; i < len; i++) {// 从1开始
    args.push('arguments[' + i + ']');
  }
  var result = eval('context.fn(' + args +')');
  delete context.fn;
  return result;
}

2)bind的例子

1
2
3
4
  const func = () => {
    console.log(this); //this指向global对象
  };
  const a = test.bind({}); // 相当于执行 test.call({}) 主要记忆点是它包含call\applay的功能 并且会返回一个新的function

具体实现:

1
2
3
4
5
6
function _bind(func, _this_, ...args) {
	return function (...restArgs) { // bind后产生的新function是可以接收参数的 并且会被排在bind的参数后面
		func.call(_this_, [...args, ...restArgs]);
    // call1(func, _this_);
	};
}