导语
遍历器Iterator
是ES6为接见数据鸠合供应的一致接口。任何内部布置了遍历器接口的数据鸠合,关于用户来讲,都能够运用雷同体式格局猎取到响应的数据构造。假如运用的是最新版Chrome
浏览器,那末你要知道——我们所熟习的数组小姐,已偷偷的打开了另一扇可到达她心扉的小径。
1 正题
某个数据鸠合布置了Iterator
接口,是指其Symbol.iterator
属性指向一个能返回Iterator
接口的函数。任何默许运用遍历器接见数据鸠合的要领,都邑挪用此属性以获得遍历器对象,再按照设定的递次顺次接见该数据构造的成员(关于Symbol.iterator
请看末了一节的延长浏览)。比方原生数组的遍历器为[][Symbol.iterator]
,也能够直接经由过程其组织函数的原型猎取Array.prototype[Symbol.iterator]
。
1.1 基础行动
挪用Iterator
接口会返回一个新的遍历器对象(指针对象)。
对象中必定有next
要领,用于接见下一个数据成员。指针初始时指向当前数据构造的肇端位置。
第一次挪用对象的next
要领,指针指向数据构造的第一个成员。
第二次挪用对象的next
要领,指针指向数据构造的第二个成员。
不停的挪用对象的next
要领,直到它指向数据构造的完毕位置。
每次挪用next
要领,都邑返回雷同的数据构造:{ value, done }
。
个中value
示意当前指向成员的值,没有则为undefined
。
个中done
是一个布尔值,示意遍历是不是完毕,完毕为true
,不然false
。
遍历器接口的规范非常简约,不供应诸如:操纵内部指针、推断是不是有值等等要领。只需要一向不停的挪用next
要领,当done
为false
时猎取当时的value
,done
为true
时住手即可。第一次打仗遍历器的行动形式是在2016的冬季,当时秘闻不够鸡毛也没长全,明白不了简约性的实用和壮大。直到现在——在行将打包被迫脱离公司的前夜才蓦地的觉醒。何等痛的意会啊。
let iterator = [1, 2, 3][Symbol.iterator]();
console.log( iterator.next() ); // {value: 1, done: false}
console.log( iterator.next() ); // {value: 2, done: false}
console.log( iterator.next() ); // {value: 3, done: false}
console.log( iterator.next() ); // {value: undefined, done: true}
1.2 简朴完成
面向差别的数据构造,有差别的遍历器完成要领,我们简朴的完成下数组的遍历器要领。
let res = null;
let iterator = myIterator([3, 7]);
console.log( iterator.next() ); // {value: 3, done: false}
console.log( iterator.next() ); // {value: 7, done: false}
console.log( iterator.next() ); // {value: undefined, done: true}
function myIterator(array = []) {
let index = 0;
return {
next() {
return index < array.length
? { value: array[index++], done: false }
: { value: undefined, done: true };
}
};
}
1.3 return & throw
除了为遍历器对象布置next
要领,还能够有return
和throw
要领。个中return
要领会在提早退出for of
轮回时(平常是因为失足,或触发了break
语句)被挪用。而throw
要领主如果合营Generator
函数运用,平常的遍历器对象用不到这个要领,所以不予引见。
let obj = {
[Symbol.iterator]() {
let index = 0;
let array = [1, 2, 3];
return {
next() {
return index < array.length
? { value: array[index++], done: false }
: { value: undefined, done: true };
},
return() {
console.log('Trigger return.');
return {};
}
};
}
};
for (let v of obj) {
console.log(v); // 打印出:1, 2, 3,没触发 return 函数。
}
for (let v of obj) {
if (v === 2) break;
console.log(v); // 打印出:1,以后触发 return 函数。
}
for (let v of obj) {
if (v === 3) break;
console.log(v); // 打印出:1, 2,以后触发 return 函数。
}
for (let v of obj) {
if (v === 4) break;
console.log(v); // 打印出:1, 2, 3,没触发 return 函数。
}
for (let v of obj) {
if (v === 2) throw Error('error');
console.log(v); // 打印出:1,以后触发 return 函数,并报错住手实行。
}
2 原生支撑
2.1 默许持有遍历器
原生默许持有遍历器接口的数据构造有:
基础范例:Array
, Set
, Map
(四种基础数据鸠合:Array
, Object
, Set
和 Map
)。
类数组对象:arguments
, NodeList
, String
。
let iterator = '123'[Symbol.iterator]();
console.log( iterator.next() ); // {value: "1", done: false}
console.log( iterator.next() ); // {value: "2", done: false}
console.log( iterator.next() ); // {value: "3", done: false}
console.log( iterator.next() ); // {value: undefined, done: true}
遍历器与先前的遍历要领
一个数据鸠合具有遍历器接口,并不意味着一切遍历它的要领都是运用此接口。现实上,只要ES6新增的几种体式格局和某些要领会运用,下面会有引见。以数组来讲,对其运用for
和for of
虽然可接见到雷同的成员,然则现实的操纵体式格局却差别。
// 转变数组默许的遍历器接口。
Array.prototype[Symbol.iterator] = function () {
let index = 0;
let array = this;
console.log('Use iterator');
return {
next() {
return index < array.length
? { value: array[index++], done: false }
: { value: undefined, done: true };
}
}
};
let arr = [1, 2];
for (let v of arr) {
console.log(v); // 打印出 Use iterator, 1, 2。
}
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]); // 打印出 1, 2。
}
arr.forEach(d => {
console.log(d); // 打印出 1, 2。
});
对象没有默许的遍历器接口
为何对象没有默许的遍历器接口?这要从两方面申明。一为遍历器是种线性处置惩罚构造,关于任何非线性的数据构造,布置了遍历器接口,就等于布置一种线性转换。二是对象原本就是一个无序的鸠合,假如愿望其有序,能够运用Map
替代。这等于各有其长,各安其职。屎壳郎假如不滚粪球而去采蜜,那,呃,花mm能够就遭殃咯。
自行天生的类数组对象(具有length
属性),不具备遍历器接口。这与String
等原生类数组对象差别,毕竟人家是亲生的,一出生就含着金钥匙(也不怕误吞)。不过我们能够将数组的遍历器接口直接应用于自行天生的类数组对象,简朴有用无副作用。
let obj = {
0: 'a',
1: 'b',
length: 2,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
};
let iterator = obj[Symbol.iterator]();
console.log( iterator.next() ); // {value: "a", done: false}
console.log( iterator.next() ); // {value: "b", done: false}
console.log( iterator.next() ); // {value: undefined, done: true}
为对象增加遍历器接口,也不影响之前不运用遍历器的要领,比方for in
, Object.keys
等等(二者不同等)。
let obj = {
0: 'a',
1: 'b',
length: 2,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
};
console.log( Object.keys(obj) ); // ["0", "1", "length"]
for (let v of obj) {
console.log(v); // 顺次打印出:"a", "b"。
}
for (let k in obj) {
console.log(k); // 顺次打印出:"0", "1", "length"。
}
2.2 默许挪用遍历器
for of for of
是特地用来消耗遍历器的,其遍历的是键值(for in
遍历的是键名)。
for (let v of [1, 2, 3]) {
console.log(v); // 顺次打印出:1, 2, 3。
}
扩大运算符
无论是解构赋值或扩大运算都是默许挪用遍历器的。
let [...a] = [3, 2, 1]; // [3, 2, 1]
let b = [...[3, 2, 1]]; // [3, 2, 1]
yield*
在Generator
函数中有yield*
敕令,假如其背面跟的是一个可遍历的构造,它会挪用该构造的遍历器接口。
for (let v of G()) {
console.log(v); // 顺次打印出:1, 2, 3, 4, 5
}
function* G() {
yield 1;
yield* [2,3,4];
yield 5;
}
别的场所
有些接收数组作为参数的函数,会默许运用数组的遍历器接口,所以也同等于默许挪用。比方Array.from()
, Promise.all()
。
延长浏览
关于ES6的Symbol
:链接。