1. 概述
遍历器(Iterator
)是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator
接口,就可以完成遍历操作。
遍历器的作用主要有两个:
- 为各种数据结构,提供一个统一的、简便的访问接口,使得数据结构的成员能够按某种次序排列。
- 供
for...of
循环调用。
Iterator
的遍历过程是这样的:
- 创建一个指针对象,指向当前数据结构的起始位置。
- 第一次调用指针对象的
next
方法,可以将指针指向数据结构的第一个成员。 - 不断调用指针对象的
next
方法,直到它指向数据结构的结束位置。
每一次调用next
方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含value
和done
两个属性的对象。其中,value
属性是当前成员的值,done
属性是一个布尔值,表示遍历是否结束。
下面是一个模拟Iterator
遍历的例子。
function myIterator(item) {
var index = 0;
return { // 返回一个对象,具有next方法
next(){
if(index < item.length) {
return { value: item[index++], done: false };
}else{
return { value: undefined, done: true } ;
}
}
}
}
var it = myIterator(['a', 'b']);
it.next(); // {value: "a", done: false}
it.next(); // {value: "b", done: false}
it.next(); // {value: undefined, done: true}
2. 默认 Iterator 接口
ES6 规定,默认的 Iterator
接口部署在数据结构的Symbol.iterator
属性,即一个数据结构只要具有Symbol.iterator
属性,就可以认为是“可遍历的”。Symbol.iterator
属性本身是一个函数,就是当前数据结构默认的遍历器生成函数。执行这个函数,就会返回一个遍历器。
var obj = {
[Symbol.iterator]() {
return {
next() {
return { value: "jidi", done: true }
}
}
}
}
上面例子中,对象obj
就是可遍历的,因为具有Symbol.iterator
属性,执行该属性,会返回一个遍历器对象。该对象的根本特征就是具有next
方法,每次调用next
方法都会返回一个代表当前成员的信息对象,该对象具有value
和done
两个属性。
ES6 的有些数据结构原生具备Iterator
接口:
- Array
- Map
- Set
- String
- TypedArray
- 函数的 arguments 对象
- NodeList 对象
var array = ["a", "b", "c"];
// 获取数组原生遍历器对象
var it = array[Symbol.iterator]();
it.next(); // {value: "a", done: false}
it.next(); // {value: "b", done: false}
it.next(); // {value: "c", done: false}
it.next(); // {value: undefined, done: true}
上面例子中,array
是一个数组,原生部署了Symbol.iterator
属性,通过该属性就可以拿到遍历器对象。
对于没有部署iterator
接口的其它数据结构,都需要自己在Symbol.iterator
属性上面部署,这样才会被for...of
循环遍历。
var person = {
name: "jidi",
age: 22,
[Symbol.iterator](){
const self = this;
let index = 0;
let array = Object.getOwnPropertyNames(self); // 存储对象属性名
return { // 返回一个遍历器对象
next() {
if(index < array.length){
return { value: self[array[index++]], done: false}
}else{
return { value: undefined, done: true }
}
}
}
}
}
let it = person[Symbol.iterator](); // 获取对象遍历器对象
it.next(); // {value: "jidi", done: false}
it.next(); // {value: 22, done: false}
it.next(); // {value: undefined, done: true}
// for of 循环遍历
for(let item of person) {
console.log(item);
}
// jidi
// 22
上面代码,为对象person
添加了Symbol.iterator
属性(函数),并且执行结果返回一个遍历器对象,就可以被for ... of
循环遍历。
对于类似数组的对象(存在数值键名和length
属性),部署 Iterator
接口,可以直接引用数组的 Iterator
接口。
let myArray = {
0: "jidi",
1: 22,
length: 2,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
}
for (let item of myArray){
console.info(item);
}
// jidi
// 22
上面代码中,定义了一个类似数组的对象myArray
,然后直接使用数组的iterator
接口作为自己的iterator
接口实现,就能被for ... of
循环遍历。
3. 调用 Iterator 接口的场合
有一些场合会默认调用 Iterator
接口(即Symbol.iterator
方法):
(1) 解构赋值
对数组和Set
结构进行解构赋值,会默认调用Symbol.iterator
方法。
let s1 = new Set(["a", "b", "c"]);
let [x, y, z] = s1;
x; // "a"
y; // "b"
z; // "c"
let [...reset] = s1;
reset; // ["a", "b", "c"]
(2)扩展运算符
扩展运算符(...
)也会调用默认的Iterator
接口。
var name = "jidi";
[...name]; // ["j", "i", "d", "i"]
实际上,只要部署了iterator
接口的数据结构,都可以使用扩展运算符快速转为数组。以上面自定义部署了iterator接口的person
对象为例。
[...person]; // ["jidi", 22]
(3)yield*
yield*
后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
let generator = function* () {
yield 1;
yield* [2,3,4];
yield 5;
};
var iterator = generator();
iterator.next() // { value: 1, done: false }
iterator.next() // { value: 2, done: false }
iterator.next() // { value: 3, done: false }
iterator.next() // { value: 4, done: false }
iterator.next() // { value: 5, done: false }
iterator.next() // { value: undefined, done: true }
(4)接受数组作为参数的场合
数组的遍历会调用遍历器接口,任何接受数组作为参数的场合,其实都调用了遍历器接口。
4. 遍历器对象的 return(),throw()
遍历器对象除了具有next
方法,还可以具有return
方法和throw
方法。自定义遍历器对象生成函数,next
方法是必须部署的,return
方法和throw
方法是否部署是可选的。
var person = {
name: "jidi",
age: 22,
[Symbol.iterator](){
const self = this;
let index = 0;
let array = Object.getOwnPropertyNames(self); // 存储对象属性名
return { // 返回一个遍历器对象
next() {
if(index < array.length){
return { value: self[array[index++]], done: false}
}else{
return { value: undefined, done: true }
}
},
return() {
console.log("调用了return方法......")
return { value: undefined, done: true }
}
}
}
}
下面两种情况,都会调用return
方法。
// 情况一
for (let item of person) {
if(item === "jidi"){
break;
}
}
// 调用了return方法......
// 情况二
for (let item of person) {
if(item === "jidi"){
throw new Error("异常!")
}
}
// 调用了return方法......
// Uncaught Error: 异常!
5. for…of 循环
ES6 引入了for...of
循环,作为遍历所有数据结构的统一的方法。
一个数据结构只要部署了Symbol.iterator
属性,就被视为具有 iterator
接口,就可以用for...of
循环遍历它的成员。即for...of
循环内部调用的是数据结构的Symbol.iterator
方法。
var person = {
name: "jidi",
age: 22
}
var value = Object.values(person);
for(let item of value){
console.log(item)
}
// jidi
// 22
6. 参考链接
本篇博文是我自己学习笔记,原文请参考:ECMAScript 6 入门
如有问题,请及时指出!
欢迎沟通交流,邮箱:jidi_jidi@163.com。