本文已参与「新人创作礼」活动,一起开启掘金创作之路。
什么是迭代器
迭代器(iterator)是确使用户可在容器对象(container,如:链表或数组)上遍历的对象,使用该接口无需关系对象的内部实现细节:
- 其行为像数据库中的光标,迭代器最早出现在1974年设计的CLU编程语言中;
- 在各种编程语言实现中,迭代器的实现方法各不相同,但基本都有迭代器,比如:Java,Python等;
从迭代器的定义中我们可以看出:迭代器是帮助我们对某个数据结构进行遍历的对象。
在JavaScript中,迭代器也是一个具体的对象,这个对象需要符合迭代器协议(iterator protocol):
- 迭代器协议定义了产生一系列值(有限个或无限个)的标准方式;
- 在JS中这个标准就是迭代器对象中需要有一个特点的next方法;
next方法有如下的要求:
- 它是一个无参或者一个参数的函数,并且返回一个拥有done和value属性的对象;
- 对于done属性:
-
- 它是一个boolean类型的值;
- 如果迭代器可以产生序列中的下一个值,则为false( 这等价于没有指定 done 这个属性 );
- 如果迭代器已将序列迭代完毕,则为 true。这种情况下,value 是可选的,如果它依然存在,即为迭代结束之后的默认返回值;
- 对于value属性:
-
- 迭代器返回的任何 JavaScript 值。done 为 true 时可省略;
迭代器的代码练习
下面,我们有一个names的数组对象,我们来实现这个对象的迭代器:
const names = ['lzh', 'cjh', 'cks']
let index = 0
const namesIterator = {
next: function() {
if (index < names.length) {
return { done: false, value: names[index++] }
} else {
return { done: true, value: undefined }
}
}
}
console.log(namesIterator.next()); // { done: false, value: 'lzh' }
console.log(namesIterator.next()); // { done: false, value: 'cjh' }
console.log(namesIterator.next()); // { done: false, value: 'cks' }
console.log(namesIterator.next()); // { done: true, value: undefined }
console.log(namesIterator.next()); // { done: true, value: undefined }
console.log(namesIterator.next()); // { done: true, value: undefined }
那么,如果我们希望对于每一个数组对象,都能够生成对应的迭代器呢?
我们可以将上面创建数组迭代器的代码封装成一个函数:
function createArrayIterator(arr) {
let index = 0;
return {
next: function () {
if (index < arr.length) {
return {
done: false,
value: arr[index++],
};
} else {
return {
done: true,
value: undefined,
};
}
},
};
}
const names = ["lzh", "cjh", "cks"];
const leasons = ["js", "vue", "react"];
const namesIterator = createArrayIterator(names);
const leasonsIterator = createArrayIterator(leasons);
可迭代对象
它和迭代器是不同的概念:
- 当一个对象实现了iterable protocol(可迭代协议)时,它就是一个可迭代对象;
- 这个对象的要求是必须实现@@iterator方法,在JS代码中我们使用
Symbol.iterator访问该属性; - 当一个对象变成一个可迭代对象时,它可以进行某些迭代操作,比如:for...of操作其实就会调用它的@@iterator方法;
可迭代对象的代码
const iterableObj = {
names: ["lzh", "cjh", "cks"],
[Symbol.iterator]() {
let index = 0;
return {
next: () => {
if (index < this.names.length) {
return { done: false, value: this.names[index++] };
} else {
return { done: true, value: undefined };
}
},
};
},
};
const namesIterator = iterableObj[Symbol.iterator]();
console.log(namesIterator.next()); // { done: false, value: 'lzh' }
console.log(namesIterator.next()); // { done: false, value: 'cjh' }
console.log(namesIterator.next()); // { done: false, value: 'cks' }
console.log(namesIterator.next()); // { done: true, value: undefined }
for (const item of iterableObj) {
console.log(item);// lzh cjh cks
}
原生可迭代对象
事实上我们平时创建的很多原生对象已经实现了可迭代协议,会生成一个迭代器对象的:String、Array、Map、Set、arguments对象、NodeList集合;
// 1.数组
const names = ["lzh", "cks", "wws"];
for (const name of names) {
console.log(name);
}
// 2.Map/Set
const map = new Map([
["java", 111],
["js", 222],
]);
for (const item of map) {
console.log(item); // ['java', 111] ['js', 222]
}
const set = new Set([111, 222]);
for (const item of set) {
console.log(item); // 111 222
}
// 3.argument
function foo() {
for (const argument of arguments) {
console.log(argument);
}
}
foo(1, 2, 3, 4, 5);
可迭代对象的应用
可迭代对象可以被用在哪里呢?
- JavaScript中的语法:for...of,展开语法(spread syntax),yield*,解构赋值(destructuring assignment);
- 创建一些对象时:new Map([Iterable]),new WeakMap([Iterable]),new Set([Iterable]),new WeakSet([Iterable]);
- 一些方法的调用:Promise.all(iterable),Promise.race(iterable),Array.from(iterable);
const iterableObj = {
names: ["lzh", "cjh", "cks"],
[Symbol.iterator]() {
let index = 0;
return {
next: () => {
if (index < this.names.length) {
return { done: false, value: this.names[index++] };
} else {
return { done: true, value: undefined };
}
},
};
},
};
// 1.for...of
// 2.展开语法
const newArr = [...iterableObj];
console.log(newArr); // [ 'lzh', 'cjh', 'cks' ]
// 3.Promise.all/allSettled/race/any方法传参时要求是个可迭代对象
Promise.all(iterableObj).then((value) => {
console.log(value); // [ 'lzh', 'cjh', 'cks' ]
});
// 4.Set构造函数
const set = new Set(iterableObj);
console.log(set); // Set(3) { 'lzh', 'cjh', 'cks' }
// 5.解构赋值
const [name1, name2, name3] = [...iterableObj];
console.log(name1, name2, name3); // lzh cjh cks
注意
- 普通对象的展开语法使用的并不是迭代器,而是ES9(ES2018)中新增的特性;
- 普通对象的解构赋值使用的并不是迭代器,而是ES9(ES2018)中新增的特性;
自定义类的迭代
在前面我们看到Array、Set、String、Map等类创建出来的对象都是可迭代对象;
在面向对象开发中,我们可以通过class定义一个自己的类,这个类可以创建很多的对象;
如果我们也希望自己的类创建出来的对象默认是可迭代的,那么在设计类的时候我们就可以添加上 @@iterator 方法;
案例
创建一个Classroom的类:
- 教室有自己的位置,名称,当前教室中的学生,这个教室可以进来(entry)新学生 ;
- 创建的教室对象是可迭代对象,能够遍历教室中的每个学生;
class ClassRoom {
constructor(address, name) {
this.address = address;
this.name = name;
this.students = [];
}
entry(...students) {
this.students.push(...students);
}
[Symbol.iterator]() {
let index = 0;
return {
next: () => {
if (index < this.students.length) {
return { done: false, value: this.students[index++] };
} else {
return { done: true, value: undefined };
}
},
};
}
}
const classroom = new ClassRoom("广州市天河区", "js高级课程");
classroom.entry("111", "222", "333");
for (const student of classroom) {
console.log(student);
}
迭代器的中断
迭代器在某些情况下会在没有完全迭代的情况下中断:
- 比如遍历的过程中通过break、continue、return、throw中断了循环操作;
- 比如在解构的时候,没有解构所有的值;
那么这个时候我们想要监听中断的话,可以添加return方法:
const iterableObj = {
names: ["cks", "wws", "cjh"],
[Symbol.iterator]() {
let index = 0;
return {
next: () => {
if (index < this.names.length) {
return { done: false, value: this.names[index++] };
} else {
return { done: true, value: undefined };
}
},
return() {
console.log("迭代中断了");
return { done: true };
},
};
},
};
for (const name of iterableObj) {
console.log(name);
if (name === "wws") {
break;
}
}