Iterator

概念

iterator遍历器,为了给js中多种的数据结构(原来的Array,String和ES6的Set,Map)加一个统一简单的调用,加上了一个Iterator接口,可以统一使用for...of来按一定顺序遍历数据结构。【嗯,还有扩展运算符...

ES6为以下一些结构实现了iterator接口

  • Array
  • String
  • Set
  • Map
  • arguments
  • NodeList

【注意没有Object,因为Object结构遍历的顺序是由开发者定义的,想要使用遍历器的语法,可以自行实现遍历器接口】

实现遍历器接口

  • 所有数据结构有属性名为Symbol.iterator,值为一个返回拥有next属性的对象(遍历器对象)的函数即可,next函数一般就返回一个拥有done和value两个属性对象,value的值就是遍历的值,done为布尔值表示能(false)否(true)继续遍历;next函数每被调用一次就往下一个遍历值移动

    【接口Symbol.iterator,其实是一个Symbol值,因而可以作为一个对象的属性名】

  • 示例代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    var obj = {
    a:'first',
    b:'second',
    c:'third',
    d:'fourth'
    }
    obj[Symbol.iterator]=function(){
    let ary = ['a','b','c','d']
    const self = this
    let index = 0

    // 返回遍历器对象
    return {
    // 遍历器对象的next属性
    next:function(){
    return index<ary.length?
    {value:self[ary[index++]],done:false} :
    {value:undefined,done:true}
    }
    }
    }
    for(let val of obj){
    console.log(val) // first second third fourth
    }

Generator

概念

Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。

  • Generator函数的写法是, function *generatorDemo(){ ... yield val1; ... yield val2; ... return val3}函数名前关键字function后加*号,函数体使用yield关键字

  • 它内部逻辑是,调用函数,然后返回一个iterator遍历器对象(对象,带属性next,next为函数),不执行函数代码;然后每次调用遍历器对象的next方法就会执行代码到定义时Generator函数体yield关键字后表达式,并返回yield后表达式的值作为next返回对象value属性的值;这样多次,直到最后的return。

  • 普通使用例子

    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
    function *generatorDemo(){
    console.log(1)
    yield 'hello'
    console.log(2)
    yield 'generator'
    console.log(3)
    return 'done'
    }
    var gd = generatorDemo()
    console.log('------------')
    console.log(gd.next())
    console.log('------------')
    console.log(gd.next())
    console.log('------------')
    console.log(gd.next())
    console.log('------------')
    console.log(gd.next())
    // ------------
    // 1
    // {value:hello,done:false}
    // ------------
    // 2
    // {value:generator,done:false}
    // ------------
    // 3
    // {value:done,done:false}
    // ------------
    // {value:undefined,done:true}

yield返回值

yield 也可以返回值,外部传值到Generator函数内部;比较值得注意的时传值的时机。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function *f(){
console.log(1)
var val = yield 66
console.log(val)
return val
}
var test = f()
console.log('------------')
// 并不是这里传的值,因为第一个词调用时,只是执行到var val=yield 66的右侧
console.log(test.next())
console.log('------------')
// 第二次执行时才是上一个yield完成的地方,所以在第二次传值进去,上一个yield结束后返回这次传的值。
console.log(test.next(2))
// ------------
// 1
// {value:66, done:false}
// ------------
// 2
// {value:2, done:false}

yield*

Generator嵌套,yield* 语法。因为当yied 后表达式也是一个Generator时可以使用yield*把后面的表达式的next给“暴露”出去调用;

【所有实现了Iterator接口的数据结构都可以被yield*解析处理(yield不行)】

【yield* 只能处理遍历器对象,单纯的数字这些会报错】

【yield* 可以接收返回值,当表达式是Generator函数调用并这个函数有return语句】

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
// 需求:for of遍历两个生成器函数A,B,期望按序先遍历了一个生成器A的再遍历另外一个B的

// ************无 yield* 语法,A时*********
function test1() {
function* A() {
yield 'a'
yield 'b'
}
function* B() {
yield A()
yield 'c'
yield 'd'
}
var g = B()
for (let item of g) {
console.log(item)
}
// 原本期望打印a b c d
// 但是事实上却打印了 f {<suspended>} c d
}

