遍历得到数组 Or Iterator 遍历器?

开发 前端
熟悉 ES 语法数据结构的朋友一定很清楚,原生对象数据结构并不支持 obj.keys()、obj.values()和 obj.entries() 方法,数组与 map、set 等数据结构才支持。但仍可以通过 Object.keys(obj)、Object.values(obj)、Object.entries(obj)获取原生对象中可遍历的属性组成数组类型数据结构。

[[433429]]

一、背景

故事的开头是这样的...

在遍历数组与对象属性时,对使用 obj.keys()、obj.values()和 obj.entries() 还是 Object.keys(obj)、Object.values(obj)、Object.entries(obj)方法产生了一些困惑。话不多说,先放问题:

需求:想要遍历一个对象,并获取遍历对象的属性值 实现:Object.keys()、Object.values() 和 Object.entries() 方法 问题:一不小心同数组的 entries(),keys()和 values() 方法混淆了~QAQ

二、keys()、values()、entries()遍历方法

熟悉 ES 语法数据结构的朋友一定很清楚,原生对象数据结构并不支持 obj.keys()、obj.values()和 obj.entries() 方法,数组与 map、set 等数据结构才支持。但仍可以通过 Object.keys(obj)、Object.values(obj)、Object.entries(obj)获取原生对象中可遍历的属性组成数组类型数据结构。

也就是说,keys()、values()和 entries() 方法有两种:

ES5-ES2017 相继引入 Object.keys 、Object.values 和 Object.entries 方法,返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名/键值/键值对,可以用 for...of 循环进行遍历;

ES6 提供 entries(),keys() 和 values() -- 可用于遍历数组/Map/Set 等类数组数据结构实例,返回一个(Iterator)遍历器对象,可以用 for...of 循环进行遍历。

注意这里又有两点区别:

两者调用语法不同,显而易见;

前者返回的是一个可迭代的对象,而后者返回的是一个真正的数组。

有没有被绕晕?那我们先来看第一个问题吧 -- 调用语法的不同

Q1: Object.keys 、Object.values 和 Object.entries 方法

为了区分这两种调用语法,我们必须得来回顾下原型链的相关知识。

因为这里的 entries(),keys()和 values() 方法正是是调用原型对象构造函数上的方法。如下图可以看到,对于一个普通对象,这三个方法在 Object 对象的[[prototype]]下的 constructor 中:

而对于一个数组结构来说,这三个方法可以在数组原型链中和原型链上层对象原型的 constructor 中同时找到:

即 Object.keys(arr)调用的是数组原型链顶层原型对象 constructor 的方法,而数组本身也支持的 arr.keys()方法,则是调用数组原型链上的方法。

即对象只支持前种调用方式,而数组同时支持这两种调用:

同时我们知道在 JavaScript 中,对象是所有复杂结构的基础。也正对应了其他复杂结构原型链的顶端是对象原型结构。现在应该能够知道为何普通对象不支持 obj.keys()、obj.values()和 obj.entries() 方法了,但到这里就不得不提出另一个疑问了:

Q2: 如何让一个对象支持 obj.keys()、obj.values()和 obj.entries() 方法呢?

理论上,我们是可以为一个对象构造任意方法,那么如何实现和数组一样的遍历方法呢?本质上这个方法是能够生成一个遍历器。

  1. let objE = { 
  2.   data: [ 'hello''world' ], 
  3.   keys: function() { 
  4.     const self = this; 
  5.     return { 
  6.       [Symbol.iterator]() { 
  7.         let index = 0; 
  8.         return { 
  9.           next() { 
  10.             if (index < self.data.length) { 
  11.               return { 
  12.                 value: self.data[index++], 
  13.                 done: false 
  14.               }; 
  15.             } 
  16.             return { value: undefined, done: true }; 
  17.           } 
  18.         }; 
  19.       } 
  20.     } 
  21.   } 
  22. }; 

上述,我们自己创建了一个 data 对象,并实现了它自己的 data.values() 方法。同时,我们依然可以对它调用 Object.values(data) 方法。

从上面的方法不难看出,我们在对象中通过添加 Symbol.iterator 手动构造了一个输出遍历器函数,关于遍历器的讨论我们在下一节讨论,现在先来讨论调用返回结果的区别。

Q3: 两种调用方法返回结果:遍历器与数组

1)第一种调用方法,根据定义可知:返回一个数组,数组成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名/键值/键值对。

敲重点!!!这三个方法只返回对象自身的可遍历属性,即属性描述对象的 enumerable 为 true。

我们可以通过 for ... in 循环来实现相同的遍历效果。

2)而第二种方法,返回一个遍历器:顾名思义,遍历器也可以满足循环遍历的需求。

本质上,遍历器的定义是一种接口,为各种不同的数据结构提供统一的访问机制。接下来就来了解下适用于不同数据结构的遍历器。

三、Iterator 遍历器

