JavaScript原有的 用来表示集合的数据结构,
ES6又增加了两种
这样就有了 4 种表示集合的数据结构,开发者可以根据自己需求,组合它们,定义自己需要的数据结构。
然而这样就需要一种统一的接口机制,来处理所有不同的数据结构。
Iterator的作用有三个
Iterator 的遍历过程:
每一次调用 next 方法,都会返回数据结构的当前成员的信息。
就是返回一个包含 value 和 done 两个属性的对象。其中,value 属性是当前成员的值,done 属性是一个布尔值,表示遍历是否结束。
举一个模拟遍历器 next方法返回值的例子
var it = makeIterator(['a', 'b']);
it.next() // { value: "a", done: false }
it.next() // { value: "b", done: false }
it.next() // { value: undefined, done: true }
function makeIterator(array) {
var nextIndex = 0;
return {
next: function() {
return nextIndex < array.length ?
{value: array[nextIndex++], done: false} :
{value: undefined, done: true};
}
};
}
//对于遍历器对象来说,done: false和value: undefined属性都是可以省略的
//因此上面的makeIterator函数可以简写成下面的形式。
function makeIterator(array) {
var nextIndex = 0;
return {
next: function() {
return nextIndex < array.length ?
{value: array[nextIndex++]} :
{done: true};
}
};
}
上面代码,makeIterator
函数,它是一个自定义的遍历器生成函数,返回一个遍历器对象。
总之,调用指针对象(遍历器对象)的 next 方法,就可以遍历事先给定的数据结构。
由于 Iterator 只是把接口规格加到数据结构之上,所以,遍历器与它所遍历的那个数据结构,实际上是分开的,完全可以写出没有对应数据结构的遍历器对象,或者说用遍历器对象模拟出数据结构。
设置默认 Iterator的目的
一种数据结构只要部署了 Iterator 接口,我们就称这种数据结构是“可遍历的”(iterable)。
ES6 规定,默认的 Iterator 接口部署在数据结构的Symbol.iterator
属性,或者说,一个数据结构只要具有Symbol.iterator
属性,就可以认为是“可遍历的”
ES6 的有些数据结构原生具备 Iterator 接口,可以直接被 for…of 循环。
因为这些数据结构部署了 Symbol.iterator 属性(遍历器接口),调用这个接口,就会返回一个遍历器对象。
而有些数据结构则没有原生部署这个接口,无法直接调用 for…of。比如对象
严格地说,对象部署遍历器接口并不是很必要,因为这时对象实际上被当作 Map 结构使用,ES5 没有 Map 结构,而 ES6 原生提供了。
let obj = {
data: [ 'hello', 'world' ],
[Symbol.iterator]() {
const self = this;
let index = 0;
return {
next() {
if (index < self.data.length) {
return {
value: self.data[index++],
done: false
};
} else {
return { value: undefined, done: true };
}
}
};
}
};
for(let i of obj){
console.log(i) //输出两次结果:'hello' 'world'
}
上面的代码,向对象部署了遍历器接口,使得 obj 可以被 for…of 遍历
对于类似数组的对象(存在数值键名和length属性),部署 Iterator 接口,有一个简便方法,就是Symbol.iterator方法直接引用数组的 Iterator 接口。
NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
// 或者
NodeList.prototype[Symbol.iterator] = [][Symbol.iterator];
[...document.querySelectorAll('div')] // 可以执行了(扩展运算符内部默认会行使遍历操作)
另一个例子
let iterable = {
0: 'a',
1: 'b',
2: 'c',
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of iterable) {
console.log(item); // 'a', 'b', 'c'
}
上面的例子,iterable 中的键名对应数组,若是改变键名为字符串,当前部署遍历器对象的操作将无效。遍历结果会输出 undefined
有一些场合会默认调用 Iterator 接口(即Symbol.iterator方法)
解构赋值:对 Array 和 Set 进行结构赋值时,会默认的调用 Symbol.iterator 方法
扩展运算符:使用扩展运算符时(…),运算符内部也会调用
yield*:yield后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。(以后的Generator函数中会学习到yield表达式)
注意:
var arr = [0,1,2]
arr[Symbol.iterator] = function(){
console.log(arr)//(3) [0, 1, "2", Symbol(Symbol.iterator): ƒ]
let _index = 0
return{
next(){
if ( _index++ <= arr.length ) {
return { value:'x' ,done:false }
}else{
return{ value:undefined ,done:true }
}
}
}
}
for( let i of arr ){
console.log(i) //输出4遍 'x'
}
数组本身是有 遍历器对象的 ,但是上面重新给数组定义了 Symbol.iterator 属性,部署了遍历的新方法。
遍历器对象一共有 3 种方法
throw
方法主要是配合 Generator 函数使用,一般的遍历器对象用不到这个方法。
next
如果你自己写遍历器对象生成函数,那么 next 方法是必须部署的。其他两种是可选的
return
如果 for…of 循环提前退出(通常是因为出错,或者有 break 语句),就会调用 return 方法。
举个例子
function readLinesSync(file) {
return {
[Symbol.iterator]() {
return {
next() {
return { done: false };
},
return() {
file.close();
return { done: true };
}
};
},
};
}
上面的例子,返回了一个遍历器对象,定义了 next 方法和 return 方法
// 情况一
for (let line of readLinesSync(fileName)) {
console.log(line);
break;
}
// 情况二
for (let line of readLinesSync(fileName)) {
console.log(line);
throw new Error();
}
上面代码中,情况一输出文件的第一行以后,就会执行return方法,关闭这个文件;情况二会在执行return方法关闭文件之后,再抛出错误。
注意:
ES6 借鉴 C++、Java、C# 和 Python 语言,引入了
for...of
循环,作为遍历所有数据结构的统一的方法。
for…of循环可以使用的范围包括数组、Set 和 Map 结构、某些类似数组的对象(比如arguments对象、DOM NodeList 对象)、 Generator 对象,以及字符串。
for...in
循环用来遍历键名,有几个缺点
for...of
循环相比上面几种做法,有一些显著的优点。
forEach
(不可以使用 break、continue 和 return)方法,它可以与break、continue和return配合使用。数组、对象、Set、Map
,他们有统一的接口机制(Iterator),来处理所有不同的数据结构。Iterator
可以使用。若需要遍历 for... of
遍历对象,则可以讲对象转为 Map 结构,或者自己部署 遍历器for...of
(遍历键值) 循环,是遍历所有数据结构的统一操作接口。Symbol.iterator
属性来自定义遍历器的行为next
return
throw
,后两个方法可选。for...in
循环遍历键名(主要遍历对象),for...of
遍历键值 。对于 for…of 遍历Map结构,会将"键值对"(数组形式)都遍历出来