// ************无 yield* 语法时*********
// 需要自己手动写嵌套语法, 以下改写B
function test2() {
function* A() {
yield 'a'
yield 'b'
}
function* B() {
let g = A()
let itObj = g.next()
while (!itObj.done) {
yield itObj.value
itObj = g.next()
}
yield 'c'
yield 'd'
}
var g = B()
for (let item of g) {
console.log(item)
}
// 原本期望打印a b c d
// 事实上打印a b c d
}

// ************有 yield* 语法时*********
// 为了方便,ES6写了个语法糖,yield* 可实现上面的改写,再改写B
function test3() {
function* A() {
yield 'a'
yield 'b'
}
function* B() {
yield* A()
yield 'c'
yield 'd'
}
var g = B()
for (let item of g) {
console.log(item)
}
// 原本期望打印a b c d
// 事实上打印a b c d
}

test1()
console.log('------------')
test2()
console.log('------------')
test3()

应用

  • 延迟调用:从普通使用例子可以知道,不执行next,代码是不会被执行的,所以当没有使用yield时,只有return,那么只有调用一次next时函数才会被执行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function *delayRun(){
    console.log('run')
    return
    }
    var tmp = delayRun()
    console.log('------------')
    console.log(tmp.next())
    // ------------
    // run
    // {value:undefined,done:true}
  • 因为Generator返回的就是遍历器,而Iterator接口函数要的也是一个返回遍历器的函数,那么可以使用Generator函数来简化Iterator接口的编写,按照期望的顺序来写yield 表达式即可(妙啊)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    var obj = {
    a:'first',
    b:'second',
    c:'third',
    d:'fourth'
    }
    obj[Symbol.iterator] = function *(){
    yield this.a
    yield this.b
    yield this.c
    // 这里不能写return,for...of语法是不会识别到return的,因为return返回的done为true了,不会被for...of遍历
    yield this.d
    }
    for(let val of obj){
    console.log(val) // first second third fourth
    }

async/await

概念

直接看使用例子,不阻塞的情况下,使用同步代码实现异步调用的;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 正常promise调用
let p = new Promise(...)
p.then(p1).then(p2).then(p3)

// 可以改成以下这种方式, async返回的值是一个promise
// 若res是一个值,则类似返回一个promise.resolve(res)
// 若res是一个promise,则返回这个promise
async function fn(){
let p1 = await p()
let p2 = await p1()
let res = await p2()
return res
}

这里的重点是如何理解“同步代码实现异步调用”,async函数是异步的,每个await都会完成异步任务后才执行后面的代码。

async/await

和promise一样的时机调用,但语法上比promise阅读流畅很多。

原理

async/await可以说是Generator的一个语法糖,它的原理就是使用Generator实现的

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

// 按照async/await方式调用generator
function runAsAsync(gen) {
return new Promise((resolve, reject) => {
const generator = gen()

function runNext(data) {
// genertort取出iterator
const iterator = generator.next(data)
// console.log(iterator)

// iterator中value为结果promise
const p = iterator.value

// return时,done为true,直接返回值;没有return时,done为true,value为undefined,也直接返回
if (iterator.done) {
resolve(p)
return
}

// 完成时pormise的结果再传入generator内,作为下一次的值
p.then(
(data) => runNext(data),
(err) => reject(err)
)
}

runNext()
})
}

// 异步函数(返回promise)
function asyncFn(p) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(p)
resolve(p + ' result')
}, 500)
})
}

// generator 模拟await
function* gen() {
let res1 = yield asyncFn('p1')
console.log(res1)
let res2 = yield asyncFn('p2')
console.log(res2)
let res3 = yield asyncFn('p3')
console.log(res3)
let res4 = yield asyncFn('p4')
console.log(res4)
// return res4
}

const res = runAsAsync(gen)
res.then((v) => console.log(v))

参考

Iterator 和 for…of 循环 - ECMAScript 6入门

Generator 函数的语法 - ECMAScript 6入门

async 函数

MDN