ES6 详解之迭代器 for of

起源

一段标准的 for 循环代码:

var colors = ['red','green','white'];

for(var i = 0,len = colors.length;i<len;i++){

console.log(colors[i]);

}

看着很简单,但是再回顾这段代码,实际上我们仅仅是需要数组中元素的值,但是却需要提前获取数组长度,声明索引变量等,尤其当多个循环嵌套的时候,更需要使用多个索引变量,代码的复杂度就会大大增加,比如我们使用双重循环进行去重:

function unique(array){

var res = [];

for(var i=0,arrayLen=array.length;i<arrayLen;i++){

for (var j=0,resLen=res.length;j<resLen;j++){

if(array[i] === res[j]){

break;

}

}

if(j===resLen){

res.push(array[i]);

}

}

return res;

}

迭代器

所谓迭代器,其实就是一个具有 next() 方法的对象,每次调用 next() 都会返回一个结果对象,该结果对象有两个属性,value 表示当前的值,done 表示遍历是否结束。

用 ES5 的语法创建一个迭代器:

function createIterator (items){

var i=0;

return {

next:function(){

var done = i>=items.length;

var value = !done?items[i++]:undefined;

return {

done:done,

value:value

};

}

};

}

// iterator 就是一个迭代器对象

var iterator = createIterator([1,2,3,4]);

console.log(iterator.next()); // {done: false, value: 1}

console.log(iterator.next()); //{done: false, value: 2}

console.log(iterator.next()); //{done: false, value: 3}

console.log(iterator.next()); //{done: false, value: 4}

console.log(iterator.next()); //{done: true, value: undefined}

for of

除了迭代器之外,我们还需要一个可以遍历迭代器对象的方式,ES6 提供了 for of 语句,我们直接用 for of 遍历一下我们上节生成的遍历器对象试试:

var arr = createIterator([1,2,3,4]);

for (const value of arr) {

console.log(value);

}

报错了: Uncaught TypeError: arr[Symbol.iterator] is not a function,表明我们生成的 iterator 对象并不是 iterable 可遍历的。

其实一种数据结构只要部署了 Iterator 接口,我们就称这种数据结构是“可遍历的”(iterable)。

ES6 规定,默认的 Iterator 接口部署在数据结构的 Symbol.iterator 属性,或者说,一个数据结构只要具有 Symbol.iterator 属性,就可以认为是”可遍历的”(iterable)。举个例子:

var obj ={

value:1

}

for (const value of obj) {

console.log(value);

}

报错 :Uncaught TypeError: obj[Symbol.iterator] is not a function

我们直接 for of 遍历一个对象,会报错,然而如果我们给该对象添加 Symbol.iterator 属性:

var obj ={};

obj[Symbol.iterator] = function(){

return createIterator([1,2,3,4]);

}

for (const value of obj) {

console.log(value);

}

由此,我们也可以发现 for of 遍历的其实是对象的 Symbol.iterator 属性。

默认可遍历对象

数组默认可遍历

模拟实现 for of

其实模拟实现 for of 也比较简单,基本就是通过 Symbol.iterator 属性获取迭代器对象,然后使用 while 遍历一下:

// 模拟实现 for of

function forOf(obj,cb){

let iterable,result;

console.log(typeof obj[Symbol.iterator]);

if(typeof obj[Symbol.iterator] !== "function")

throw new TypeError (result + "is not iterable");

if(typeof cb!== "function") throw new TypeError("cb must be callable");

iterable = obj[Symbol.iterator]();

result = iterable.next();

while(!result.done){

cb(result.value);

result = iterable.next();

}

}

内建迭代器

为了更好的访问对象中的内容,比如有的时候我们仅需要数组中的值,但有的时候不仅需要使用值还需要使用索引,ES6 为数组、Map、Set 集合内建了以下三种迭代器:

  • entries()返回一个遍历器对象,用来遍历[key,value]组成的数组,对于数组键名就是索引值
  • keys() 返回一个遍历器对象,用来遍历所有的键名
  • values()返回一个遍历器对象,用来遍历所有的键值

