引入
Iterator和for...of
ES6 规定,默认的
Iterator接口部署在数据结构的Symbol.iterator属性,或者说,一个数据结构只要具有Symbol.iterator属性,就可以认为是“可遍历的”(iterable)jslet 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 }原生具备 Iterator 接口的数据结构如下
- Map Set String Array TypedArray 函数的arguments对象 NodeList对象
- 本质上,遍历器是一种线性处理,对于任何非线性的数据结构,部署遍历器接口,就等于部署一种线性转换.所以对象没有默认部署 Iterator 接口
调用 Iterator 接口的场合
- 解构赋值
- 扩展运算符
- yield*
- 其他场合(for…of Array.from() Map(), Set(), WeakMap(), WeakSet()(比如new Map([['a',1],['b',2]]))) Promise.all() Promise.race() )
修改默认的iterator接口
jslet str = new String('4522688899') str[Symbol.iterator] = function () { return { index: 0, next: function () { return this.index < str.length ? { value: str[this.index++] + 1, done: false } : { done: true } }, }; }; console.log([...str]); //["41", "51", "21", "21", "61", "81", "81", "81", "91", "91"]
for...of与for...in
- for...in循环读取键名,for...of循环读取键值。如果要通过for...of循环,获取数组的索引,可以借助数组实例的entries方法和keys方法js
var arr = ['a', 'b', 'c', 'd']; let obj = { 'a': 111, 'b': 221, '0': 555, 1.1: 666, 2: 777, 'c': 331, } for (let key in obj) { console.log(key); // "0","2","a","b","1.1","c" } for (let a in arr) { console.log(a); // 0 1 2 3 } for (let a of arr) { console.log(a); // a b c d } - for...of循环调用遍历器接口,数组的遍历器接口只返回具有数字索引的属性。这一点跟for...in循环也不一样js
let arr = [3, 5, 7]; arr.foo = 'hello'; for (let i in arr) { console.log(i); // "0", "1", "2", "foo" } for (let i of arr) { console.log(i); // "3", "5", "7" } - Set 和 Map 结构也原生具有 Iterator 接口,可以直接使用for...of循环。Set 结构遍历时,返回的是一个值,而 Map 结构遍历时,返回的是一个数组,该数组的两个成员分别为当前 Map 成员的键名和键值。
计算生成的数据结构
entries()返回一个遍历器对象,用来遍历[键名, 键值]组成的数组。对于数组,键名就是索引值;对于 Set,键名与键值相同。Map 结构的 Iterator 接口,默认就是调用entries方法。keys()返回一个遍历器对象,用来遍历所有的键名。values()返回一个遍历器对象,用来遍历所有的键值。jslet arr = ['a', 'b', 'c']; arr.foo = 11 for (let pair of arr.entries()) { console.log(pair); } // [0, 'a'] // [1, 'b'] // [2, 'c']- 对于字符串来说,for...of循环还有一个特点,就是会正确识别 32 位 UTF-16 字符js
for (let x of 'a\uD83D\uDC0A') { console.log(x); } // 'a' // '🐊' - 对于普通的对象,for...of结构不能直接使用,会报错,必须部署了 Iterator 接口后才能使用.
- 一种解决方法是,使用Object.keys方法将对象的键名生成一个数组,然后遍历这个数组js
let someObject = { edition: 6, committee: "TC39", standard: "ECMA-262" }; for (var key of Object.keys(someObject)) { console.log(key + ': ' + someObject[key]); //edition: 6 } //committee: TC39 //standard: ECMA-262 - 另一个方法是使用 Generator 函数将对象重新包装一下。js
function* entries(obj) { for (let key of Object.keys(obj)) { yield [key, obj[key]]; } } for (let [key, value] of entries(obj)) { console.log(key, '->', value); } // a -> 1 // b -> 2 // c -> 3
与其他遍历语法的比较
- 以数组为例,
JavaScript提供多种遍历语法。最原始的写法就是for循环,写法比较麻烦. - 因此数组提供内置的
forEach方法,这种写法的问题在于,无法中途跳出forEach循环,break命令或return命令都不能奏效 for...in循环可以遍历数组的键名- 数组的键名是数字,但是
for...in循环是以字符串作为键名“0”、“1”、“2”等等 for...in循环不仅遍历数字键名,还会遍历手动添加的其他键,甚至包括原型链上的键- 某些情况下,
for...in循环会以任意顺序遍历键名 - 总之,
for...in循环主要是为遍历对象而设计的,不适用于遍历数组
- 数组的键名是数字,但是
for...of循环相比上面几种做法,有一些显著的优点- 有着同
for...in一样的简洁语法,但是没有for...in那些缺点 - 不同于
forEach方法,它可以与break、continue和return配合使用 - 提供了遍历所有数据结构的统一操作接口js
for (var n of fibonacci) { if (n > 1000) break; console.log(n); }
- 有着同