概述
Iterator
概念
遍历器(Iterator)是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
作用
1. 为各种数据结构,提供一个统一的、简便的访问接口;
2. 使得数据结构的成员能够按某种次序排列;
3. ES6 创造了一种新的遍历命令
for...of
循环,Iterator 接口主要供for...of
消费
遍历过程
(1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
(2)第一次调用指针对象的
next
方法,可以将指针指向数据结构的第一个成员。(3)第二次调用指针对象的
next
方法,指针就指向数据结构的第二个成员。(4)不断调用指针对象的
next
方法,直到它指向数据结构的结束位置
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};
}
};
}
可以看出next
方法返回一个对象,表示当前数据成员的信息。这个对象具有value
和done
两个属性,value
属性返回当前位置的成员,done
属性是一个布尔值,表示遍历是否结束,即是否还有必要再一次调用next
方法。
for of
上面说了 ES6 创造了一种新的遍历命令for...of
循环,Iterator 接口主要供for...of
消费
我们来试下:
var iterator = makeIterator([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)。Symbol.iterator
属性本身是一个函数,就是当前数据结构默认的遍历器生成函数。执行这个函数,就会返回一个遍历器。至于属性名Symbol.iterator
,它是一个表达式,返回Symbol
对象的iterator
属性,这是一个预定义好的、类型为 Symbol 的特殊值,所以要放在方括号内。
var obj = {
value: 1,
[Symbol.iterator]: function() {
return makeIterator([1, 2, 3]);
}
}
for (value of obj) {
console.log(value);
}
// 1
// 2
// 3
由此,我们也可以发现 for of 遍历的其实是对象的 Symbol.iterator 属性。
默认可遍历对象
let arr = ['a', 'b', 'c'];
for (let val of arr) {
console.log(val);
}
// 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 }
对于原生部署 Iterator 接口的数据结构,不用自己写遍历器生成函数,for...of
循环会自动遍历它们。除此之外,其他数据结构(主要是对象)的 Iterator 接口,都需要自己在Symbol.iterator
属性上面部署,这样才会被for...of
循环遍历。
原生具备 Iterator 接口的数据结构如下。
- Array
- Map
- Set
- String
- TypedArray
- 函数的 arguments 对象
- NodeList 对象
对象(Object)为什么没有默认部署 Iterator 接口?
因为对象的哪个属性先遍历,哪个属性后遍历是不确定的,需要开发者手动指定。本质上,遍历器是一种线性处理,对于任何非线性的数据结构,部署遍历器接口,就等于部署一种线性转换。
一个对象如果要具备可被for...of
循环调用的 Iterator 接口,就必须在Symbol.iterator
的属性上部署遍历器生成方法(原型链上的对象具有该方法也可)
function Obj(value) {
this.value = value;
this.next = null;
}
Obj.prototype[Symbol.iterator] = function() {
var iterator = { next: next };
var current = this;
function next() {
if (current) {
var value = current.value;
current = current.next;
return { done: false, value: value };
}
return { done: true };
}
return iterator;
}
var one = new Obj(1);
var two = new Obj(2);
var three = new Obj(3);
one.next = two;
two.next = three;
for (var i of one){
console.log(i); // 1, 2, 3
}
模拟实现 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();
}
}
entries、keys、values
ES6 的数组、Set、Map 都部署了以下三个方法,调用后都返回遍历器对象。
entries()
返回一个遍历器对象,用来遍历[键名, 键值]
组成的数组。对于数组,键名就是索引值;对于 Set,键名与键值相同。Map 结构的 Iterator 接口,默认就是调用entries
方法。keys()
返回一个遍历器对象,用来遍历所有的键名。values()
返回一个遍历器对象,用来遍历所有的键值。
数组:
let arr = ['a', 'b', 'c'];
for (let pair of arr.entries()) {
console.log(pair);
}
// [0, 'a']
// [1, 'b']
// [2, 'c']
for (let key of arr.keys()) {
console.log(key);
}
// 0
// 1
// 2
for (let value of arr.values()) {
console.log(value);
}
// a
// b
// c
Map 类型与数组类似
let map = new Map()
map.set(0, 'a');
map.set(1, 'b');
map.set(2, 'c');
for (let pair of map.entries()) {
console.log(pair);
}
// [0, 'a']
// [1, 'b']
// [2, 'c']
for (let key of map.keys()) {
console.log(key);
}
// 0
// 1
// 2
for (let value of map.values()) {
console.log(value);
}
// a
// b
// c
Set
let set = new Set(['a', 'b', 'c']);
for (let pair of set.entries()) {
console.log(pair);
}
// ['a', 'a']
// ['b', 'b']
// ['c', 'c']
for (let key of set.keys()) {
console.log(key);
}
// a
// b
// c
for (let value of set.values()) {
console.log(value);
}
// a
// b
// c
Set 类型的 keys() 和 values() 返回的是相同的迭代器,这也意味着在 Set 这种数据结构中键名与键值相同。
而且每个集合类型都有一个默认的迭代器,在 for-of 循环中,如果没有显式指定则使用默认的迭代器。数组和 Set 集合的默认迭代器是 values() 方法,Map 集合的默认迭代器是 entries() 方法。
Babel 是如何编译 for of 的
let set = new Set(['a', 'b', 'c']);
for (let s of set) {
console.log(s);
}
// a
// b
// c
可以在 Babel 的 Try it out 中查看编译的结果:
"use strict";
function _createForOfIteratorHelper(o, allowArrayLike) {
// 判断是否有Symbol.iterator属性
var it =
(typeof Symbol !== "undefined" && o[Symbol.iterator]) || o["@@iterator"];
if (!it) {
if (
Array.isArray(o) ||
(it = _unsupportedIterableToArray(o)) ||
(allowArrayLike && o && typeof o.length === "number")
) {
if (it) o = it;
var i = 0;
var F = function F() {};
return {
s: F,
n: function n() {
if (i >= o.length)
return {
done: true,
};
return {
done: false,
value: o[i++],
};
},
e: function e(_e) {
throw _e;
},
f: F,
};
}
throw new TypeError(
"Invalid attempt to iterate non-iterable instance.nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."
);
}
var normalCompletion = true,
didErr = false,
err;
return {
s: function s() {
it = it.call(o);
},
n: function n() {
var step = it.next();
normalCompletion = step.done;
return step;
},
e: function e(_e2) {
didErr = true;
err = _e2;
},
f: function f() {
try {
// 没有正常的遍历完成,并且遍历器有 return 方法时,就会执行该方法
if (!normalCompletion && it["return"] != null) it["return"]();
} finally {
if (didErr) throw err;
}
},
};
}
// 转成数组
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === "Object" && o.constructor) n = o.constructor.name;
if (n === "Map" || n === "Set") return Array.from(o);
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))
return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) {
arr2[i] = arr[i];
}
return arr2;
}
var colors = new Set(["red", "green", "blue"]);
var _iterator = _createForOfIteratorHelper(colors),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done; ) {
var color = _step.value;
console.log(color);
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
可以看出使用 for of
循环的背后,还是会使用 Symbol.iterator 接口
遍历器对象除了具有
next()
方法,还可以具有return()
方法和throw()
方法。如果你自己写遍历器对象生成函数,那么next()
方法是必须部署的,return()
方法和throw()
方法是否部署是可选的。
return()
方法的使用场合是,如果for...of
循环提前退出(通常是因为出错,或者有break
语句),就会调用return()
方法。如果一个对象在完成遍历前,需要清理或释放资源,就可以部署return()
方法。
return()
方法必须返回一个对象,这是 Generator 语法决定的。
throw()
方法主要是配合 Generator 函数使用,一般的遍历器对象用不到这个方法。
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 1;
}
};
}
var colors = ["red", "green", "blue"];
var iterator = createIterator([1, 2, 3]);
colors[Symbol.iterator] = function() {
return iterator;
};
for (let color of colors) {
if (color == 2) break;
console.log(color);
}
// 执行了 return 方法
可以看出return()
方法必须返回一个对象,不然会报错
参考资料:
ES6 系列之迭代器与 for of · Issue #90 · mqyqingfeng/Blog · GitHub
阮一峰- ES6-Iterator 和 for...of 循环
最后
以上就是虚拟手套为你收集整理的ES6 Iterator 和 for...of 循环的全部内容,希望文章能够帮你解决ES6 Iterator 和 for...of 循环所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复