ES6 迭代协议

最后更新:
阅读次数:

ES6 中引入的迭代器和 Python 的迭代器类型非常类似,可互相参考进行学习。

迭代器贯穿 ES6 的始终,它是数据和循环的新标准。

迭代协议

  • 有两个迭代协议:可迭代协议和迭代器协议

    • 可迭代协议 允许 JavaScript 对象去定义或定制它们的迭代行为, 例如(定义)在一个 for..of 结构中什么值可以被循环(得到)
    • 迭代器协议 定义了一种标准的方式(调用 next() 方法)来产生一个有限或无限序列的值
  • 可迭代协议 —> 可迭代对象

  • 迭代器协议 —> 迭代器对象

可迭代对象

一种数据结构只要部署了 Iterator 接口,我们就称这种数据结构是可迭代的,即它是一种可迭代对象(iterable)。

ES6 规定,默认的 Iterator 接口部署在数据结构的 Symbol.iterator 属性上。

即:Array、TypedArray、String、Map、Set 是 JavaScript 内置的可迭代对象

Symbol.iterator 属性本身是一个函数,就是当前数据结构默认的迭代器生成函数。 执行这个函数,就会返回一个迭代器。

至于属性名 Symbol.iterator,它是一个表达式,返回 Symbol 对象的 iterator 属性,这是一个预定义好的、类型为 Symbol 的特殊值,所以要放在方括号内使用。

  • 通过重写 Symbol.iterator 方法来重新定义迭代行为
let arr = ["a", "b", "c"];

arr[Symbol.iterator] = function() {
return {
_first: true,
next: function() {
if (this._first) {
this._first = false;
return {
value: "only one",
done: false
};
} else {
return {
done: true
};
}
}
};
};

for (let x of arr) {
console.log(x);
}
// 只打印 'only one'
  • 一些语句和表达式是预料会用于可迭代对象,比如 for-of 循环,spread operator(扩展运算符), yield* 和 destructuring assignment(解构赋值)
for (let x of "abc") {
console.log(x);
}
// 打印 'a'、'b'、'c'
console.log([..."abc"]);
// ["a", "b", "c"]
function* gen() {
yield* ["a", "b", "c"];
}
let g = gen();
g.next(); // Object {value: "a", done: false}
g.next(); // Object {value: "b", done: false}
g.next(); // Object {value: "c", done: false}
g.next(); // Object {value: undefined, done: true}
let arr = ["a", "b", "c"];
let [x, y, z] = arr;
console.log(x); // a
console.log(y); // b
console.log(z); // c

for…of 语句

ES6 之前,我用常用 forEach 方法来遍历数组,但是数组的 forEach 函数有个缺陷:不能正确处理 continue(跳过本次循环)、break(中断循环)、return(返回到外层函数) 语句的逻辑

ES6 发布后,推出了新的 for...of 语句,用来遍历数组以及新的 Set、Map 数据结构,并且解决了上面 forEach 的缺陷。

  • for…of 循环可迭代的数据结构: 数组、 大部分类数组对象、字符串、Map、Set
    • 不支持直接迭代普通的对象,但可以迭代 Object.keys(obj)
for (let x of ["aa", 123, 4]) {
if (x === 123) {
continue;
}
console.log(x);
}

// aa
// 4
  • 支持 for…of 循环遍历的对象都直接或间接地部署了 Iterator 接口,即在 Symbol.iterator 属性上部署了迭代器生成方法(原型链上的对象具有该方法也可)

  • for-of 循环的内部原理: 首先调用集合的 Symbol.iterator 方法,紧接着返回一个新的迭代器对象,for-of 循环将重复调用这个迭代器对象的 next() 方法,每次循环调用一次,直到 next().donetrue 结束。

  • 并不是所有的类数组对象都具有 Iterator 接口

    • 一个解决办法是使用 Array.from(arrayLike) 方法将类数组对象转换为数组
    • 或者直接将类数组对象的 Iterator 接口部署为数组的 Iterator 接口
let arrayLike = {
0: "a",
1: "b",
length: 2
};
// 方法一

for (let x of Array.from(arrayLike)) {
console.log(x);
}
// 方法二

