# ES6 难点

# Iterator 和 for...of 循环

# Iterator 遍历器概念

ES6有四种数据集合,Array,对象,Map,Set。

Iterator是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。

原生具备 Iterator 接口的数据结构如下。

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • 函数的 arguments 对象
  • NodeList 对象

Iterator 的作用有三个:

  • 一是为各种数据结构,提供一个统一的、简便的访问接口;
  • 二是使得数据结构的成员能够按某种次序排列;
  • 三是 ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费。

Iterator 的遍历过程

  1. 创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
  2. 第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
  3. 第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
  4. 不断调用指针对象的next方法,直到它指向数据结构的结束位置。

每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值(true结束,false未结束),表示遍历是否结束。 对于遍历器对象来说,done: false和value: undefined属性都是可以省略的

代码示例

function makeIterator(arr) {
    var nextIndex = 0
    return {
        next() {
            return nextIndex < arr.length
            ?  {value: arr[nextIndex++], done: false}
            : { value:undefined, done: true }
        }
    }
}

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


var it = makeIterator([1,3])

console.log(it.next());
console.log(it.next());
console.log(it.next());


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

# 默认 Iterator 接口

Iterator 接口的目的,就是为所有数据结构,提供了一种统一的访问机制,即for...of循环.当使用for...of循环遍历某种数据结构时,该循环会自动去寻找 Iterator 接口。

一种数据结构只要部署了 Iterator 接口,我们就称这种数据结构是“可遍历的”(iterable)。Symbol.iterator属性本身是一个函数,就是当前数据结构默认的遍历器生成函数。 执行这个函数,就会返回一个遍历器。至于属性名Symbol.iterator,它是一个表达式,返回Symbol对象的iterator属性,这是一个预定义好的、类型为 Symbol 的特殊值,所以要放在方括号内

var obj = {
    [Symbol.iterator]() {
        return {
            next() {
                return {
                    value:1,
                    done:true
                }
            }
        }
    }
}

console.log(obj[Symbol.iterator]().next());

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

上面代码中,对象obj是可遍历的(iterable),因为具有Symbol.iterator属性

ES6 的有些数据结构原生具备 Iterator 接口,凡是部署了Symbol.iterator属性的数据结构,就称为部署了遍历器接口。调用这个接口,就会返回一个遍历器对象。

下面的例子是数组的Symbol.iterator属性。

let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();

iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }
1
2
3
4
5
6
7

# 调用Iterator接口的场合

  • for ... of
  • 对数组和 Set 结构进行解构赋值时 let [first, ...rest] = new Set().add('a').add('b').add('c');
  • 扩展运算符 let arr = ['b', 'c'];let arr2 = ['a', ...arr, 'd']
  • yield* yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
let generator = function* () {
  yield 1;
  yield* [2,3,4];
  yield 5;
};

var iterator = generator();

iterator.next() // { value: 1, done: false }
iterator.next() // { value: 2, done: false }
iterator.next() // { value: 3, done: false }
iterator.next() // { value: 4, done: false }
iterator.next() // { value: 5, done: false }
iterator.next() // { value: undefined, done: true }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  • 其他场合
  • for...of
  • Array.from()
  • Map(), Set(), WeakMap(), WeakSet()(比如new Map([['a',1],['b',2]]))
  • Promise.all()
  • Promise.race()

# Iterator 接口与 Generator 函数

Symbol.iterator()方法的最简单实现,还是使用下一章要介绍的 Generator 函数。

var obj = {
   *[Symbol.iterator]() {
      yield {name:1}
      yield {age:100}
   }
}
for(const key of obj) {
   console.log('key :>> ', key);
   if key.name
}
1
2
3
4
5
6
7
8
9
10

# 遍历器对象的 return(),throw() (opens new window)

遍历器对象除了具有next()方法,还可以具有return()方法和throw()方法。如果你自己写遍历器对象生成函数,那么next()方法是必须部署的,return()方法和throw()方法是否部署是可选的。

return()方法的使用场合是,如果for...of循环提前退出(通常是因为出错,或者有break语句),就会调用return()方法。如果一个对象在完成遍历前,需要清理或释放资源,就可以部署return()方法。

