JS前端必需知道的对象遍历,你记住了吗?
highlight: a11y-dark theme: channing-cyan
方法总览
对象的遍历方法
一、for...in
定义
- 遍历对象自身的和继承的可枚举的属性(不含Symbol属性)的键名。
- 由于对象的属性没有顺序,因此for...in每个属性都会返回一次,但输出的属性顺序不可预测。
语法:
js
for (var key in obj) {
// 执行代码块
console.log(obj[key])
}
这里的var操作符不是必需,但为了保证局部变量,推荐使用上面的写法
代码解析
```js function Person (name, age) { this.name = name this.age = age this.sayHello = function () { console.log('Hello'); } }
Person.prototype.addr = 'The Earth' Person.prototype.getNum = function() { console.log(1); }
const tom = new Person('Tom', 18, 'tomato') console.log(tom); // { name: 'Tom', age: 18, sayHello: [Function] } console.log(tom.proto); // { addr: 'The Earth', getNum: [Function] }
// 会遍历自身和继承的属性和方法 for (var key in tom) { console.log(key + ':' + tom[key]); / name:Tom, age:18, sayHello:function(){...} , addr:The Earth, getNum:function() {...} / } ```
for...in用于遍历数组
遍历数组的缺点: - 数组的键名是数字,但是for...in循环是以字符串作为键名“0”、“1”、“2”等等。 - for...in循环不仅遍历数字键名,还会遍历手动添加的其他键,甚至包括原型链上的键。 - 某些情况下,for...in循环会以任意顺序遍历键名。 - for...in循环主要是为遍历对象而设计的,不适用于遍历数组 ```js const colors = ['red', 'blue', 'yellow'] colors.name = 'overcast' for (var key in colors) { // 以字符串作为键名 console.log(key); // '0', '1', '2', name }
for (var v of colors) { console.log(v); // red, blue, yellow } ```
使用for...in进行深拷贝
```js function deepClone (obj = {}) { let result if (typeof obj !== 'object' || obj === null) { return obj }
// 判断是对象还是数组 if (obj instanceof Array) { result = [] } else { result = {} }
for (let key in obj) { // 保证key不是原型上的属性 if (obj.hasOwnProperty(key)) { // 递归调用 result[key] = deepClone(obj[key]) } }
return result }
const obj1 = { name: 'tom', colors: ['red', 'blue'], numbers: { language: 96, mathematics: 100 } }
const obj2 = deepClone(obj1) obj2.name = 'jane' obj2.colors = ['pink'] obj2.numbers.language = 100
console.log(obj1); // {name: 'tom', colors: Array(2), numbers: {language: 96, mathematics: 100}} console.log(obj2); // {name: 'jane', colors: Array(1), {language: 100, mathematics: 98}} ```
Object.keys(),Object.values(),Object.entries()
定义
Object.keys(),Object.values(),Object.entries()均返回一个数组,区别是数组成员不一样(都不含Symbol属性)。
-
Object.keys()
- 返回一个数组
- 数组成员是参数对象自身的(不含继承的)所有可枚举(enumerable)属性的键名
-
Object.values()
- 返回一个数组
- 数组成员是参数对象自身的(不含继承的)所有可枚举(enumerable)属性的键值
-
Object.entries()
- 返回一个数组
- 数组成员是参数对象自身的(不含继承的)所有可枚举(enumerable)属性的键值对数组。
语法
- Object.keys(obj)
- Object.values(obj)
- Object.entries(obj) ```js // 数组不包含symbol属性 var data = { name: 'tim', age: 18, [Symbol()]: 123 }; console.log(Object.keys(data)); // [ 'name', 'age' ] console.log(Object.values(data)); // [ 'tim', 18 ] console.log(Object.entries(data)); // [ [ 'name', 'tim' ], [ 'age', 18 ] ]
// 参数是字符串时 console.log(Object.keys('foo')); // [ '0', '1', '2' ] console.log(Object.values('foo')); // [ 'f', 'o', 'o' ] console.log(Object.entries('foo')); // [ [ '0', 'f' ], [ '1', 'o' ], [ '2', 'o' ] ] ```
结合结合数组的方法遍历对象属性
- for...of
```js let obj = { a: 1, b: 2, c: 3 };
for (let key of Object.keys(obj)) { console.log(key); // 'a', 'b', 'c' }
for (let value of Object.values(obj)) { console.log(value); // 1, 2, 3 }
for (let [key, value] of Object.entries(obj)) { console.log([key, value]); // ['a', 1], ['b', 2], ['c', 3] } ```
- forEach, filter, map, findIndex, every, some...
js
// forEach
Object.keys(obj).forEach(key => {
console.log(key + ':' + obj[key]); // a:1, b:2, c:3
})
Object.values(obj).forEach((value) => {
console.log(value); // 1, 2, 3
})
Object.entries(obj).forEach(([k, v]) => {
console.log(k + ':' + v); // a:1, b:2, c:3
})
Object.entries将对象转为真正的Map
结构
js
const obj = { foo: 'bar', baz: 42 };
const map = new Map(Object.entries(obj));
console.log(map); // Map { foo: "bar", baz: 42 }
Object.getOwnPropertyNames(obj)
定义
- 返回一个数组
- 数组成员包含对象自身的所有属性(不含Symbol属性,但是包括不可枚举属性)的键名。
语法
js
Object.getOwnPropertyNames(obj)
代码解析
```js const obj = { foo: 'bar', baz: 42 }; console.log(Object.getOwnPropertyNames(obj)); // [ 'foo', 'baz' ]
const obj1 = { a: 1, b: 2, [Symbol()]: 123 }; console.log(Object.getOwnPropertyNames(obj1)); // [ 'a', 'b' ]
let obj2 = Object.create({}, { num: { value: 42, enumerable: false // 不可枚举 } })
console.log(Object.values(obj2)); // [] console.log(Object.getOwnPropertyNames(obj2)); // [ 'num' ] ```
遍历
Object.getOwnPropertyNames(obj)返回的数组便可以用数组的方法进行遍历
js
var obj = { 0: "a", 1: "b", 2: "c"};
Object.getOwnPropertyNames(obj).forEach(function(val, idx, array) {
console.log(val + " -> " + obj[val]); // 0 -> a, 1 -> b, 2 -> c
});
Object.getOwnPropertySymbols(obj)
定义
- 返回一个数组
- 数组成员包含对象自身的所有Symbol属性的键名。
语法
js
Object.getOwnPropertySymbols(obj)
代码解析
只返回对象的自身的symbol属性,不返回普通属性 ```js const obj = { a: 'Hello' }; let b = Symbol('a'); objSymbol('b') = 'World';
console.log(Object.getOwnPropertySymbols(obj)); // [ Symbol(b) ] ```
与for...in
循环、Object.getOwnPropertyNames
比较
```js const obj1 = { [Symbol('foo')]: 'bar' }; const obj2 = { a: 1 }
for (let i in obj1) { console.log(i); // 无输出 } console.log(Object.getOwnPropertyNames(obj1)); // [] console.log(Object.getOwnPropertySymbols(obj1)); // [Symbol(foo)] console.log(Object.getOwnPropertySymbols(obj2)); // [] ```
遍历对象
```js const obj = {
}
Object.getOwnPropertySymbols(obj).forEach(key => { console.log(key, ':', obj[key]); // Symbol(a) : 1, Symbol(b) : 2, Symbol(c) : 3 })
```
Reflect.ownKeys(obj)
定义
- 返回一个数组
- 数组成员包含对象自身的(不含继承的)所有键名,不管键名是Symbol或字符串,也不管是否可枚举。
- 基本为
Object.getOwnPropertyNames
和Object.getOwnPropertySymbols
之和。
语法
js
Reflect.ownKeys(obj)
代码解析
```js const obj = { a: 1,
}
Object.defineProperty(obj, 'd', { value: 4, enumerable: false })
console.log(obj); // { a: 1, [Symbol(b)]: 2, [Symbol(c)]: 3 }
console.log(Object.keys(obj)); // ['a'] - 不包含不可枚举属性 console.log(Object.getOwnPropertyNames(obj)); // [ 'a', 'd' ] - 包含不可枚举属性 console.log(Object.getOwnPropertySymbols(obj)); // [ Symbol(b), Symbol(c) ] console.log(Reflect.ownKeys(obj)); // [ 'a', 'd', Symbol(b), Symbol(c) ] ```
其他
对象属性遍历的次序规则
以上遍历对象的方法都遵循同一个规则: - 首先遍历所有数值键,按照数值升序排列。 - 其次遍历所有字符串键,按照加入时间升序排列。 - 最后遍历所有 Symbol 键,按照加入时间升序排列。
js
Reflect.ownKeys({ [Symbol()]:0, b: 0, 10: 0, 2: 0, a:0 })
// ['2', '10', 'b', 'a', Symbol()]
注意事项
- ES6 规定,所有 Class 的原型的方法都是不可枚举的。
- 操作中引入继承的属性会让问题复杂化,大多数时候,我们只关心对象自身的属性。所以,尽量不要用
for...in
循环,而用Object.keys()
代替。