ECMAScript 將對(duì)象的屬性分為兩種:數(shù)據(jù)屬性 和 訪問(wèn)器屬性。每一種屬性?xún)?nèi)部都有一些特性,這里我們只要關(guān)注對(duì)象屬性的[[Enumerable]]特性,可以理解為:是否可枚舉。
然后根據(jù)上下文環(huán)境的不同,我們又可以將屬性分為:原型屬性和實(shí)例屬性。原型屬性是定義在對(duì)象的原型(prototype)中的屬性,而實(shí)例屬性就是構(gòu)造函數(shù)實(shí)例化后添加的新屬性。
for in 循環(huán)
使用 for in 循環(huán),只遍歷對(duì)象自身和繼承的可枚舉的屬性。
雖然 for in 主要用于遍歷對(duì)象屬性,但同樣也可以用來(lái)遍歷數(shù)組元素。
let arr = ['a', 'b', 'c', 'd'];
for (let i in arr) {
console.log('索引: ' + i, '值: ' + arr[i]);
}
for (let i = 0; i < arr.length; i++) {
console.log('索引: ' + i, '值: ' + arr[i]);
}
// 兩種方式都輸出:
// 索引: 0 值: a
// 索引: 1 值: b
// 索引: 2 值: c
// 索引: 3 值: d
for 和 for in 遍歷數(shù)組的區(qū)別主要有 3 點(diǎn):
1. 如果擴(kuò)展了 Array,那么擴(kuò)展的屬性會(huì)被 for in 輸出。
let colors = ['red', 'green', 'yellow'];
// 擴(kuò)展了 Array.prototype
Array.prototype.demo = function () {};
for (let i in colors) {
console.log(i); // 0 1 2 demo
}
// 查看原型的方法[[enumerable]]特性,以 push 方法為例
console.log(Array.prototype.propertyIsEnumerable('push')); // false
console.log(Object.getOwnPropertyDescriptor(Array.prototype, 'push')); // {value: ?, writable: true, enumerable: false, configurable: true}
// 查看 demo 屬性的特性
console.log(Array.prototype.propertyIsEnumerable('demo')); // true
console.log(Object.getOwnPropertyDescriptor(Array.prototype, 'demo')); // {value: ?, writable: true, enumerable: true, configurable: true}
可以看出我們添加的 demo 方法,默認(rèn)是可以被 for in 枚舉出來(lái)的。如果不想被其枚舉,可以使用 Es5 的 Object.defineProperty() 來(lái)重新定義這個(gè)屬性,此外,還可以使用 hasOwnProperty() 方法來(lái)過(guò)濾掉。
2. for 和 for in 遍歷數(shù)組時(shí)下標(biāo)類(lèi)型不一樣。
let colors = ['red', 'green', 'yellow'];
for (let i in colors) {
console.log(typeof i); // string string string
}
for (let i = 0; i < colors.length; i ++) {
console.log(typeof i); // number number number
}
3. 對(duì)于不存在的數(shù)組項(xiàng)的處理差異。
對(duì)于數(shù)組來(lái)講,我們知道如果將其 length 屬性設(shè)置為大于數(shù)組項(xiàng)數(shù)的值,則新增的每一項(xiàng)都會(huì)得到 undefined 的值。
let colors = ['red', 'green', 'yellow'];
// 將數(shù)組長(zhǎng)度變?yōu)?10
colors.length = 10;
// 在添加一個(gè)元素到數(shù)組末尾
colors.push('blue');
for (let i in colors) {
console.log(i); // 0 1 2 10
}
Object.keys()
Object.keys() 返回對(duì)象自身的所有可枚舉的屬性的鍵名。返回一個(gè)由屬性名組成的數(shù)組。
// 遍歷數(shù)組
let colors = ['red', 'green', 'yellow'];
colors.length = 10;
colors.push('blue');
Array.prototype.demo = function () {};
console.log(Object.keys(colors)); // ["0", "1", "2", "10"]
// 遍歷對(duì)象
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.demo = function() {};
var jenemy = new Person('jenemy', 25);
console.log(Object.keys(jenemy)); // ["name", "age"]
Object.getOwnPropertyNames()
Object.getOwnPropertyNames() 方法返回對(duì)象的所有自身屬性的屬性名(包括不可枚舉的屬性)組成的數(shù)組,但不會(huì)獲取原型鏈上的屬性。
A.prototype.date = Date();
function A (a, b) {
this.a = a;
this.b = b;
this.getA = function () {
return this.a;
}
}
let a = new A('b', 'bb');
a.getB = function () {};
Object.defineProperty(a, 'demo', {
value: function () { }
})
console.log(Object.getOwnPropertyNames(a)); // ["a", "b", "getA", "getB", "demo"]
總結(jié):其實(shí)這幾個(gè)方法之間的差異主要在屬性是否可可枚舉,是來(lái)自原型,還是實(shí)例。
| 方法 | 適用范圍 | 描述 |
|---|---|---|
| for..in | 數(shù)組,對(duì)象 | 獲取可枚舉的實(shí)例和原型屬性名 |
| Object.keys() | 數(shù)組,對(duì)象 | 返回可枚舉的實(shí)例屬性名組成的數(shù)組 |
| Object.getPropertyNames() | 數(shù)組,對(duì)象 | 返回除原型屬性以外的所有屬性(包括不可枚舉的屬性)名組成的數(shù)組 |