var obj = {
   *[Symbol.iterator]() {
      yield {name:1}
      yield {age:100}
   }
}
for(const key of obj) {
   if( key.name){
       console.log('key :>> ', key);
       break;
   }
}
1
2
3
4
5
6
7
8
9
10
11
12

# for...of 循环

一个数据结构只要部署了Symbol.iterator属性,就被视为具有 iterator 接口,就可以用for...of循环遍历它的成员。也就是说,for...of【循环内部调用的是数据结构的Symbol.iterator方法】。

for...of的本质是一个while循环,所以上面的代码实质上执行的是下面的逻辑。

# Set 和 Map 结构

Set 和 Map 结构也原生具有 Iterator 接口,可以直接使用for...of循环。

const engines = new Set(["Gecko", "Trident", "Webkit", "Webkit"])
for(const eng of engines){
    console.log('eng :>> ', eng);
}

const mapEngines = new Map([['edition', 6],["committee", "TC39"],["standard", "ECMA-262"]])
for(const [key,value] of mapEngines){
    console.log(key,value)
}

1
2
3
4
5
6
7
8
9
10

上面代码演示了如何遍历 Set 结构和 Map 结构。值得注意的地方有两个,首先,遍历的顺序是按照各个成员被添加进数据结构的顺序。其次,Set 结构遍历时,返回的是一个值,而 Map 结构遍历时,返回的是一个数组,该数组的两个成员分别为当前 Map 成员的键名和键值。

# 对象遍历

使对象可以用for ... of遍历

var obj = {
    name:1,
    age: 2
}
// 方式一
obj[Symbol.iterator] = function* () {
    for(const key in this) {
        yield [key, this[key]]
    }
}

for (let [key, value] of obj) {
  console.log(key, '->', value);
}
// 方式二
function* objectEntries() {
  let propKeys = Object.keys(this);
  for (let propKey of propKeys) {
    yield [propKey, this[propKey]];
  }
}

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

jane[Symbol.iterator] = objectEntries;

for (let [key, value] of jane) {
  console.log(`${key}: ${value}`);
}
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

# Generator 函数的语法

Generator 函数是 ES6 提供的一种异步编程解决方案。

Generator 函数有多种理解角度。语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。

执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。

Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是遍历器对象. 必须调用遍历器对象的next方法,使得指针移向下一个状态

Generator 函数是一个普通函数,有两个特征。

  • 一是,function关键字与函数名之间有一个星号;
  • 二是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)
function* helloWorldGenerator() {
  console.log(1)
  yield 'hello';
  console.log(2)
  yield 'world';
  console.log(3)
  return 'ending';
}

var hw = helloWorldGenerator();
hw.next()
// { value: 'hello', done: false }

hw.next()
// { value: 'world', done: false }

hw.next()
// { value: 'ending', done: true }

hw.next()
// { value: undefined, done: true }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

总结一下,调用 Generator 函数,返回一个遍历器对象,代表 Generator 函数的内部指针。以后,每次调用遍历器对象的next方法,就会返回一个有着value和done两个属性的对象。value属性表示当前的内部状态的值,是yield表达式后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束。

# yield 表达式

由于 Generator 函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield表达式就是暂停标志。

遍历器对象的next方法的运行逻辑如下

  1. 遇到yield表达式,就暂停执行后面的操作,并将紧跟在【yield后面的那个表达式的值,作为返回的对象的value属性值】。
  2. 下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式
  3. 如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值
  4. 如果该函数没有return语句,则返回的对象的value属性值为undefined。

需要注意的是,yield表达式后面的表达式,只有当调用next方法、内部指针指向该语句时才会执行,因此等于为 JavaScript 提供了手动的“惰性求值”(Lazy Evaluation)的语法功能

function* gen() {
  yield  123 + 456;
}

var it = gen() // 此时yield 后面的 123 + 456还没执行

it.next() // yield 后面的 123 + 456才执行
1
2
3
4
5
6
7

# 与 Iterator 接口的关系

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

var obj = {}
obj[Symbol.iterator] = function*() {
    yield 1;
    yield 2;
    yield 3;
}
for(const key of obj) {
    console.log('key :>> ', key);
}
1
2
3
4
5
6
7
8
9

Generator 函数执行后,返回一个遍历器对象。该对象本身也具有Symbol.iterator属性,执行后返回自身。

