文章目录
- 一、认识迭代器
- 二、为类数组添加迭代器方法
- 三、为colorObj对象添加迭代器方法
- 四、优化colorObj代码
- 五、ES6内置迭代对象方法
这里有一个普通对象:
const colorObj = {
white: "小白",
black: "小黑",
gray: "小灰",
}
如何对 colorObj 对象进行for...of
迭代获取其中的值呢?
for (const value of colorObj) {
console.log(value);
}
// Uncaught TypeError: colorObj is not iterable
如果直接使用for...of
迭代对象,会像下面这样报错:
一、认识迭代器
这里就需要具备迭代器 Iterator 的知识了。那么,什么是迭代器呢?我们先来看一下迭代的定义:
迭代就是指可以从一个数据集中按照一定的顺序,不断取出数据的过程。
再来说一说迭代器:
在JavaScript中,迭代器是能调用 next 方法实现迭代的一个对象,该方法返回一个具有两个属性的对象。
- value:可迭代对象的下一个值
- done:表示是否已经取出所有的数据了。 false表示还有数据, true表示已经取出所有数据
const arr = [1, 2, 3];
let arrIterator = arr[Symbol.iterator](); // 调用数组的迭代器方法
arrIterator.next(); // 执行迭代器的next方法
--> { value: 1, done: false }
arrIterator.next();
--> { value: 2, done: false }
arrIterator.next();
--> { value: 3, done: false }
arrIterator.next();
--> { value: undefined, done: true }
原生具备迭代器接口的数据结构有:Array、String、Set 和 Map,特殊类数组对象:arguments、NodeList 等。
此处列举 Set 的 Iterator 接口:
对象不可迭代,因为它没有指定 Iterator 接口。可以为对象添加迭代方法,对象才可迭代。才可供for…of消费。
Iterator 的作用有三个:
- 为各种数据结构,提供一个统一的、简便的访问接口;
- 使得数据结构的成员能够按某种次序排列;
- ES6 创造了一种新的遍历命令 for…of 循环,Iterator 接口主要供 for…of 消费。
二、为类数组添加迭代器方法
先来看一个简单的类数组对象:
const arrayLike= {
0: "小白",
1: "小黑",
2: "小灰",
length: 3
}
如上代码所示,类数组需要满足两个条件:
- 具备length属性;
- key必须是一组有序的数字;
现在我们想迭代当前的类数组对象 arrayLike,必须为其添加 iterator 接口:
// 添加迭代器方法 Symbol.iterator
arrayLike[Symbol.iterator] = function () {
let index = 0;
return { // 返回一个对象,里面带有next方法,执行next方法,返回一个带有value与done属性的对象
next: () => {
if (index < this.length) { // 判断是否还存在值,存在值则返回值,否则返回 { value: undefined, done: true }
const result = { value: this[index], done: false };
index++; // index自增,查找下一个数据
return result;
} else {
return { value: undefined, done: true };
}
}
}
}
上面代码为 arrayLike 对象添加 Symbol.iterator 方法,再使用for...of
迭代,就可以按顺序取出value值了。
console.log("=========================");
for (const value of arrayLike) {
console.log(value);
}
console.log("=========================");
三、为colorObj对象添加迭代器方法
回到开头我们所提的问题,在 colorObj 对象中,既没有 length 属性,对象的key也不是一组有序的数字,这时候我们如何像类数组一样为其添加 Iterator 接口呢?
const colorObj= {
white: "小白",
black: "小黑",
gray: "小灰",
}
很简单,我们伪装它的length属性,并且按顺序排列它的key就可以了。
colorObj[Symbol.iterator] = function () {
let keys = []; // 对象中key的集合
let length = 0; // 伪装length属性
for (const key in this) {
if (Object.hasOwnProperty.call(this, key)) { // 防止 for...in 遍历原型对象的属性
keys.push(key);
length++;
}
}
let index = 0;
return {
next: () => {
if (index < length) {
const result = { value: this[keys[index]], done: false };
index++;
return result;
} else {
return { value: undefined, done: true };
}
}
}
}
console.log("++++++++++++++++++++++++++++");
for (const value of colorObj) {
console.log(value);
}
console.log("++++++++++++++++++++++++++++");
到这里就完成了对象的迭代了。
但现在还不算完,上面的代码还有优化空间,不知道你有没有听说过生成器 Generator 。
生成器 Generator ,可以将其理解成一个状态机,封装了多个内部状态。执行 Generator 函数会返回一个迭代器对象 Iterator,也就是说,Generator 函数除了状态机,还是一个迭代器对象生成函数。
形式上,Generator 函数是一个普通函数,但是有两个特征:
- function关键字与函数名之间有一个星号 *;
- 函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。
function* hello() {
yield 'hello';
yield 'world';
yield 'ending'; // 使用return时,代表结束done:true;即使后面还存在值,也不返回。
}
var hw = hello();
console.log(hw.next());
console.log(hw.next());
console.log(hw.next());
console.log(hw.next());
四、优化colorObj代码
colorObj[Symbol.iterator] = function* () {
let keys = [];
let length = 0;
for (const key in this) {
if (Object.hasOwnProperty.call(this, key)) {
keys.push(key);
length++;
}
}
let index = 0;
while (index < length) {
yield this[keys[index]]; // 生成器自动包装返回的值,且yield语句可以暂停while循环语句
index++;
}
}
let iterator = colorObj[Symbol.iterator](); // 执行iterator方法,生成迭代器对象
console.log(iterator.next()); // 执行迭代器对象的next方法
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log("----------------------------");
for (const value of colorObj) {
console.log(value);
}
console.log("----------------------------");
五、ES6内置迭代对象方法
最后,ES6中内置了迭代对象的方法:
Object.keys(); // 迭代对象的 key
Object.values(); // 迭代对象的 value
Object.entries(); // 迭代对象的 [key, value]
以 colorObj 为例:
const colorObj= {
white: "小白",
black: "小黑",
gray: "小灰",
}
console.log("Object.keys", Object.keys(colorObj));
console.log("Object.values", Object.values(colorObj));
console.log("Object.entries", Object.entries(colorObj));
使用遍历命令for...of
迭代对象:
for (const key of Object.keys(colorObj)) {
console.log(key);
}
console.log("*******************************");
for (const value of Object.values(colorObj)) {
console.log(value);
}
console.log("*******************************");
for (const [key, value] of Object.entries(colorObj)) {
console.log(`${key}: ${value}`);
}
参考文章:
循环、递归、遍历、迭代四者的概念区别
可枚举属性、可迭代对象、迭代器(iterator)、for-in、for-of
JS的生成器详细使用、生成器结合迭代器使用
JavaScript之迭代器
es6:如何将一个普通对象转化为可迭代的对象