ES6-iterator遍历接口

书籍参考:《ECMAScript 6入门》 作者:阮一峰

1.Iterator概念

ES6发布后Javascript的数据集合对象有ArrayObjectMapSet四种,我们同时知道在Array中,我们可以放对象,Object中我们一样可以放Array,四种数据集合对象都可以相互交叉使用。

那么问题来了,如果在一个数据集合对象中含有多个不同数据集合对象的类型,那么这时候就需要一种接口的机制,来处理不同的数据集合了。

Iterator有三个作用:

  1. 为各种数据接口提供一个统一的访问接口
  2. 使数据接口的成员能够按照某种次序排序
  3. ES6新遍历名利 for…of循环

按照《ECMAScript 6入门》这本书中的4个步骤就能很好的解释Iterator遍历过程

  1. 创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。

  2. 第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。

  3. 第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。

  4. 不断调用指针对象的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来判断是否遍历结束。

2.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属性数组的遍历器

3.遍历器对象 return(),throw()

遍历器的next()方法是必须具备的,而return()throw()方法是可选的,其中return()方法的作用是在循环的时候提前退出,包括出错、break、contiune等命令就会触发return()方法,return()方法在出错或者提前退出循环的时候清空不需要的资源,return()方法由Generator规格规定返回必须为一个对象,而throw()方法主要是配合Generator函数使用。

文完

文档信息

发表评论

电子邮件地址不会被公开。 必填项已用*标注