起源

一段标准的 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;
}
0 条评论
作者
状态
  • 发布于 ,2 次更新
  • 最后更新于
  • 被浏览 161 次
  • 被评论 0 次
  • 隶属于 文章 版块
  • 归纳于 JavaScript 分类
  • 收录于 ES6 详解 专辑
标签
二维码
目录