混子前端所知道关于ES6的Iterator

栏目: JavaScript · 发布时间: 5年前

内容简介:由于是关于Iterator的文章,所以前文不得不提起 JavaScript 表示“集合”的数据结构,主要是数组(Array)和 对象(Object),ES6又添加了 Map 和 Set,这样就有了四种数据集合,用户还可以组合使用它们,定义自己的数据结构,这样就需要一种统一的接口机制,来处理所有不同的数据结构。Iterator的作用有三个:

由于是关于Iterator的文章,所以前文不得不提起 JavaScript 表示“集合”的数据结构,主要是数组(Array)和 对象(Object),ES6又添加了 Map 和 Set,这样就有了四种数据集合,用户还可以组合使用它们,定义自己的数据结构,这样就需要一种统一的接口机制,来处理所有不同的数据结构。

Iterator(遍历器)的概念

说明: 遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口,就可以完成遍历操作。

Iterator的作用有三个:

  • 为各种数据结构,提供统一 简便的访问接口
  • 使数据结构的成员能够按某种次序排列
  • ES6创造了一种新的遍历 for...of 循环,Iterator接口主要供 for...of 消费。

Iterator的遍历过程大概是这样的:

  • 创建一个指针对象,指向当前数据结构的起始位置。
  • 第一次调用指针对象的next 方法,可以将指针指向数据结构的第一个成员。
  • 第二次调用指针对象的 next 方法,指针就指向数据结构的第二个成员。
  • 不断调用指针对象的 next 方法,直到它指向数据结构的结束位置。

PS: 每一次调用  next 方法,都会一个包含  value 和  done 两个属性的对象。

value  属性是当前成员的值, done 

属性是一个布尔值,表示遍历是否结束,来看代码:

JS code:

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};
        }
    };
}复制代码

说明: makeIterator 是遍历器生成函数,作用就是返回一个遍历器对象。 对数组  ['a', 'b']  执行这个函数,就会返回该数组的遍历器对象 (  指针对象  ) it, 指针对象的  next 方法,用来移动指针, next  方法返回一个对象, 对象具有  value   和  done   两个属性, value  属性返回当前位置的成员, done  属性是一个布尔值,表示遍历是否结束,即是否还有必要再一次调用  next  方法。

数据结构的默认Iterator接口

说明: Iterator接口的目的:为所有数据结构提供统一的访问机制( for...of循环 ) , 当使用  for...of  循环遍历某种数据结构时,该循环会自动去寻找Iterator接口。

ES6规定, 数据结构只要具有  Symbol.iterator  属性,就是可遍历的

Symbol.iterator  属性本身是一个函数,是当前数据结构默认的遍历器生成函数, 执行这个函数,就会返回一个遍历器。

PS: Symbol.iterator  是一个表达式,返回   Symbol  对象的   iterator  属性,是一个预定义好类型为Symbol的特殊值,所以要放在方括号内, 来看代码:

JS code:

const obj = {
    [Symbol.iterator]: function () {
        return {
            next: function () {
                return {
                  value: 1,
                  done: true
                };
            }
        };
    }
};
复制代码

说明: 对象  obj  是可遍历的,因为有  Symbol.iterator   属性。执行这个属性,会返回一个遍历器对象。对象的根本特征就是具有  next  方法。调用   next  方法,会返回一个代表当前成员的信息对象,具有   value    done  两个属性。

在ES6中,有三类数据结构原生具备Iterator接口:数组、某些类似数组的对象、Set和Map结构,来看代码:

JS code:

let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();

iter.next()    // { value: 'a', done: false }
iter.next()    // { value: 'b', done: false }
iter.next()    // { value: 'c', done: false }
iter.next()    // { value: undefined, done: true }

复制代码

说明: 变量arr是一个数组,原生具有遍历器接口,部署在 arr 的 Symbol.iterator 属性上面,所以,调用这个属性,就得到遍历器对象。

上面提到三类数据结构原生具备Iterator接口,有可能会问为什么没有我们常见的对象Object,那现在为大家解答,对象之所以没有默认部署Iterator接口,是因为对象的哪个属性先遍历,哪个属性后遍历是不确定的,需要开发者手动指定,所以一个对象 如果要有可被   for...of  循环调用的Iterator接口,就必须在   Symbol.iterator  的属性上部署遍历器生成方法(原型链上的对象具有该方法也可),首先来看类部署Iterator接口的写法,来看代码:

JS code:
class RangeIterator {
    constructor(start, stop) {
        this.value = start;
        this.stop = stop;
    }  [Symbol.iterator]() { return this; }

  next() {
      var value = this.value;
      if (value < this.stop) {
          this.value++;
          return {done: false, value: value};
        } else {
          return {done: true, value: undefined};
        }
    }
}

function range(start, stop) {
    return new RangeIterator(start, stop);
}

for (var value of range(0, 3)) {
    console.log(value);
}复制代码

说明: Symbol.iterator  属性对应一个函数,执行后返回当前对象的遍历器对象。

下面是另一个为对象添加Iterator接口的例子,来看代码:

JS code:

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 };
        	}
      	    }
    	};
    }
};

复制代码

PS: 对于类似数组的对象(存在数值键名和length属性)部署Iterator接口,有一个简便方法,就是  Symbol.iterator  方法直接引用数组的Iterator接口,来看代码:

JS code:

NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];

// 或者
NodeList.prototype[Symbol.iterator] = [][Symbol.iterator];

[...document.querySelectorAll('div')] // 可以执行了

复制代码

如果是类似数组的对象调用数组的 Symbol.iterator 方法呢?来看代码

