ES6 系列之迭代器与 for of

劈头

一段范例的 for 轮回代码:

var colors = ["red", "green", "blue"];

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

为了消弭这类庞杂度以及削减轮回中的毛病(比方毛病运用其他轮回中的变量),ES6 供应了迭代器和 for of 轮回配合处置惩罚这个题目。

迭代器

所谓迭代器,实在就是一个具有 next() 要领的对象,每次挪用 next() 都邑返回一个效果对象,该效果对象有两个属性,value 示意当前的值,done 示意遍历是不是完毕。

我们直接用 ES5 的语法建立一个迭代器:

function createIterator(items) {
    var i = 0;
    return {
        next: function() {
            var done = i >= item.length;
            var value = !done ? items[i++] : undefined;

            return {
                done: done,
                value: value
            };
        }
    };
}

// iterator 就是一个迭代器对象
var iterator = createIterator([1, 2, 3]);

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: true, value: undefined }

for of

除了迭代器以外,我们还须要一个能够遍历迭代器对象的体式格局,ES6 供应了 for of 语句,我们直接用 for of 遍历一下我们上节天生的遍历器对象尝尝:

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

for (let value of iterator) {
    console.log(value);
}

效果报错 TypeError: iterator is not iterable,表明我们天生的 iterator 对象并非 iterable(可遍历的)。

那什么才是可遍历的呢?

实在一种数据结构只需布置了 Iterator 接口,我们就称这类数据结构是“可遍历的”(iterable)。

ES6 划定,默许的 Iterator 接口布置在数据结构的 Symbol.iterator 属性,或许说,一个数据结构只需具有 Symbol.iterator 属性,就能够认为是”可遍历的”(iterable)。

举个例子:

const obj = {
    value: 1
};

for (value of obj) {
    console.log(value);
}

// TypeError: iterator is not iterable

我们直接 for of 遍历一个对象,会报错,但是假如我们给该对象增加 Symbol.iterator 属性:

const obj = {
    value: 1
};

obj[Symbol.iterator] = function() {
    return createIterator([1, 2, 3]);
};

for (value of obj) {
    console.log(value);
}

// 1
// 2
// 3

由此,我们也能够发明 for of 遍历的实际上是对象的 Symbol.iterator 属性。

默许可遍历对象

但是假如我们直接遍历一个数组对象:

const colors = ["red", "green", "blue"];

for (let color of colors) {
    console.log(color);
}

// red
// green
// blue

只管我们没有手动增加 Symbol.iterator 属性,照样能够遍历胜利,这是由于 ES6 默许布置了 Symbol.iterator 属性,固然我们也能够手动修正这个属性:

var colors = ["red", "green", "blue"];

colors[Symbol.iterator] = function() {
    return createIterator([1, 2, 3]);
};

for (let color of colors) {
    console.log(color);
}

// 1
// 2
// 3

除了数组以外,另有一些数据结构默许布置了 Symbol.iterator 属性。

所以 for…of 轮回能够运用的局限包含:

  1. 数组
  2. Set
  3. Map
  4. 类数组对象,如 arguments 对象、DOM NodeList 对象
  5. Generator 对象
  6. 字符串

模仿完成 for of

实在模仿完成 for of 也比较简单,基础就是经由历程 Symbol.iterator 属性猎取迭代器对象,然后运用 while 遍历一下:

