JS中如何将一个普通对象转换为可迭代对象,并且可使用for...of迭代对象,内含手写一个简单的迭代器

这里有一个普通对象:

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 }

原生具备迭代器接口的数据结构有:ArrayStringSetMap,特殊类数组对象:arguments、NodeList 等。

此处列举 Set 的 Iterator 接口:

在这里插入图片描述

对象不可迭代,因为它没有指定 Iterator 接口。可以为对象添加迭代方法,对象才可迭代,才可供for…of消费。

在这里插入图片描述

Iterator 的作用有三个:

  1. 为各种数据结构,提供一个统一的、简便的访问接口;
  2. 使得数据结构的成员能够按某种次序排列;
  3. ES6 创造了一种新的遍历命令 for…of 循环,Iterator 接口主要供 for…of 消费。

二、为类数组添加迭代器方法

先来看一个简单的类数组对象:

const arrayLike= {
    
    
	0: "小白",
	1: "小黑",
	2: "小灰",
	length: 3
}

如上代码所示,类数组需要满足两个条件:

  1. 具备length属性。
  2. 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 colorObj) {
    
    
    if (colorObj.hasOwnProperty(key)) {
    
      // 防止 for...in 遍历原型对象的属性
      keys.push(key);
      length++;
    }
  }
  let index = 0;
  return {
    
    
    next: () => {
    
    
      if (index < length) {
    
    
        const result = {
    
     value: colorObj[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 函数是一个普通函数,但是有两个特征:

  1. function关键字与函数名之间有一个星号 *;
  2. 函数体内部使用 yield 表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。
function* hello() {
    
    
  yield 'hello';
  yield 'world';
  yield 'ending';
  return "使用return语句"; // 使用return时,代表结束done:true;即使后面还存在值,也不返回。
  yield 'after return';
}
var hw = hello();

console.log(hw.next());
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) {
    
      // this就是当前的 colorObj 对象
    if (this.hasOwnProperty(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:如何将一个普通对象转化为可迭代的对象

猜你喜欢

转载自blog.csdn.net/ThisEqualThis/article/details/129158782