# next 方法的参数

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

function* f() {
  for(var i = 0; true; i++) {
    console.log('start') 
    var reset = yield i;
    console.log('reset=>>',reset)
    if(reset) { i = -1; }
  }
}

var g = f();

g.next() // { value: 0, done: false }
第一步执行:执行console.log('start')
第二步执行:返回yield i;i此时为i = 0


g.next() // { value: 1, done: false }
第一步执行:console.log('reset=>>',reset)
第二步执行:i++
第三步执行:执行console.log('start')
第四步执行:返回yield i;i此时为i = 1

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

第一步执行:console.log('reset=>>',reset)
第二步执行:i = -1;
第三步执行:i++
第四步执行:执行console.log('start')
第五步执行:返回yield i;i此时为i = 0
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

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

由于next方法的参数表示上一个yield表达式的返回值,所以在第一次使用next方法时,传递参数是无效的。V8 引擎直接忽略第一次使用next方法时的参数,只有从第二次使用next方法开始,参数才是有效的。从语义上讲,第一个next方法用来启动遍历器对象,所以不用带有参数。

function* dataConsumer() {
  console.log('Started');
  console.log(`1. ${yield}`);
  console.log(`2. ${yield}`);
  return 'result';
}

let genObj = dataConsumer();
genObj.next();
// Started
genObj.next('a')
// 1. a
genObj.next('b')

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function* foo(x) {
    console.log('执行')
    var y = 2 * (yield (x + 10) + 10);
    console.log('y=>>>',y) // 200
    var z = yield (y / 5);
    console.log('z=>>>',z) // 10
    return (x + y + z); // 310
  }
  
  var a = foo(100);
  
  console.log(a.next())  // { value: 120, done: false }
  第一步:console.log('执行') 
  第二步:yield (100 + 10) + 10

  console.log(a.next(100)) // { value: 40, done: false } , 100作为 (yield (x + 10) + 10)的返回值,
  第一步:console.log('y=>>>',y) // 所以打印100
  第二步:yield (200 / 5);

  console.log(a.next(10)) // { value: 310, done: true }, 10 作为 yield (y / 5)
  第一步:console.log('z=>>>',z) 
  第二步:return (x + y + z);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# for...of 循环 Generator

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

function* foo() {
  yield 1;
  yield 2;
  yield 3;
  yield 4;
  yield 5;
  return 6;
}

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

for (let n of fibonacci()) {
  if (n > 1000) break;
  console.log(n);
}
1
2
3
4
5
6
7
8
9
10
11
12

# yield* 表达式

如果在 Generator 函数内部,调用另一个 Generator 函数。需要在前者的函数体内部,自己手动完成遍历。


function* foo() {
  yield 'a';
  yield 'b';
}

function* bar() {
  yield 'x';
  // 手动遍历 foo()
  for (let i of foo()) {
    console.log(i);
  }
  yield 'y';
}