function forOf(obj, cb) {
    let iterable, result;

    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 鸠合内建了以下三种迭代器:

  1. entries() 返回一个遍历器对象,用来遍历[键名, 键值]构成的数组。关于数组,键名就是索引值。
  2. keys() 返回一个遍历器对象,用来遍历一切的键名。
  3. values() 返回一个遍历器对象,用来遍历一切的键值。

以数组为例:

var colors = ["red", "green", "blue"];

for (let index of colors.keys()) {
    console.log(index);
}

// 0
// 1
// 2

for (let color of colors.values()) {
    console.log(color);
}

// red
// green
// blue

for (let item of colors.entries()) {
    console.log(item);
}

// [ 0, "red" ]
// [ 1, "green" ]
// [ 2, "blue" ]

Map 范例与数组相似,然则关于 Set 范例须要注重以下:

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

for (let index of colors.keys()) {
    console.log(index);
}

// red
// green
// blue

for (let color of colors.values()) {
    console.log(color);
}

// red
// green
// blue

for (let item of colors.entries()) {
    console.log(item);
}

// [ "red", "red" ]
// [ "green", "green" ]
// [ "blue", "blue" ]

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

而且每一个鸠合范例都有一个默许的迭代器,在 for-of 轮回中,假如没有显式指定章运用默许的迭代器。数组和 Set 鸠合的默许迭代器是 values() 要领,Map 鸠合的默许迭代器是 entries() 要领。

这也就是为何直接 for of 遍历 Set 和 Map 数据结构,会有差异的数据结构返回:

const values = new Set([1, 2, 3]);

for (let value of values) {
    console.log(value);
}

// 1
// 2
// 3
const values = new Map([["key1", "value1"], ["key2", "value2"]]);
for (let value of values) {
    console.log(value);
}

// ["key1", "value1"]
// ["key2", "value2"]

遍历 Map 数据结构的时刻能够趁便连系解构赋值:

const valuess = new Map([["key1", "value1"], ["key2", "value2"]]);

for (let [key, value] of valuess) {
    console.log(key + ":" + value);
}

// key1:value1
// key2:value2

Babel 是怎样编译 for of 的

我们能够在 Babel 的 Try it out 中检察编译的效果:

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

for (let 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]);
}

又比方:

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

然后我们再来看 Babel 编译的这个 for 轮回表达式:

for (
    var _iterator = colors[Symbol.iterator](), _step;
    !(_iteratorNormalCompletion = (_step = _iterator.next()).done);
    _iteratorNormalCompletion = true
) {
    var color = _step.value;
    console.log(color);
}

用 while 的写法相当于:

var _iterator = colors[Symbol.iterator](),
    _step;
while (!(_iteratorNormalCompletion = (_step = _iterator.next()).done)) {
    var color = _step.value;
    console.log(color);
    _iteratorNormalCompletion = true;
}

是不是是就好懂了许多呢,然后你就会发明,实在 _iteratorNormalCompletion = true 这句是完全没有必要的……

别的一段轻微庞杂的代码是:

try {
  ...
} catch (err) {
  ...
} finally {
  try {
    if (!_iteratorNormalCompletion && _iterator.return) {
      _iterator.return();
    }
  } finally {
    ...
  }
}

由于 _iteratorNormalCompletion = (_step = _iterator.next()).done,所以 _iteratorNormalCompletion 示意的就是是不是完成了一次完全的迭代历程,假如没有一般的迭代完成,而且迭代器有 return 要领时,就会实行该要领。

而之所以这么做,就要提到迭代器的 return 要领。

援用阮一峰先生的 ECMAScript 6 入门:

遍历器对象除了具有 next 要领,还能够具有 return 要领和 throw 要领。假如你本身写遍历器对象天生函数,那末 next 要领是必需布置的,return 要领和 throw 要领是不是布置是可选的。

return 要领的运用场所是,假如 for…of 轮回提早退出(一般是由于失足,或许有 break 语句或 continue 语句),就会挪用 return 要领。假如一个对象在完成遍历前,须要清算或开释资本,就能够布置 return 要领。

我们能够举个例子:

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
            };
        },
        return: function() {
            console.log("实行了 return 要领");
            return {
                value: 23333,
                done: true
            };
        }
    };
}

var colors = ["red", "green", "blue"];

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

colors[Symbol.iterator] = function() {
    return iterator;
};

for (let color of colors) {
    if (color == 1) break;
    console.log(color);
}
// 实行了 return 要领

不过正如你在编译后的代码中看到,仅仅是在有 return 函数的时刻实行了 return 函数罢了,return 函数中返回的值实在并不见效……

然则你不返回值或许返回一个基础范例的值的话,效果又会报错……

TypeError: Iterator result undefined is not an object

这是由于 return 要领必需返回一个对象,而这又是 Generator 范例决议的……

总之假如是在浏览器中运用的话,return 函数的返回值实在并不见效 T^T

ES6 系列

ES6 系列目次地点:https://github.com/mqyqingfeng/Blog

ES6 系列估计写二十篇摆布,旨在加深 ES6 部份知识点的明白,重点解说块级作用域、标签模板、箭头函数、Symbol、Set、Map 以及 Promise 的模仿完成、模块加载计划、异步处置惩罚等内容。

假如有毛病或许不严谨的处所,请务必赋予斧正,非常谢谢。假如喜好或许有所启示,迎接 star,对作者也是一种勉励。

    原文作者:冴羽
    原文地址: https://segmentfault.com/a/1190000015585585
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