JS code:

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'
}

复制代码

注意: 普通对象部署数组的  Symbol.iterator  方法,并无效果。 

调用Iterator接口的场合

有一些场合会默认调用Iterator接口(即   Symbol.iterator  方法),除了   for...of  循环,还有几个别的场合。

  • 解构赋值

    对数组和Set结构进行解构赋值时,会默认调用
      Symbol.iterator 方法。
JS code:

let set = new Set().add('a').add('b').add('c');

let [x,y] = set;
// x='a'; y='b'

let [first, ...rest] = set;
// first='a'; rest=['b','c'];
复制代码
  • 扩展运算符

    扩展运算符(...)也会调用默认的iterator接口。
JS code:

// 例一
var str = 'hello';
[...str] //  ['h','e','l','l','o']

// 例二
let arr = ['b', 'c'];
['a', ...arr, 'd']
// ['a', 'b', 'c', 'd']

复制代码

说明: 提供了一种简便机制,可以将任何部署了Iterator接口的数据结构 转为数组,即只要某个数据结构部署了Iterator接口,就可以对它使用扩展运算符,将其转为数组。

  • yield*

    yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。

JS code:

let generator = function* () {
    yield 1;
    yield* [2,3,4];
    yield 5;
};

var iterator = generator();

iterator.next()    // { value: 1, done: false }
iterator.next()    // { value: 2, done: false }
iterator.next()    // { value: 3, done: false }
iterator.next()    // { value: 4, done: false }
iterator.next()    // { value: 5, done: false }
iterator.next()    // { value: undefined, done: true }

复制代码
  • 其他场合

    由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,其实都调用了遍历器接口,如:

    1. for...of

    2. Array.from()

    3. Map(), Set(), WeakMap(), WeakSet()

    4. Promise.all()

    5. Promise.race()

字符串的Iterator接口

字符串是一个类似数组的对象,也原生具有Iterator接口,来看代码:

JS code:

var someString = "hi";
typeof someString[Symbol.iterator]
// "function"

var iterator = someString[Symbol.iterator]();

iterator.next()  // { value: "h", done: false }
iterator.next()  // { value: "i", done: false }
iterator.next()  // { value: undefined, done: true }
复制代码

说明: 调用   Symbol.iterator  方法返回一个遍历器对象,在这个遍历器上可以调用 next 方法,实现对于字符串的遍历。

当然还可以 覆盖原生的   Symbol.iterator  方法,达到修改遍历器行为的目的,来看代码:

JS code:

var str = new String("hi");

[...str] // ["h", "i"]

str[Symbol.iterator] = function() {
    return {
        next: function() {
            if (this._first) {
                this._first = false;
                return { value: "bye", done: false };
            } else {
                return { done: true };
            }
        },
        _first: true
    };
};

[...str] // ["bye"]
str // "hi"

复制代码

说明: 字符串 str 的   Symbol.iterator  方法被修改了,所以扩展运算符 ( ... )  返回的值变成了bye ,而字符串本身还是 hi

Iterator接口与Generator函数

Symbol.iterator 方法的最简单实现,还是使用Generator函数,来看代码:

JS code:

var myIterable = {};

myIterable[Symbol.iterator] = function* () {
    yield 1;
    yield 2;
    yield 3;
};

[...myIterable] // [1, 2, 3]

// 或者采用以下简单写法

let obj = {
    * [Symbol.iterator]() {
        yield 'hello';
    	yield 'world';
    }
};

for (let x of obj) {
    console.log(x);
};

// hello
// world复制代码

说明:Symbol.iterator 方法几乎不用部署代码,仅用 yield 命令给出每一步的返回值即可。

遍历器对象的return(),throw()

说明: 遍历器对象除了具有   next  方法,还可以具有   return  方法和   throw  方法。如果自己写遍历器对象生成函数, next  方法是必须部署的, return  方法和  throw  方法是否部署是可选的。

return 适用场景:如果 for...of 循环提前退出(通常是因为出错,或有 break 语句 或 continue 语句),就会调用   return  方法。如果一个对象在完成遍历前,需要清理或释放资源,就可以部署   return  方法,来看代码:

JS code:

function readLinesSync(file) {
    return {
        next() {
      	    if (file.isAtEndOfFile()) {
                file.close();
        	return { done: true };
      	    }
    	},
    	return() {
      	    file.close();
      	    return { done: true };
    	},
    };
}
复制代码

说明: 函数  readLinesSync  接受一个文件对象作为参数,返回一个遍历器对象,其中除了   next  方法,还部署了   return  方法。

throw 方法主要是配合Generator函数使用,一般的遍历器对象用不到这个方法。

好了,关于 Iterator 混子前端就总结到这里,老规矩,欢迎点赞和纠错!

最后,祝大家工作愉快!

( 内心os:明天终于周五了 )


以上所述就是小编给大家介绍的《混子前端所知道关于ES6的Iterator》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

复杂性思考

复杂性思考

Allen B. Downey / 张龙 / 机械工业出版社 / 2013-5 / 49.00元

本书的灵感来源于无聊与迷恋的感觉:对常规的数据结构与算法介绍的无聊,对复杂系统的迷恋。数据结构的问题在于教师在教授这门课程的时候通常不会调动起学生的积极性;复杂性科学的问题在于学校通常不会教授这门课程。 2005年,我在欧林学院讲授了一门新课程,学生要阅读关于复杂性的主题,使用Python进行实验,并学习算法与数据结构。当我在2008年再次讲授这门课程时,我写了本书的初稿。 在2011......一起来看看 《复杂性思考》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具