概念
1. for...of
for...of
是在 ECMA 2015(ECMA-262)
提出的草案,并在在 第六版
中正式成为标准。在已有 for
for...in
forEach
的情况下为什么还要引入一个新的迭代器?先来看看之前的迭代方案都有些什么特点。
for...in
:- 我们都知道,
for...in
遍历返回的是键名,数组的键名应该是数字,但是for...in
在遍历数组的时候却是返回的字符串;
var a = [1, 2, 3]; for (var k in a) { console.log(k); // "0", "1", "2" console.log(typeof k); // string }
for...in
不仅遍历数字键名,还会遍历手动添加的其它键,甚至包括原型链上的键;
var a = [1,2,3]; a["val"] = "key"; for (var k in a) { console.log(k); // "0", "1", "2", "val" // 神奇吧,还有更神奇的 } a.__proto__["proto_val"] = "proto_key"; // 给原型链赋值 for (var k in a) { console.log(k); // "0", "1", "2", "val", "proto_val" }
- 某些情况下,
for...in
循环会以任意顺序遍历键名;
var o = { length: 3, 2: 2, "0": 0, 1: 1, }; for (var k in o) { console.log(k); // "0", "1", "2", "length" } // 按照预想输出顺序应该是我声明时的顺序, // 但是如果在声明对象时如果键名存在数字或者字符串类型的数字(typeof 1 || typeof "1"), // 那么在该对象声明后便会自动先排个序 console.log(JSON.stringify(o)); // "{"0":0,"1":1,"2":2,"length":3}"
- 我们都知道,
forEach
:- 这种循环方式
break
命令无效,无法跳出循环,只能利用return
跳出当次循环;
var a = [1,2,3]; a.forEach(function(item) { if (item === 1) { return; // 只会跳出当次循环,类似continue } console.log(item); })
- 这种循环方式
for
:- 这种方式都知道的,最基础的循环,
for (var i = 0; i < 10; i++) { }
一眼过去就知道用这种方式循环还是比较麻烦的;
- 这种方式都知道的,最基础的循环,
for...of
:- 和
for...in
一样的简洁语法,但是没有对应的缺点; - 与
forEach
不同,break
continue
和return
可以配合使用; - 提供遍历所有数据结构统一的接口;
- 和
2. Symbol.iterator
在可被 for...of
迭代遍历的数据集合的原型链中都存在该属性为 key
的方法。
Symbol.iterator in Array.prototype; // true
Symbol.iterator in Set.prototype; // true
Symbol.iterator in Map.prototype; // true
// Generator 对象
function* generator() {
yield 1;
yield 2;
}
Symbol.iterator in generator(); // true
for (var i of generator()) {
console.log(i); // 1, 2
}
// 对象结构无法使用 for...of 遍历
Symbol.iterator in Object.prototype; // false
还有一些特殊的数据类型,类似数组对象
TypedArray
,比如:函数参数集合arguments
类似数组对象的定义:有 length 属性和若干索引属性对象。
并不是所有类似数组对象都存在 Iterator 接口,可以使用 Array.from() 将其转为数组。
/**
* 类似数组对象
*/
Symbol.iterator in String.prototype; // true
Symbol.iterator in NodeList.prototype; // true
/**
* arguments
*/
// ES 5
(function() {
return Symbol.iterator in arguments
})("a", "b", "c") // true
// ES 6
((...args) => Symbol.iterator in args )("a", "b", "c") // true
3. Symbol.iterator 有什么用
遍历器(iterator)属性是一种接口,为各种数据结构提供一个统一的访问机制。不管何种数据结构,只要部署了 Iterator 接口,就能完成遍历操作 。
作用:
- 为各种数据结构提供一个便捷的、统一的访问接口;
- 使得数据结构的成员
item
按照次序排列; - 主要提供给
for...of
消费
遍历过程:
- 创建一个指针对象,指向当前数据结构起始位置。遍历器对象本质上就是一个指针对象。
- 第一次调用指针对象的
next
方法,指针就指向第一个成员。 - 第二次调用
next
方法,指针就指向第二个成员。 - ......循环执行第三步,直到指针指向该数据结构结束位置。
next
方法返回的对象结构:
next() {
return {
value: item, // 当前下标指向的位置成员
done: Boolean, // 遍历是否结束,true 为结束
}
}
用代码说话,举个栗子:
let a = ['1', '2', '3'];
let iter = a[Symbol.iterator](); // 执行数组的 iterator 属性方法
iter.next(); // { value: '1', done: false }
iter.next(); // { value: '2', done: false }
iter.next(); // { value: '3', done: false }
iter.next(); // { value: undefined, done: true }
对类似数组对象可以直接引用数组的 Iterator 接口。
NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator]
var iter = document.querySelectorAll('div')[Symbol.iterator]() // 可以执行了
iter.next(); // 第一个 div
// 另外一种类似数组对象调用 Symbol.iterator 的例子
var iterable = {
0: 'a',
1: 'b',
2: 'c',
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (var item of iterable) {
console.log(item); // 'a', 'b', 'c'
}
4. 在什么情况下会调用 Symbol.iterator
接口
- 对数组或者
Set
数据类型解构赋值时会默认触发;var iterable = ['a', 'b', 'c']; iterable[Symbol.iterator] = function() { var self = this; var v = 0; console.log("触发了 Iterator"); //返回iterator return { next: function () { return v < self.length ? { value: self[v++] } : { done: true }; } }; }; var [a, b, c] = iterable; // 触发了 Iterator // a => 'a', b => 'b', c => 'c'
- 扩展运算符也会默认调用
Iterator
接口,可以利用该特性将部署了Iterator
接口的对象使用扩展运算符转为数组;var iterable = { [Symbol.iterator]: () => { var v = 0; console.log("触发了 Iterator"); //返回iterator return { next: function () { return { value: ++v, done: v > 3 }; } }; } } let arr = [...iterable]; // "触发了 Iterator" // arr => [1, 2, 3]
yield*
后面跟一个可遍历结构数据也会默认触发;- 所有接受数组为参数的方法都调用了遍历器接口
Array.from
for...of
Promise.all
Promise.race
Map
Set
WeakMap
WeakSet
5. 总结
Symbol.iterator
就是为了给 for...of
循环提供一个统一的遍历接口,for...of
能遍历所有提供了 Symbol.iterator
接口的数据集合;for...of
也是为了让遍历更优雅,提升代码的可读性,毕竟 for...of
的写法比起 for
表达式更简洁。