arrayLike[Symbol.iterator] = Array.prototype[Symbol.iterator];
// 或者 arrayLike[Symbol.iterator] = [][Symbol.iterator];
for (let x of arrayLike) {
console.log(x);
}

迭代器对象

迭代器对象是一种具有 .next() 方法的特殊的可迭代对象。

let arr = ["hello", "vue", "js"];

for (let x of arr) {
console.log(x);
}
// 分别打印 'hello'、'vue'、'js'

for (let x of arr[Symbol.iterator]()) {
console.log(x);
}
// 分别打印 'hello'、'vue'、'js'
  • 将一个可迭代对象转换为一个迭代器对象,直接调用可迭代对象的 [Symbol.iterator] 方法
let arr = ["hello", "vue", 1314, [1, 2, 3]];
let arrIterator = arr[Symbol.iterator]();

console.log(arrIterator.next());
// Object {value: "hello", done: false}

console.log(arrIterator.next());
// Object {value: "vue", done: false}

console.log(arrIterator.next());
// Object {value: 1314, done: false}

console.log(arrIterator.next());
// Object {value: Array(3), done: false}

console.log(arrIterator.next());
// Object {value: undefined, done: true}
  • Array、Set、Map 都部署了下面的 3 个方法,调用后返回一个迭代器对象
    • *.prototype.entries(): 返回一个迭代器对象,用来迭代 [键名, 键值] 组成的数组。对于数组,键名就是索引值;对于 Set,键名与键值相同。Map 结构的 Iterator 接口,默认就是调用 entries 方法。
    • *.prototype.keys(): 返回一个迭代器对象,用来迭代所有的键名。
    • *.prototype.values(): 返回一个迭代器对象,用来迭代所有的键值。

生成器(Generator

生成器是一种特殊的迭代器。所有的生成器都有内建 .next() 和 Symbol.iterator 方法的实现。

生成器函数在执行后会返回一个迭代器对象,即生成器。

  • 生成器函数 VS 普通函数
    • 普通函数使用 function 声明,而生成器函数使用 function* 声明
    • 在生成器函数内部,有一种类似 return 的语法:关键字 yield。二者的区别是,普通函数只可以 return 一次,而生成器函数可以 yield 多次(当然也可以只 yield 一次)。在生成器的执行过程中,遇到 yield 表达式立即暂停,后续可恢复执行状态。

普通函数和生成器函数之间最大的区别,普通函数不能自暂停,生成器函数可以。

function* generator() {
yield "hello";
yield "vue.js";
yield {
name: "ppp",
age: 22
};
}

let test = generator();

console.log(Object.prototype.toString.call(test)); // [object Generator]

console.log(test.next()); // Object {value: "hello", done: false}
console.log(test.next()); // Object {value: "vue.js", done: false}
console.log(test.next()); // Object {value: Object, done: false}
console.log(test.next()); // Object {value: undefined, done: true}
  • yield * 语句
function* generator() {
yield "hello";
yield* "js";
}

let test = generator();

console.log(test.next()); // Object {value: "hello", done: false}
console.log(test.next()); // Object {value: "j", done: false}
console.log(test.next()); // Object {value: "s", done: false}
console.log(test.next()); // Object {value: undefined, done: true}
  • 通过 next(value) 方法向 Generator 函数体内输入数据
function* gen(x) {
let y = yield x + 2;
let z = yield y + 2;
console.log(x, y, z);
return x + y + z;
}

let g1 = gen(1);
g1.next(); // Object {value: 3, done: false}
g1.next(); // Object {value: NaN, done: false}
g1.next(); // Object {value: NaN, done: true}
// 生成器函数内部变量 x, y, z ---> 1 undefined undefined

let g2 = gen(1);
g2.next(); // Object {value: 3, done: false}
g2.next(2); // Object {value: 4, done: false}
g2.next(3); // Object {value: 6, done: true}
// 生成器函数内部变量 x, y, z ---> 1 2 3

通过 next(value) 方法向生成器函数传入的参数将作为上次 yield 语句的返回值。(g2.next(2) 的参数 2 将作为 yield x + 2 语句的返回值,然后赋值给变量 y)

应用

待补充 ~

参考资料