for (let v of bar()){
  console.log(v);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function* inner() {
  yield 'hello!';
}

function* outer1() {
  yield 'open';
  yield inner();
  yield 'close';
}

var gen = outer1()
gen.next().value // "open"
gen.next().value // 返回一个遍历器对象
gen.next().value // "close"

function* outer2() {
  yield 'open'
  yield* inner()
  yield 'close'
}

var gen = outer2()
gen.next().value // "open"
gen.next().value // "hello!"
gen.next().value // "close"



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

上面例子中, outer2使用了yield*,outer1没使用。结果就是,outer1返回一个遍历器对象,outer2返回该遍历器对象的内部值。

# 作为对象属性的 Generator 函数

如果一个对象的属性是 Generator 函数,可以简写成下面的形式

let obj = {
  * myGeneratorMethod() {
    ···
  }
};
1
2
3
4
5

# Generator 函数的this

Generator 函数总是返回一个遍历器,ES6 规定这个遍历器是 Generator 函数的实例,也继承了 Generator 函数的prototype对象上的方法。

function* g() {}

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

let obj = g();

obj instanceof g // true
obj.hello() // 'hi!'
1
2
3
4
5
6
7
8
9
10

# 含义

Generator 是实现状态机的最佳结构。比如,下面的clock函数就是一个状态机。

下面的 Generator 实现与 ES5 实现对比,可以看到少了用来保存状态的外部变量ticking,这样就更简洁,更安全(状态不会被非法篡改)、更符合函数式编程的思想,在写法上也更优雅。Generator 之所以可以不用外部变量保存状态,是因为它本身就包含了一个状态信息,即目前是否处于暂停态。


var ticking = true;
var clock = function() {
  if (ticking)
    console.log('Tick!');
  else
    console.log('Tock!');
  ticking = !ticking;
}

// Generator 实现

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

# Generator 与上下文

JavaScript 代码运行时,会产生一个全局的上下文环境(context,又称运行环境),包含了当前所有的变量和对象。然后,执行函数(或块级代码)的时候,又会在当前上下文环境的上层,产生一个函数运行的上下文,变成当前(active)的上下文,由此形成一个上下文环境的堆栈(context stack)。

这个堆栈是“后进先出”的数据结构,最后产生的上下文环境首先执行完成,退出堆栈,然后再执行完成它下层的上下文,直至所有代码执行完成,堆栈清空。

Generator 函数不是这样,它执行产生的上下文环境,一旦遇到yield命令,就会暂时退出堆栈,但是并不消失,里面的所有变量和对象会冻结在当前状态。等到对它执行next命令时,这个上下文环境又会重新加入调用栈,冻结的变量和对象恢复执行。


function* gen() {
  yield 1;
  return 2;
}

let g = gen();

console.log(
  g.next().value,
  g.next().value,
);

1
2
3
4
5
6
7
8
9
10
11
12
13

上面代码中,第一次执行g.next()时,Generator 函数gen的上下文会加入堆栈,即开始运行gen内部的代码。等遇到yield 1时,gen上下文退出堆栈,内部状态冻结。第二次执行g.next()时,gen上下文重新加入堆栈,变成当前的上下文,重新恢复执行。

# 应用

# 异步操作的同步化表达

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

<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script>
    function* main() {
        var result = yield request("https://api.github.com/");
        console.log(result);
    }
    function request(url) {
        $.ajax(url,{
            success: function(res) {
                it.next(res)
            }
        })
    }

    var it = main();
    it.next();
</script>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 控制流管理

如果有一个多步操作非常耗时,采用回调函数,可能会写成下面这样。


step1(function (value1) {
  step2(value1, function(value2) {
    step3(value2, function(value3) {
      step4(value3, function(value4) {
        // Do something with value4
      });
    });
  });
});
1
2
3
4
5
6
7
8
9
10

采用 Promise 改写上面的代码。

Promise.resolve(step1)
  .then(step2)
  .then(step3)
  .then(step4)
  .then(function (value4) {
    // Do something with value4
  }, function (error) {
    // Handle any error from step1 through step4
  })
  .done();
1
2
3
4
5
6
7
8
9
10

上面代码已经把回调函数,改成了直线执行的形式,但是加入了大量 Promise 的语法。Generator 函数可以进一步改善代码运行流程。

注意:这种做法,只适合同步操作


function* longRunningTask(value1) {
  try {
    var value2 = yield step1(value1);
    var value3 = yield step2(value2);
    var value4 = yield step3(value3);
    var value5 = yield step4(value4);
    // Do something with value4
  } catch (e) {
    // Handle any error from step1 through step4
  }
}
1
2
3
4
5
6
7
8
9
10
11
12

然后,使用一个函数,按次序自动执行所有步骤。

scheduler(longRunningTask(initialValue));

function scheduler(task) {
  var taskObj = task.next(task.value);
  // 如果Generator函数未结束,就继续调用
  if (!taskObj.done) {
    task.value = taskObj.value
    scheduler(task);
  }
}
1
2
3
4
5
6
7
8
9
10

# 部署 Iterator 接口

利用 Generator 函数,可以在任意对象上部署 Iterator 接口。


function* iterEntries(obj) {
  let keys = Object.keys(obj);
  for (let i=0; i < keys.length; i++) {
    let key = keys[i];
    yield [key, obj[key]];
  }
}

let myObj = { foo: 3, bar: 7 };

for (let [key, value] of iterEntries(myObj)) {
  console.log(key, value);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 作为数据结构

# Generator 函数的异步应用

异步编程对 JavaScript 语言太重要。JavaScript 语言的执行环境是“单线程”的,如果没有异步编程,根本没法用,非卡死不可。本章主要介绍 Generator 函数如何完成异步操作。

ES6 诞生以前,异步编程的方法,大概有下面四种。

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

# Thunk 函数

编译器的“传名调用”实现,往往是将参数放到一个临时函数之中,再将这个临时函数传入函数体。这个临时函数就叫做 Thunk 函数。

function f(m) {
  return m * 2;
}

f(x + 5);

// 等同于

var thunk = function () {
  return x + 5;
};

function f(thunk) {
  return thunk() * 2;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

上面代码中,函数 f 的参数x + 5被一个函数替换了。凡是用到原参数的地方,对Thunk函数求值即可。

这就是 Thunk 函数的定义,它是“传名调用”的一种实现策略,用来替换某个表达式。

# JavaScript 语言的 Thunk 函数


// 正常版本的readFile(多参数版本)
fs.readFile(fileName, callback);

// Thunk版本的readFile(单参数版本)
var Thunk = function (fileName) {
  return function (callback) {
    return fs.readFile(fileName, callback);
  };
};

var readFileThunk = Thunk(fileName);
readFileThunk(callback);
1
2
3
4
5
6
7
8
9
10
11
12
13

上面代码中,fs模块的readFile方法是一个多参数函数,两个参数分别为文件名和回调函数。经过转换器处理,它变成了一个单参数函数,只接受回调函数作为参数。这个单参数版本,就叫做 Thunk 函数。

任何函数,只要参数有回调函数,就能写成 Thunk 函数的形式。下面是一个简单的 Thunk 函数转换器。

function Thunk(fn){
  return function (){
    var args = Array.prototype.slice.call(arguments);
    return function (callback){
      args.push(callback);
      return fn.apply(this, args);
    }
  };
};


function Thunk(fn){
  return function (...args){
    return function (callback){
      return fn.apply(this, [...args,callback]);
    }
  };
};

// 转换操作
var readFileThunk = Thunk(fs.readFile);
readFileThunk(fileA)(callback);

function f(a, cb) {
  cb(a);
}
const ft = Thunk(f);

ft(1)(console.log) // 1
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

TG大神写的thunkify (opens new window)函数

function thunkify(fn) {
  return function(...args) {
    const ctx = this
    return function(done) {
      let called
      args.push(function() {
        if(called) return
        called = true
        done.apply(null, arguments)
      })
      try {
        fn.apply(ctx,args)
      }catch(err) {
        done(err)
      }
    }
  }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

Tunckify函数使用方式

// 练习一
const fs = require('fs')
function read(err,data){
  console.log(data.toString())
}
const readFile = thunkify(fs.readFile)
readFile('test.txt')(read)


// 练习二
var fs = require('fs');
var thunkify = require('thunkify');
var readFileThunk = thunkify(fs.readFile);

var gen = function* (){
  var r1 = yield readFileThunk('/etc/fstab');
  console.log(r1.toString());
  var r2 = yield readFileThunk('/etc/shells');
  console.log(r2.toString());
};

var g = gen();

var r1 = g.next();
r1.value(function (err, data) {
  if (err) throw err;
  var r2 = g.next(data);
  r2.value(function (err, data) {
    if (err) throw err;
    g.next(data);
  });
});


// 练习三 ,自动执行thunk函数

function run(fn) {
  var gen = fn();

  function next(err, data) {
    var result = gen.next(data);
    if (result.done) return;
    result.value(next);
  }

  next();
}
var g = function* (){
  var f1 = yield readFileThunk('fileA');
  var f2 = yield readFileThunk('fileB');
  // ...
  var fn = yield readFileThunk('fileN');
};

run(g);

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

# Generator 函数的流程管理

Generator 函数可以自动执行。

function* gen() {
  // ...
}

var g = gen();
var res = g.next();

while(!res.done){
  console.log(res.value);
  res = g.next();
}
1
2
3
4
5
6
7
8
9
10
11

# Proxy (opens new window)

<script>
    var obj = [1, 2, 4, 5]
    // 负的index
    var proxy = new Proxy(obj, {
        get(target, key, reciever) {
            console.log('target', target);
            console.log('key :>> ', key);
            return target[target.length + key * 1]
        }
    })

    // console.log(proxy[-1]);
    // console.log(proxy[1]);
    // console.log(proxy1);
    

    // receiver 指向proxy2实例
    var  proxy2 = new Proxy({}, {
        get: function(target, key, receiver) {
            return receiver;
        }
    });

    // const d = Object.create(proxy2);
    // // d.a === d // true
    // console.log('d :>> ', d);

    var double = n => n * 2;
    var pow    = n => n * n;
    var reverseInt = n => n.toString().split("").reverse().join("") | 0;

    var pipe = function(value) {
        var funcStack = [];
        var proxy = new Proxy({}, {
            get(target,key,receiver) {
               if(key ===  'get') {
                   return funcStack.reduce((val, fn )=> {
                       return fn(val)
                   },value)
               } 
               funcStack.push(window[key])
               return proxy
            }
        })
        return proxy
    }
    console.log(pipe(3).double.pow.reverseInt.get);; 




</script>

<h1>Proxy的set配置</h1>

<p>接受4个参数 `target, key ,newValue, receiver`</p>


<h2>用set校验参数,age不能大于100,且是整数</h2>

<script>
var obj ={
    age: 10
}
var proxy = new Proxy(obj, {
    set(target, key, value, receiver) {
        if(!Number.isInteger(value)) {
            throw '请输入整数'
        }

        if(value > 100) {
            throw 'age 不能大于 100'
        }
        return Reflect.set(target, key, value, receiver)
    }

})
// proxy.age = 200
proxy.age = 20

</script>
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81

# Reflect

Reflect对象与Proxy对象一样,也是 ES6 为了操作对象而提供的新 API。Reflect对象的设计目的有这样几个。

  • 将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上
  • 修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false。
  • 让Object操作都变成函数行为。某些Object操作是命令式,比如name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)让它们变成了函数行为。
  • Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。

# async 滥用

三个函数相互依赖

DETAILS
function timeout(ms) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(ms)
    }, ms);
  });
}
async function asyncPrint(value, ms) {
  console.time('ok')
  const a = await timeout(1000);
  console.log(a)
  const b = await timeout(a + 1000);
  console.log(b)
  const c = await timeout(b+ 1000);
  console.log(c)
  console.log(value);
  console.timeEnd('ok')
}
  