首先我们知道,目前主要有四种表示“集合”的数据结构:数组(Array)、对象(Object)、Map 和 Set,这里表示"集合"的对象例如 NodeList 集合类数组对象,而遍历器可以使我们遍历访问这些集合。

实际上,原生具备 Iterator 接口的数据结构包括 Array、Map、Set、String、TypedArray、函数的 arguments 对象和 NodeList 对象。

具体遍历器的概念可参考阮一峰老师 ES6 入门 Iterator 一章,已经十分详细清楚:

因此,Iterator 遍历器本质上为所有数据结构,提供了一种统一的访问机制,即 for...of 循环。

关于遍历,我们前面已经讲到了遍历对象属性,这里再提一嘴:

1. 遍历类数组对象/Array/Map/Set 等数组数据结构实例

当使用 for...of 循环遍历某种数据结构时,该循环会自动去寻找 Iterator 接口。一种数据结构只要部署了 Iterator 接口,我们就称这种数据结构是“可遍历的”(iterable)。ES6 规定,默认的 Iterator 接口部署在数据结构的 Symbol.iterator 属性,换句话说,一个数据结构只要具有 Symbol.iterator 属性,就可以认为是“可迭代/遍历的”(iterable)。

2. 获取对象可遍历属性

Object.keys 、Object.values 和 Object.entries 方法只返回对象自身的可遍历属性,通过属性描述对象的 enumerable 标识改对象属性是否可以遍历。同时因为普通对象 not iterable,即普通对象不具有 Symbol.iterator 属性,所以无法通过 for...of 循环直接遍历,否则会报错 Uncaught TypeError: obj is not iterable。

可见,数组及类数组的遍历(迭代)与普通对象中的提到的遍历是不同的,这分别取决于各自的 iterable 和 enumerable 属性。

3. for ... of

ES6 中引入 for...of 循环,很多时候用以替代 for...in 和 forEach() ,并支持新的迭代协议。for...of 语句在可迭代对象上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句。

那么终极问题:如何实现 Symbol.iterator 方法,使普通对象可被 for of 迭代?其实在 Q2 部分已经实现了。

尝试给普通对象实现一个 Symbol.iterator 接口:

  1. // 普通对象 
  2. const obj = { 
  3.   foo: 'value1'
  4.   bar: 'value2'
  5.   [Symbol.iterator]() { 
  6.     // 这里 Object.keys 不会获取到 Symbol.iterator 属性 
  7.     const keys = Object.keys(obj); // 得到一个数组 
  8.     let index = 0; 
  9.     return { 
  10.       next: () => { 
  11.         if (index < keys.length) { 
  12.           // 迭代结果 未结束 
  13.           return { 
  14.             value: this[keys[index++]], 
  15.             done: false 
  16.           }; 
  17.         } else { 
  18.           // 迭代结果 结束 
  19.           return { value: undefined, done: true }; 
  20.         } 
  21.       } 
  22.     }; 
  23.   } 
  24. for (const value of obj) { 
  25.   console.log(value); // value1 value2 
  26. }; 

for...of 循环内部调用的是数据结构的 Symbol.iterator 方法,for...of 循环可以使用的范围包括数组、Set 和 Map 结构、某些类似数组的对象(比如 arguments 对象、DOM NodeList 对象)、后文的 Generator 对象,以及字符串。

for...of 循环作为 ES6 新引入的一种循环,具有以下明显优势(按需使用):

有着同 for...in 一样的简洁语法,但是没有 for...in 那些缺点(无序,不适用于遍历数组)。

不同于 forEach 方法,它可以与 break、continue 和 return 配合使用。

提供了遍历所有数据结构的统一操作接口。

以上是我从 keys()、values()、entries() 遍历方法出发对遍历器产生的几点思考,如有不足之处,欢迎指正~~~

 

责任编辑:武晓燕 来源: 微医大前端技术
相关推荐

2009-11-17 15:07:16

PHP数组遍历

2009-11-16 16:23:10

PHP数组遍历

2021-03-29 12:01:00

遍历数组for循环

2019-07-25 10:08:05

JavaScript数组转换

2009-09-08 09:59:26

LINQ遍历多个数组

2009-11-17 15:00:19

PHP遍历数组

2024-04-25 07:54:46

遍历数组PythonFor循环

2022-09-07 11:52:48

forforEach前端

2023-11-07 10:10:36

设计模式元素

2020-06-30 10:37:55

JavaScript开发技术

2021-06-18 10:05:14

JavaScript数组遍历

2021-06-15 10:01:27

JavaScript数组遍历Entries

2021-02-05 23:08:10

JS代码循环

2021-01-11 07:51:16

DOM对象节点树

2021-08-02 10:01:09

Iterator接口Java项目开发

2009-09-16 17:21:53

LINQ遍历

2022-10-26 09:27:59

Python编程迭代器协议

2010-04-16 10:24:17

Oracle存储过程

2018-04-28 19:01:54

JavaScript数组Promise

2021-07-22 07:20:24

JS 遍历方法前端
点赞
收藏

51CTO技术栈公众号