以数组为例:

let colors = ["red","black","orange","blue"];

for (const index of colors.keys()) {

console.log(index); // 0,1,2,3

}

for (const value of colors.values()) {

console.log(value); // red,black,orange,blue

}

for (const item of colors.entries()) {

console.log(item); // [0,"red"] ,[1,"black"],[2,"orange"],[3,"blue"]

}

for (const [k,v] of colors.entries()) {

console.log(k+":"+v); // 0 :red ,1:black,2:orange,3:blue

}

Map 类型与数组类似,但是对于 Set 类型需要注意以下:

var colors = new Set(["red","black","orange","blue"]);

for (const index of colors.keys()) {

console.log(index); // red black orange blue

}

for (const color of colors.values()) {

console.log(color); // red black orange blue

}

for (const color of colors.entries()) {

console.log(color); // ["red","red"],["black","black"],["orange","orange"],["blue","blue"]

}

Set 类型的 keys() 和 values() 返回的是相同的迭代器,这也意味着在 Set 这种数据结构中键名与键值相同。

而且每个集合类型都有一个默认的迭代器,在 for-of 循环中,如果没有显式指定则使用默认的迭代器。

数组和 Set 集合的默认迭代器是 values() 方法,Map 集合的默认迭代器是 entries() 方法。

遍历 Map 数据结构的时候可以顺便结合解构赋值

let colors = ["red","black","orange","blue"];

for (const [k,v] of colors.entries()) {

console.log(k+":"+v); // 0 :red ,1:black,2:orange,3:blue

}

Babel 是如何编译 for of 的

const colors = new Set(["red","green","blue"]);

for (const color of colors) {

console.log(color);

}

"use strict"

var colors = new Set(["red","green","blue"]);

var _iteratorNormalCompletion = true;

var _didIteratorError = false;

var _iteratorError = undefined;

try {

for (

var _iterator = colors[Symbol.iterator](),_step;

!(_iteratorNormalCompletion = (_step = _iterator.next()).done);

_iteratorNormalCompletion = true

) {

var color = _step.value;

console.log(color);

}

} catch(err) {

_didIteratorError = true;

_iteratorError = err;

} finally {

try {

if(!_iteratorNormalCompletion && _iterator.return) {

_iterator.return();

}

} finally {

if(_didIteratorError){

throw _iteratorError;

}

}

}

至少由编译的结果可以看出,使用 for of 循环的背后,还是会使用 Symbol.iterator 接口。

而这段编译的代码稍微复杂的地方有两段,一段是 for 循环这里:

for (

var _iterator = colors[Symbol.iterator](),_step;

!(_iteratorNormalCompletion = (_step = _iterator.next()).done);

_iteratorNormalCompletion = true

) {

var color = _step.value;

console.log(color);

}

跟标准的 for 循环写法有些差别,我们看下 for 语句的语法:

for(initialize;test;increment) statement;

initialize、test 和 increment 三个表达式之间用分号分割,它们分别负责初始化操作、循环条件判断和计数器变量的更新。

for 语句其实就相当于:

initialize;

while(test){

statement;

increment;

}

代码的逻辑为:先进行初始化,然后每次循环执行之前会执行 test 表达式,并判断表达式的结果来决定是否执行循环体,如果 test 计算结果为真值,则执行循环体中的 statement。最后,执行 increment 表达式。

而且值得注意的是,其实 for 循环中的三个表达式中任意一个都可以被忽略,不过分号还是要写的。

比如 for(;;),不过这就是一个死循环,比如:

var i =0;

len = colors.length;

for (;i<len;i++){

console.log(colors[i])

}

通过 Bable 编译的 for 循环表达式通过 while 来重写

var _itera = colors[Symbol.iterator](),_step;

while(!(_iteratorNormalCompletion = (_step = _iterator.next()).done)){

var color = _step.calue;

console.log(color);

_iteratorNormalCompletion = true;

}

以上是 ES6 详解之迭代器 for of 的全部内容, 来源链接: utcz.com/z/264060.html

回到顶部