asyncPrint('hello world');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

三个函数不依赖,但是要串行

DETAILS
function timeout(ms) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(ms)
    }, ms);
  });
}
async function asyncPrint(value, ms) {
  console.time('ok')
  await timeout(1000);
  await timeout(a + 1000);
  await timeout(b+ 1000);
  console.log(value);
  console.timeEnd('ok')
}
asyncPrint('hello world');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

三个函数不依赖,可以并行

DETAILS
function timeout(ms) {
    return new Promise((resolve) => {
      setTimeout(() => {
         resolve(ms)
     }, ms);
    });
  }
  
  async function asyncPrint(value, ms) {
    console.time('ok')
    const t1 = timeout(1000);
    const t2 = timeout(2000);
    const t3 = timeout(3000);
    const a = await t1
    console.log(a)
    const b = await t2
    console.log(b)
    const c = await t3
    console.log(c)
    console.log(value)
    console.timeEnd('ok')
  }
  
  asyncPrint('hello world');

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

forEach会让async函数并行

:::detais

function dbFuc(db) { 
  let docs = [{}, {}, {}];
  docs.forEach(async function (doc) {
    await db.post(doc);
  });
}
1
2
3
4
5
6

:::

for循环会让函数串行

DETAILS
async function dbFuc(db) {
  let docs = [{}, {}, {}];

  for (let doc of docs) {
    await db.post(doc);
  }
}
1
2
3
4
5
6
7

# Promise.resolve 和 new Promise

  • https://www.zhihu.com/question/268007969/answer/339811998
  • https://stackoverflow.com/questions/53894038/whats-the-difference-between-resolvethenable-and-resolvenon-thenable-object/53929252#53929252
  • https://segmentfault.com/q/1010000016147496
  • https://segmentfault.com/q/1010000016913023
  • https://github.com/xianshenglu/blog/issues/60