在 JavaScript
当中遍历对象方式有许多种,比如常见的 for-in
和 Object.keys(obj)
等等,但是对于它们之间的区别一直有些模糊,所以今天就抽些时间将 JavaScript
当中涉及到遍历对象的 API
整体的梳理了一遍,看看它们的使用方式和之间的区别,先来小小的总结一下,大致有以下几种
方法 | return | prototype | Symbol | 如果参数不是对象 |
---|---|---|---|---|
Object.keys(obj) |
返回所有可枚举属性 | 不包括 | 不含 | ES6 之前报错,之后强制转换为对象 |
for-in |
返回所有可枚举属性 | 包括 | 不含 | ES6 之前报错,之后强制转换为对象 |
Object.getOwnPropertyNames(obj) |
返回所有自身属性,也包括不可枚举属性,如果是数组,那么 length 属性也包含其中 |
不包括 | 不含 | ES6 之前报错,之后强制转换为对象 |
Reflect.ownKeys(obj) |
返回所有自身属性,不管是否可枚举,不管是不是 Symbol ,一律返回 |
包括 | 包含 | 报错 |
for-of |
返回当前对象上的每一个属性(迭代) | 不包括 | 不含 | 普通的对象不能直接使用 for-of ,否则会报错,必须部署了 iterator 接口才能使用,可以考虑使用 Object.entries(obj) |
通过上表可以发现,Reflect.ownKeys(obj)
这个方法很强大,因为它可以返回所有自身属性,不管是否可枚举,不管是不是 Symbol
,一律返回,下面我们就一个一个来看看它们之间的区别
Object.keys(obj)
Object.keys(obj)
方法返回一个表示给定对象的所有可枚举属性的字符串数组,注意这里是可枚举属性,它包括对象自身的所有可枚举属性,但是不包含继承而来的属性,也不包含 Symbol
属性,使用如下
1 | var arr = ['a', 'b', 'c'] |
它的参数如果不是一个对象,那么在 ES6
之前报错,之后会强制转换为对象,返回的是一个表示给定对象的所有可枚举属性的字符串数组(但不包括原型中的属性),这里有一个需要注意的地方,即使用 Object.keys(obj)
循环遍历对象时返回的顺序不一定正确,至于为什么会这样,可以参考 为什么 Object.keys 的返回值会自动排序 这篇文章
for-in
for-in
语句以任意顺序遍历一个对象的可枚举属性,对于每个不同的属性,语句都会被执行
1 | var c = Symbol('c') |
它将返回所有可枚举属性,包括原型中的属性,但是不包含 Symbol
属性,如果参数不是一个对象,在 ES6
之前报错,之后会强制转换为对象,for-in
循环只遍历『可枚举』属性,循环将迭代对象的所有可枚举属性和从它的构造函数的 prototype
继承而来的(包括被覆盖的内建属性,即包括原型中的属性),但是因为其返回值的顺序问题,它不应该被用来迭代一个下标顺序很重要的 Array
,同 Object.keys(obj)
一样,for-in
并不能够保证返回的是按一定顺序的索引,如果仅迭代自身的属性,而不是它的原型,可以使用
getOwnPropertyNames()
- 返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性)组成的数组
hasOwnProperty()
- 来确定某属性是否是对象本身的属性(一般使用这个来过滤)
propertyIsEnumerable()
- 返回一个布尔值,表明指定的属性名是否是当前对象可枚举的自身属性
Object.getOwnPropertyNames(obj)
Object.getOwnPropertyNames()
方法返回一个由指定对象的所有自身属性的属性名,『包括』不可枚举属性组成的数组,但不会获取原型链上的属性,如果参数不是一个对象,在 ES6
之前报错,之后会强制转换为对象
1 | var arr = ['a', 'b', 'c'] |
它会返回所有的自身属性,不包括原型中属性,不含 Symbol
属性,但是它与前两种方式不同的是,它会包括不可枚举的属性 length
,它枚举属性的顺序与通过 for-in
循环(或 Object.keys()
)迭代该对象属性时是一致的,如果只获取到可枚举属性,使用 Object.keys()
或用 for-in
循环(配合 hasOwnProperty()
)
Reflect.ownKeys
静态方法 Reflect.ownKeys()
返回一个由目标对象自身的属性键组成的数组,这个方法在之前也提到过了,十分强大,它会返回一个数组,里面包含了对象的所有自身属性,不管是否可枚举,不管是不是 Symbol
,一律返回,参数不是一个对象的话,那么会抛出一个错误
1 | Reflect.ownKeys({ z: 3, y: 2, x: 1 }) // [ 'z', 'y', 'x' ] |
它的返回值等同于 Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))
for-of
for-of
语句在可迭代对象(包括 Array
,Map
,Set
,string
,TypedArray
,arguments
对象等)上创建一个迭代循环,对每个不同属性的属性值,调用一个自定义的有执行语句的迭代挂钩
所谓的
TypedArray
,它描述的是一个底层的二进制数据缓存区的一个类似数组(array-like
)视图
1 | let iterable = [10, 20, 30] |
for-of
会返回当前对象上的每一个属性,遍历的是『当前对象』上的每一个属性值,不包括原型上的,但是并不适用于所有的 Object
,只能迭代出拥有 iterator
的对象,通过观察上面的例子我们可以发现,我们在其中分别使用的是数组跟字符串,这两者都是默认部署了 Iterator
接口,所以可以使用 for-of
来进行遍历,那么对于普通的对象会是什么情况呢,我们来看下面这个例子
1 | var c = Symbol('c') |
运行以后可以发现,对于普通的对象,for-of
不能直接使用,否则会报错,在这种情况下,我们可以使用 Object.entries()
来将我们的对象包裹一下即可(当然,你也可以手动的为对象添加 Iterator
接口),该方法返回一个给定对象『自身可枚举』属性的键值对数组,其排列与使用 for-in
循环遍历该对象时返回的顺序一致(区别在于 for-in
循环还会枚举原型链中的属性),因为返回的是数组,所以具有 Iterator
接口可以直接被 for-of
遍历
1 | for (let [key, value] of Object.entries(obj)) { |
Object.getOwnPropertySymbols()
最后我们再来看一个比较特殊的方法,因为在上面列举的一些 API
当中我们可以发现,其中有很多方法的输出结果都是不包括 Symbol
属性的,所以 JavaScript
提供了一个 Object.getOwnPropertySymbols()
方法,专门用来返回一个给定对象自身的所有 Symbol
属性的数组
但是有一点需要注意,所有的对象在初始化的时候不会包含任何的 Symbol
,除非你在对象上赋值了 Symbol
,否则 Object.getOwnPropertySymbols()
只会返回一个空的数组,我们还是使用 Reflect.ownKeys(target) 当中的例子来进行对比说明,如下
1 | let a = Symbol.for('a') |