书籍参考:《ECMAScript 6入门》 作者:阮一峰
Iterator概念
ES6发布后Javascript的数据集合对象有Array
、Object
、Map
和Set
四种,我们同时知道在Array
中,我们可以放对象,Object
中我们一样可以放Array
,四种数据集合对象都可以相互交叉使用。
那么问题来了,如果在一个数据集合对象中含有多个不同数据集合对象的类型,那么这时候就需要一种接口的机制,来处理不同的数据集合了。
Iterator有三个作用:
- 为各种数据接口提供一个统一的访问接口
- 使数据接口的成员能够按照某种次序排序
- ES6新遍历名利 for...of循环
按照《ECMAScript 6入门》这本书中的4个步骤就能很好的解释Iterator遍历过程
- 创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
-
第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
-
第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
-
不断调用指针对象的next方法,直到它指向数据结构的结束位置。
用一个代码段来实现上面的4个点,也就是来实现一个Iterator遍历器
function iter(arr) {
let index = 0;
return {
next() {
return index <= arr.length ?
{ value: arr[index++], done: false } :
{ value: undefined, done: true}
}
}
}
let test = iter([1, 2]);
test.next(); // 第一次运行next方法。返回Object {value: 1, done: false}
test.next(); // 第二次运行next方法。返回Object {value: 2, done: false}
test.next(); // 第三次运行next方法。返回Object {value: undefined, done: false}
上面的代码实现了一个遍历器生成函数,每次传入数组或者类数组或者是含有length
属性的并可遍历数据,都可以返回一个遍历器。遍历器生成函数在内部定义了一个变量index用于数据指针,指向当前遍历位置的成员,然后返回了一个对象,用指针去判断如果指针已经大于数组最大的成员数,那么返回值为空和返回一个done
属性并值为true
表示已经结束遍历,反之,返回相应的值。其中用done来判断是否遍历结束。
Iterator接口原生的数据结构
数组、类数组、Map、Set几种数据结构是具备原生的Iterator接口,也就是说我们不需要任何的处理就可以使用遍历器、以及调用遍历器接口循环的for...of命令。如何知道一个数据结构是否具备Iterator接口?通过Symbol.iterator这个属性来确定是否部署了iterator接口,Symbol.iterator是Symbol对象下面的iterator属性,它返回一个独一无二的Symbol值,如果一个数据结构通过此属性访问会返回一个函数,说明这个数据结构已经部署了遍历器。
let arr = [1, 2];
arr[Symbol.iterator]; // function values() { [native code] }
let arrIt = arr[Symbol.iterator](); // 得到一个遍历器
arrIt.next(); // Object {value: 1, done: false}
arrIt.next(); // Object {value: 2, done: false}
arrIt.next(); // Object {value: undefined, done: true}
上面提到的是具备原生iterator接口的数据结构,那没有的数据结构呢?比如对象,《ECMAScript 6入门》这本书中提到了一段话来说明为什么对象没有部署原生的iterator接口.
对象(Object)之所以没有默认部署Iterator接口,是因为对象的哪个属性先遍历,哪个属性后遍历是不确定的,需要开发者手动指定。本质上,遍历 器是一种线性处理,对于任何非线性的数据结构,部署遍历器接口,就等于部署一种线性转换。不过,严格地说,对象部署遍历器接口并不是很必 要,因为这时对象实际上被当作Map结构使用,ES5没有Map结构,而ES6原生提供了。
如果我们真的需要给没有部署Iterator接口的数据结构就只需要在这个数据结构的Symbol.iterator属性上面部署一个iterator接口就可以实现了:
const obj = {
data: [1, 2, 3],
[Symbol.iterator](it) {
const self = this;
let index = 0;
return {
next() {
if(index <= self[it].length ) {
return {value: self[it][index++], done: false }
} else {
return {value: undefined, done: true}
}
}
}
}
}
let objIt = obj[Symbol.iterator]('data'); //返回obj对象data属性中数组的iterator遍历器
obj.pubData = ['1', 'b', 'bbx'];
objIt = obj[Symbol.iterator]('pubData'); //返回obj对象pubData属性中数组的iterator遍历器
通过上面的代码我们可以给obj对象部署一个iterator接口,并且在调用Symbol.iterator的属性方法的时候传入的一个值,可以用来便利对象中的自定义数组,当然这个方法我们一样可以定义在原型上面,方便所有对象来生成iterator遍历器。
Object.prototype[Symbol.iterator] = function(it){
const self = this;
let index = 0;
return {
next() {
if(index <= self[it].length ) {
return {value: self[it][index++], done: false }
} else {
return {value: undefined, done: true}
}
}
}
}
const obj = {
data: [1, 2, 3]
}
let objIt = obj[Symbol.iterator]('data'); //返回obj对象data属性数组的遍历器
遍历器对象 return(),throw()
遍历器的next()
方法是必须具备的,而return()
和throw()
方法是可选的,其中return()
方法的作用是在循环的时候提前退出,包括出错、break、contiune等命令就会触发return()
方法,return()
方法在出错或者提前退出循环的时候清空不需要的资源,return()
方法由Generator规格规定返回必须为一个对象,而throw()
方法主要是配合Generator函数使用。
文完
《ES6-iterator遍历接口》留言数:0