1. Interator(遍历器)
四种数据集合 Array Object Set Map
Interator 遍历器: 依次处理该数据的所有成员(指针对象) 一是为各种数据结构,提供一个统一的、简便的访问接口; 二是使得数据结构的成员能够按某种次序排列; 三是 ES6 创造了一种新的遍历命令for…of循环,Iterator 接口主要供for…of消费。
1
2
3
4
5
6
7
8
9
10
11
| Iterator 的遍历过程是这样的。
(1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
(2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
(3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
(4)不断调用指针对象的next方法,直到它指向数据结构的结束位置。
每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。
|
具有Symbol.iterator属性的对象都是可遍历的,调用这个属性,就会返回一个遍历器对象
1
2
3
4
5
6
7
| let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator](); // 调用 arr 的遍历器生成函数, 返回遍历器对象, 指向数组起始位置
iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }
|
原生具备 Iterator 接口的数据结构如下:Array/Map/SetString/TypedArray/函数的 arguments 对象/NodeList 对象,
一个对象如果要具备可被for…of循环调用的 Iterator 接口,就必须在Symbol.iterator的属性上部署遍历器生成方法(原型链上的对象具有该方法也可)
1
2
3
4
5
6
7
8
9
10
11
12
| const arr = ['red', 'green', 'blue'];
for(let v of arr) {
console.log(v); // red green blue
}
const arr = ['red', 'green', 'blue'];
arr.forEach(function (element, index) {
console.log(element); // red green blue
console.log(index); // 0 1 2
});
|
2. Generator定义
2-1. function关键字与函数名之间有一个星号;
2-2. 函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。
3. yield表达式和next()。
(1)遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。
(2)下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。
(3)如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。
(4)如果该函数没有return语句,则返回的对象的value属性值为undefined。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| function* foo(x) {
console.log("start");
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var a = foo(5); `生成一个遍历器,函数不会执行`
a.next() // "start" Object{value:6, done:false} `输出start, 执行到第一个yield, value = 第一个yield 后面表达式的值 = x+1 = 5`
a.next() // Object{value:NaN, done:false} `next()没有传值,上一个yield (x+1)的值为NaN, 所以y值为NaN, y/3为NaN`
a.next() // Object{value:NaN, done:true} `next()没有传值,上一个yield (y/3)的值为NaN, z为NaN, 5+NaN+NaN`
var b = foo(5);
b.next() // { value:6, done:false } `第一个next传值是无效的, next是给上一个yield表达式传值`
b.next(12) // { value:8, done:false } => `yield(x + 1) = 12; y = 2*12 = 24; yield value = > y/3 = 8;`
b.next(13) // { value:42, done:true } =>`yield(y / 3) = 13; z = 13; yield value => x+y+z = 5+24+13 = 42`
#### Object{value:6, done:false} value => yield后表达式的值,无则返回undefined; done => generator执行完返回done
|
4. yield*
用来在一个 Generator 函数里面执行另一个 Generator 函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| function* foo() {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
// 手动遍历 foo()
for (let i of foo()) {
console.log(i);
}
// 等于 yield* foo()
yield 'y';
}
for (let v of bar()){
console.log(v);
}
|
5.throw()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| var g = function* () {
try {
yield;
} catch (e) {
console.log('内部捕获', e);
}
};
var i = g(); // 返回一个遍历器对象
i.next(); // 执行到 yield暂停 返回 {value: NaN, done: false}
try {
i.throw('a'); // 把yield表达式替换成throw ("a"); generator函数catch到error, done: true
i.throw('b'); // done: true 就不再执行generator函数
} catch (e) {
console.log('外部捕获', e);
}
|
5-1.throw方法抛出的错误要被内部捕获,前提是必须至少执行过一次next方法。
因为第一次执行next方法,等同于启动执行 Generator 函数的内部代码,否则 Generator 函数还没有开始执行,这时throw方法抛错只可能抛出在函数外部
1
2
3
4
5
6
7
8
9
10
11
12
| function* gen() {
try {
yield 1;
} catch (e) {
console.log('内部捕获');
}
}
var g = gen();
g.throw(1);
// Uncaught 1
// g.throw(1)执行时,next方法一次都没有执行过。这时,抛出的错误不会被内部捕获,而是直接在外部抛出,导致程序出错
|
5-2.throw方法被捕获以后,会附带执行下一条yield表达式。也就是说,会附带执行一次next方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| var gen = function* gen(){
try {
yield console.log('a');
} catch (e) {
// ...
}
yield console.log('b');
yield console.log('c');
}
var g = gen();
g.next() // a
g.throw() // b
g.next() // c
`上面代码中,g.throw方法被捕获以后,自动执行了一次next方法,所以会打印b。
另外,也可以看到,只要 Generator 函数内部部署了try...catch代码块,
那么遍历器的throw方法抛出的错误,不影响下一次遍历`
|
6. return()
6-1. 调用return()后, 返回的对象{value: return的值/undefined, done: true};
1
2
3
4
5
6
7
8
9
10
| function* gen() {
yield 1;
yield 2;
yield 3;
}
var g = gen();
g.next() // { value: 1, done: false }
g.return() // { value: undefined, done: true }
|
6-2. 执行try代码块,那么return()方法会导致立刻进入finally代码块,执行完以后,整个函数才会结束
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| function* numbers () {
yield 1;
try {
yield 2;
yield 3;
} finally {
yield 4;
yield 5;
}
yield 6;
}
var g = numbers();
g.next() // { value: 1, done: false }
g.next() // { value: 2, done: false }
g.return(7) // { value: 4, done: false }
g.next() // { value: 5, done: false }
g.next() // { value: 7, done: true }
|
7. next()/throw()/return()的共同之处
7-1. next()是将yield表达式替换成一个值。
1
2
3
4
5
6
7
8
9
10
11
| const g = function* (x, y) {
let result = yield x + y;
return result;
};
const gen = g(1, 2);
gen.next(); // Object {value: 3, done: false}
gen.next(1); // Object {value: 1, done: true}
// 相当于将 let result = yield x + y
// 替换成 let result = 1;
|
7-2. throw()是将yield表达式替换成一个throw语句。
1
2
3
| gen.throw(new Error('出错了')); // Uncaught Error: 出错了
`// 相当于将 let result = yield x + y
// 替换成 let result = throw(new Error(`'出错了'));
|
7-3. return()是将yield表达式替换成一个return语句。
1
2
3
| gen.return(2); // Object {value: 2, done: true}
`// 相当于将 let result = yield x + y
// 替换成 let result = return 2;`
|
8. Iterator 和 Generator关系
任意一个对象的Symbol.iterator方法,等于该对象的遍历器生成函数,调用该函数会返回该对象的一个遍历器对象。 由于 Generator 函数就是遍历器生成函数,因此可以把 Generator 赋值给对象的Symbol.iterator属性,从而使得该对象具有 Iterator 接口。
对象的Symbol.iterator属性 -》 实现了Iterator 接口 即 遍历器生成函数 -》 返回遍历器对象
1.
1
2
3
4
5
6
7
8
9
10
| var myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
[...myIterable] // [1, 2, 3]
`上面代码中,Generator 函数赋值给Symbol.iterator属性,
从而使得myIterable对象具有了 Iterator 接口,可以被...运算符遍历了`
|
2.
1
2
3
4
5
6
7
8
9
| function* gen(){
// some code
}
var g = gen();
g[Symbol.iterator]() === g
`gen是一个 Generator 函数,调用它会生成一个遍历器对象g。
它的Symbol.iterator属性,也是一个遍历器对象生成函数,执行后返回它自己。`
|