超级面板
文章目录
最新文章
最近更新
文章分类
标签列表
文章归档

ES6 - Iterator 与 for...of

概述

Iterator 接口允许为所有数据结构,提供了一种统一的遍历机制,被 for...of 使用这种机制遍历这些数据结构, 一些内置类型都是内置的可遍历对象并且有默认的迭代行为, 比如 Array , 另一些类型则不是, 比如 Object

为了变成可遍历解构,对象或其原型链上某个对象必须实现 @@iterator 方法,即属性名为 Symbol.iterator

当该对象需要被遍历的时候(比如用于一个 for..of 循环中),它的 @@iterator 方法被调用并且无参数,然后返回一个用于在遍历中获得值的迭代器,具体过程如下:

  • 创建一个指针对象,指向当前数据结构的起始位置;
  • 第一次调用该指针对象的 next 方法,指向数据结构的第一个成员,返回结果是一个包含 valuedone 两个属性的对象。
  • value 字段表示当前遍历的成员的值。
  • 如果 done 的值为 true ,则遍历结束,否则,done 的值为 false ,下次会继续遍历下一个成员。

可以看一下数组的默认遍历器

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

iter.next(); // Object {value: "a", done: false}
iter.next(); // Object {value: "b", done: false}
iter.next(); // Object {value: undefined, done: true}
  • 第 1 次调用 iter.next() ,返回数组的第1个元素 valuea,以及 done 的值为 fasle ,表示遍历没有结束,还可以继续遍历。
  • 第 2 次调用 iter.next() ,返回数组的第2个元素 valueb,以及 done 的值还是为 fasle ,表示遍历没有结束,还可以继续遍历。
  • 第 3 次调用 iter.next() ,返回的 valueundefineddone 的值为 true ,遍历结束。

了解了这些,就可以创建一个可遍历的对象,并定义它的遍历行为:

let obj = {
data: ['a', 'b'],
[Symbol.iterator]() {
const _self = this;
let _curr = 0;
return {
next() {
if (_curr < _self.data.length) {
return {
value: _self.data[_curr ++],
done: false
};
} else {
return { value: undefined, done: true };
}
}
};
}
};

for (var value of obj) {
console.log(value) // a b
}

Generate

如果了解 Generate ,就会发现这个和 Generate 的结构非常相似,那么遍历函数可以使用 Generate 吗?

答案是可以,接下来使用 Generate 函数改写上面的例子:

let obj = {
data: ['a', 'b'],
[Symbol.iterator]: function * () {
for (let index = 0; index < this.data.length; index ++) {
yield this.data[index];
}
}
};

for (var value of obj) {
console.log(value) // a b
}

使用 Generate 更加节省代码,而且流程也更加清晰,更容易理解。

内置支持迭代的对象

StringArrayTypedArrayMapSet 都内置迭代器, 因为它们的原型对象都有一个 @@iterator 方法:

var string = "hello";
for (var chr of string) {
console.log(chr); // h, e, l, l, o
}

支持迭代操作的语句、表达式和方法

除了内置迭代器的对象,还有一些场合下也会使用迭代器:

解构赋值

对数组和Set结构进行执行解构操作的时候,会调用对象的迭代器:

let array = ["a", "b", "c"];
[a, ...others] = array
a // "a"
others // ["b", "c"]

扩展运算符

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

let hello = "hello";
[...hello] // ["h", "e", "l", "l", "o"]

let array = ["a", "b", "c"];
[...array, "d"]; // ["a", "b", "c", "d"]

对象默认不支持迭代器,因此对象的解构赋值和扩展运算符并不是使用迭代器的:

var {a, ...others} = {a: 1, b: 2, c: 3}

// 等价于:
function _objectWithoutProperties(obj, keys) {
var target = {};
for (var i in obj) {
if (keys.indexOf(i) >= 0)
continue;
if (!Object.prototype.hasOwnProperty.call(obj, i))
continue;
target[i] = obj[i];
}

return target;
}

var obj = { a: 1, b: 2, c: 3 };
var a = obj.a,
others = _objectWithoutProperties(obj, ["a"]);

for..of 循环

前文已说明,不再多言

常见的类数组对象

常见的一些类数组对象,如 argumentsNodeList 等也支持迭代器:

(function test() {
for (let v in arguments) {
console.log(v);
}
})(1, 2, 3)
// 1 2 3

但并不是所有的类数组对象都支持迭代器,所以在使用的时候,可以先将其转换为数组:

var arrLike = {
0: 0,
1: 1,
length: 2
}

for (let val of arrLike) {
console.log(val);
}
// Uncaught TypeError: undefined is not a function

for (let val of Array.from(arrLike)) {
console.log(val);
}
// 0 1

Generators

generator函数自身就是一个迭代器,当然也可以使用迭代器:

function * list() {
yield 1;
yield 2;
yield 3;
}

for (var v in list()) {
console.log(v); // 1 2 3
}

接受数组或可迭代对象作为参数

任何接受数组作为参数的场合,其实都调用了遍历器接口,如 Array.from()Map() , Set() , WeakMap() , WeakSet()Promise.all()Promise.race()

var arr = [1, 2, 3];
// 修改数组的迭代器,让返回结果都 *10
arr[Symbol.iterator] = function() {
return {
next: function() {
if (this._curr < arr.length) {
return { value: arr[this._curr ++] * 10, done: false };
} else {
return { done: true };
}
},
_curr: 0,
};
};

for (let value of arr) {
console.log(value) // 10, 20, 30
}

Array.from(arr) // [10, 20, 30]

new Set(arr) // {10, 20, 30}

arr.map(item => item) // [1, 2, 3]

for 循环、for…in 以及 forEach等数组函数遍历对比

在有些应用场景下,这些遍历方式可以通用,这个根据实际的应用场景选择即可。

for 循环

for 语句遍历就一个特点,写起来麻烦,应用场景到时最普遍的。for 语句遍历对象的时候,如果遍历对象需要搭配其他函数,如 Object.keys(object) ,获得对象的键值 keys 的数组

for..in

for..in 可以遍历对象和数组对象,但有一些情况需要注意:

  1. index 的索引为字符串的数字,不能直接进行数值的运算:
var arr = [1, 2, 3];
for (var index in arr) {
console.log(typeof index); // string
}
  1. for...in 会遍历对象上的所有可枚举属性,包括原型方和原型属性:
var obj = {
a: 1
}
obj.__proto__ = {
b: 2
}

for (var key in obj) {
console.log(key)
}

// a
// b

如果不想遍历原型方法和原型属性的话可以使用 hasOwnPropery 做一个判断:

for (var key in obj) {
  if (obj.hasOwnProperty(key)){
    console.log(key);
  }
}

// a

也可以使用 Object.keys(object) 获取对象自身的实例属性组成的数组:

var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i ++) {
  console.log(keys[i]);
}

// a

forEach 等数组函数

数组上也有一些方法支持遍历数组,比如 forEachmapfiltereveryreduce 等函数,这些函数都有着不同的用途,但是最主要的问题在于他们遍历数组的操作是在回调函数中执行、不能使用 continuebreak 中断遍历,而且不能直接 return 返回到外部。

参考