前端开发系列125-进阶篇之Iterator
Iterator 是一种接口,为各种不同的数据结构提供统一的访问机制,任何数据结构只要部署实现了 Iterator 接口,就可以完成遍历操作。
Iterator 的优点在于能够为不同的数据结构提供了统一的接口;能够以特定的排序来遍历数据结构;提供创造了for...of循环
。JavaScript中默认实现迭代器接口( Iterator )的数据结构有类数组结构(NodeList、arguments、String
等) 和 Set 、Map、Array
等 ,实现 Iterator 接口的数据结构均支持使用 for...of
循环来执行遍历操作。
下面通过代码简单展示Set 、Map、Array
三种数据结构中实现的原生迭代器接口( Iterator )和for...of遍历
。
/* 1.数组 Array */
/* 2.集合 Set */
/* 3.映射 Map */
/* 4.其它结构 */
let arr = [100, 200, 300];
console.log("arr", arr);
let set = new Set([10, 20, 30, 20, "测试"]);
console.log("set", set);
let map = new Map();
map.set("a", "A");
map.set("b", "B");
map.set("c", "C");
console.log("map", map);
/* 测试数据 */
for (let ele of arr) {
console.log(ele);
}
console.log('______________');
for (let ele of set) {
console.log(ele);
}
console.log('______________');
for (let [key,val] of map) {
console.log(key,val);
}
console.log('______________');
/* 打印输出 */
/*
arr [ 100, 200, 300 ]
set Set { 10, 20, 30, '测试' }
map Map { 'a' => 'A', 'b' => 'B', 'c' => 'C' }
100
200
300
______________
10
20
30
测试
______________
a A
b B
c C
______________ */
通过查看console.log(Array.property,Set.property,Map.property);
打印结果,你会发现在数组、集合和映射它们的内部,都在其构造函数的原型对象上无一例外都实现了Symbol(Symbol.iterator): ƒ entries()函数
,调用该函数我们能够得到一个iterator 型对象
,当我们使用for...of
循环结构来遍历它们的时候,在内部会利用该对象来完成遍历操作。
let arr = [100, 200, 300];
/* arr.__proto__ === Array.prototype[Symbol.iterator] */
let iterator = arr[Symbol.iterator]();
console.log(iterator); /* Object [Array Iterator] {} */
console.log("_________bgn_________")
let o = iterator.next();
while (!o.done) {
o = iterator.next()
console.log(o);
}
console.log("_________end_________")
/* 打印输出 */
/*
Object [Array Iterator] {}
_________bgn_________
{ value: 200, done: false }
{ value: 300, done: false }
{ value: undefined, done: true }
_________end_________
*/
通过Array.prototype[Symbol.iterator]()
可以得到一个iterator 型对象
,调用该对象的next方法
后能得到个拥有两个键值对的对象,其中value
表示的是当前的值,而 done
可以理解为是循环是否结束。在上面的代码中,我通过一个 while 循环来模拟了for..of
循环过程。此外,也可以简单对比下这些结构中的entries()、keys() 和 values()
等函数的用法。
let arr = [100, 200, 300];
let iterator = arr.entries();
console.log("_________bgn_________")
let o = iterator.next();
while (!o.done) {
o = iterator.next()
console.log(o);
}
console.log("_________end_________")
console.log(arr.keys())
console.log(arr.values())
/* 测试输出 */
/*
_________bgn_________
{ value: [ 1, 200 ], done: false }
{ value: [ 2, 300 ], done: false }
{ value: undefined, done: true }
_________end_________
Object [Array Iterator] {}
Object [Array Iterator] {} */
在 ES6中的数组、Set和 Map 中都部署了entries() 、keys()、values()
三个方法,它们调用后都返回 iterator
迭代器对象,其中entries()
返回的迭代器对象用于遍历[key,value]
组成的数组,而keys()
返回的迭代器对象用于遍历所有的键名,values()
返回的迭代器对象用于遍历所有的键值。除了上面列出的Array、Set和 Map
结构支持for..of
外,下面在给出类数组结构(伪数组)的几种情况。
/* 1. arguments */
function test() {
console.log('arguments');
for (const iterator of arguments) {
console.log('iterator = ', iterator);
}
}
test("a", "b", "c", 10, 203);
/* 打印输出: */
/*
arguments
iterator = a
iterator = b
iterator = c
iterator = 10
iterator = 203 */
/* 2.NodeList */
let oDiv = document.createElement("div");
oDiv.innerHTML = "<span>A</span><span>B</span><span>c</span><span>D</span>";
console.log(oDiv.children);
for (const iterator of oDiv.children) {
console.log('element = ', iterator);
}
/* 打印输出 */
/*
HTMLCollection(4) [span, span, span, span]
VM76:5 element = <span>A</span>
VM76:5 element = <span>B</span>
VM76:5 element = <span>c</span>
VM76:5 element = <span>D</span> */
/* 3.字符串(String) */
let str = "Hello";
for (const iterator of str) {
console.log("s = ", iterator);
}
for (const iterator of str[Symbol.iterator]()) {
console.log("s = ", iterator);
}
/* 打印输出 */
/*
s = H
s = e
s = l
s = l
s = o */
在上面列出的几种伪数组结构中,他们内部都实现了iterator
接口,自己写的伪数组或者是对象实现了iterator
接口支持for...of
循环吗? 答案是否定的。
/* 1、自己写的伪数组结构 */
let likeArray = { 0: "a", 1: "b", 2: 'c', length: 3 };
for (const iterator of likeArray) {
console.log('iterator = ', iterator);
}
/* 报错:TypeError: likeArray is not iterable */
/* 2.对象结构 */
let o = { name: "Yong", age: 18 };
for (const iterator of o) {
console.log('iterator = ', iterator);
}
/* 报错:TypeError: o is not iterable */
如果自己写的伪数组也要能够支持for...of
循环,那么可以有下面几种尝试的办法。
let likeArray = { 0: "a", 1: "b", 2: 'c', length: 3 };
/* 第一种方式:通过对象解构方式来先转换为数组 */
/* 结果:失败 (分析原因:扩展运算符[...]内部默认会自动调用 iterator 接口) */
for (const iterator of[...likeArray]) {
console.log('iterator = ', likeArray);
}
/* 第二种方式:利用 Array.from尝试转换为数组 */
for (const iterator of Array.from(likeArray)) {
console.log('iterator = ', iterator);
}
/* 结果输出:
iterator = a
iterator = b
iterator = c */
/* 第三种方式:在当前伪数组的原型上面部署"原生"的 iterator 迭代器接口 */
/* ① */
// likeArray.__proto__[Symbol.iterator] = Array.prototype[Symbol.iterator];
/* ② */
// Object.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
/* ③ */
// likeArray[Symbol.iterator] = Array.prototype[Symbol.iterator];
/* ④ */
likeArray[Symbol.iterator] = [][Symbol.iterator].bind(likeArray);
for (const iterator of likeArray) {
console.log('iterator = ', iterator);
}
/* 结果输出:
iterator = a
iterator = b
iterator = c */
/* 2.对象结构 */
let o = { name: "Yong", age: 18 };
/* 2.1 对象无法直接通过 for...of进行遍历 */
for (const iterator of o) {
console.log('iterator = ', likeArray);
}
/* 报错:TypeError: o is not iterable */
/* 2.2 尝试利用数组的Symbol.iterator接口部署 */
o.__proto__[Symbol.iterator] = [][Symbol.iterator];
for (const iterator of o) {
console.log('iterator = ', likeArray);
}
/* 结果:不会进入循环,没有任何输出 */
/* 2.3 尝试遍历对象的 keys 间接遍历对象 */
for (const key of Object.keys(o)) {
console.log(`key:${key} value:${o[key]}`);
}
/* 结果 */
/*
key:name value:Yong
key:age value:18 */
这里简单思考和总结下,对象中没有实现
Iterator
迭代器的原因
○ 对象已经拥有了 for...in循环 (该循环专为对象迭代设计)。
○ 对象在遍历的时候,属性( 键值对 )遍历的先后顺序是不确定的,而Iterator
迭代器是线性的。
○ ES6提供了 Map ,可以在某种程度上实现替代操作。
在数组等数据结构中,当我们调用 entries()
或者是Symbol.iterator()
的时候将得到一个iterator
迭代器对象,在该对象中next
方法每调用一次就会返回一个包含本次迭代 value 值以及标记是否完成迭代的 done 属性。
let arr = ["a", "b"];
let iterator = arr[Symbol.iterator]();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
/* 打印输出 */
/*
{ value: 'a', done: false }
{ value: 'b', done: false }
{ value: undefined, done: true } */
这里我们可以尝试来封装一个函数makeIterator
,模拟 next
函数的工作过程。
let makeIterator = (arr) => {
let idx = 0;
return {
next: () => idx < arr.length ?
{ value: arr[idx], done: false } : { value: undefined, done: true }
}
}
let iterator = makeIterator([100, 200, "Yong"]);
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
/* 打印输出 */
/*
{ value: 100, done: false }
{ value: 200, done: false }
{ value: 'Yong', done: false }
{ value: undefined, done: true }*/
假如我们想要让普通的对象也能够直接支持(除了Object.keys()
形式)for...of
循环,那么可以考虑主动的在对象或者对象的原型对象上面部署iterator
迭代器接口,下面简单给出对应的示例代码。
/* 方案-01 */
// let o = {
// name: "Yong",
// address: "GuangZhou",
// [Symbol.iterator]() {
// let idx = 0;
// let map = [];
// Object.keys(this).forEach(key => map.push([key, this[key]]))
// return {
// next: () => idx < map.length ? { value: map[idx++], done: false }
// : { value: undefined, done: true }
// };
// }
// };
/* 方案-02 */
Object.prototype[Symbol.iterator] = function() {
let idx = 0;
let map = [];
Object.keys(this).forEach(key => map.push([key, this[key]]))
return {
next: () => idx < map.length ? { value: map[idx++], done: false }
: { value: undefined, done: true }
};
}
let o = {
name: "Yong",
address: "GuangZhou"
};
/* 测试代码 */
let iterator = o[Symbol.iterator]();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log("+++++++++++++++++");
for (const iterator of o) {
console.log('iterator:', iterator);
}
console.log("+++++++++++++++++");
for (const [key, value] of o) {
console.log('key:', key, "val:", value);
}
/* 打印输出 */
/*
{ value: [ 'name', 'Yong' ], done: false }
{ value: [ 'address', 'GuangZhou' ], done: false }
{ value: undefined, done: true }
+++++++++++++++++
iterator: [ 'name', 'Yong' ]
iterator: [ 'address', 'GuangZhou' ]
+++++++++++++++++
key: name val: Yong
key: address val: GuangZhou */
如果想要更简单点,其实还可以借助 Generator 生成器函数来实现。
/* 生成器函数来实现 */
let obj = {
*[Symbol.iterator]() {
yield "H";
yield "e";
yield "l";
yield "l";
yield "o"
}
}
for (const e of obj) {
console.log('e:', e);
}
/* 打印输出 */
/*
e: H
e: e
e: l
e: l
e: o */