ECMAScript Object 的 Property 基本上是由 Key / Value 構成,連 Method 也是廣義 Property,我們該如何取得 Object 的所有 Key 呢 ?
Version
ECMAScript 2015
Object.keys()
Object Literal
let person = {
firstName: 'Sam',
lastName: 'Xiao',
fullName: function() {
return this.firstName + ' ' + this.lastName
}
}
Object.keys(person) // ?
以 object literal 定義 person,在 object literal 的 property 都是 enumerable,所以 Object.keys() 都能顯示。
Object.keys()
顯示 Object 所有 enumerable property 的 key,但不包含 prototype 的 property

Object.create()
let person = Object.create({}, {
firstName: {
value: 'Sam',
writable: true,
configurable: true,
enumerable: true,
},
lastName: {
value: 'Xiao',
writable: true,
configurable: true,
enumerable: false,
},
fullName: {
value: function() {
return `${this.firstName} ${this.lastName}`
},
writable: true,
configurable: true,
enumerable: true,
},
})
Object.keys(person) // ?
若要特別建立 non enumerable property,就必須使用 Object.create(),並特別設定是否為 enumerable。
第 8 行
lastName: {
value: 'Xiao',
writable: true,
configurable: true,
enumerable: false,
},
特別設定 lastName property 的 enumerable 為 false,則 Object.keys() 將無法顯示 lastName。

Prototype
function Person(firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
}
Person.prototype.fullName = function() {
return `${this.firstName} ${this.lastName}`
}
let person = new Person('Sam', 'Xiao')
Object.keys(person) // ?
ECMAScript 的 OOP 正統寫法,是將 method 定義在 prototype,也因為定義在 prototype,Object.keys() 無法抓到該 method。

Class
class Person {
constructor(firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
}
fullName() {
return `${this.firstName} ${this.lastName}`
}
}
let person = new Person('Sam', 'Xiao')
Object.keys(person) // ?
若使用 ECMAScript 2015 的 class 寫法,Object.keys() 也只能抓到 firstName 與 lastName,別忘了 class 本質仍是 constructor function,其 method 也是定義在 prototype,因此 Object.keys() 抓不到很合理。

Object.getOwnPropertyNames()
Object Literal
let person = {
firstName: 'Sam',
lastName: 'Xiao',
fullName: function() {
return this.firstName + ' ' + this.lastName
}
}
Object.getOwnPropertyNames(person) // ?
使用 Object.getOwnPropertyNames() 顯示 person 所有 property 的 key。
object.getOwnPropertyNames()
顯示 Object 所有 property 的 key,無論是 enumerable 或 non enumerable,但不包含 prototype 的 property

Object.create()
let person = Object.create({}, {
firstName: {
value: 'Sam',
writable: true,
configurable: true,
enumerable: true,
},
lastName: {
value: 'Xiao',
writable: true,
configurable: true,
enumerable: false,
},
fullName: {
value: function() {
return `${this.firstName} ${this.lastName}`
},
writable: true,
configurable: true,
enumerable: true,
},
})
Object.getOwnPropertyNames(person) // ?
僅管 lastName 的 Enumerable 為 false,但 Object.getOwnPropertyNames() 仍然會顯示。

Prototype
let person = Object.create({
fullName: function() {
return `${this.firstName} ${this.lastName}`
}}, {
firstName: {
value: 'Sam',
writable: true,
configurable: true,
enumerable: true,
},
lastName: {
value: 'Xiao',
writable: true,
configurable: true,
enumerable: false,
},
})
Object.getOwnPropertyNames(person) // ?
將 fullName() 移到 prototype,而 object literal 都是 enumerable property。
但因為 Object.getOwnPropertyNames() 不會顯示 prototype 的 property,因此只能顯示 firstName 與 lastName。

Class
class Person {
constructor(firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
}
fullName() {
return `${this.firstName} ${this.lastName}`
}
}
let person = new Person('Sam', 'Xiao')
Object.getOwnPropertyNames(person) // ?
若使用 ECMAScript 2015 的 class 寫法,Object.getOwnPropertyNames() 也只能抓到 firstName 與 lastName,別忘了 class 本質仍是 constructor function,其 method 也是定義在 prototype,因此 Object.getOwnPropertyNames() 抓不到很合理。

for…in
Object Literal
let person = {
firstName: 'Sam',
lastName: 'Xiao',
fullName: function() {
return this.firstName + ' ' + this.lastName
}
}
for (let key in person)
console.log(key)
for...in 能顯示所有 enumerable,因此能顯示 object literal 所有 property 的 key。
for…in
如同Object.keys()將 Object 所有 enumerable property 的 key 以 array 傳回,但包含 prototype 的 property

Object.create()
let person = Object.create({}, {
firstName: {
value: 'Sam',
writable: true,
configurable: true,
enumerable: true,
},
lastName: {
value: 'Xiao',
writable: true,
configurable: true,
enumerable: false,
},
fullName: {
value: function() {
return `${this.firstName} ${this.lastName}`
},
writable: true,
configurable: true,
enumerable: true,
},
})
for (let key in person)
console.log(key)
for...in 無法顯示 non enumerable property。

Prototype
function Person(firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
}
Person.prototype.fullName = function() {
return `${this.firstName} ${this.lastName}`
};
let person = new Person('Sam', 'Xiao')
for (let key in person)
console.log(key)
我們發現無論 Object.keys() 或 Object.getOwnPropertyNames(),都無法顯示 prototype 的 property,但 for...in 可顯示。

Class
class Person {
constructor(firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
}
fullName() {
return `${this.firstName} ${this.lastName}`
}
}
let person = new Person('Sam', 'Xiao')
for (let key in person)
console.log(key)
但 for...in 搭配 ECMAScript 2015 的 class 時,卻不會顯示 fullName。
理論上 class 的 fullName() 是在 prototype 上,for...in 應該會顯示卻沒顯示,也就是若搭配 class,for...in 相當於 Object.keys(),結果比較令人訝異。

Summary
| Object.keys() | Object.getOwnPropertyNames() | for in | |
|---|---|---|---|
| Enumerable | Yes | Yes | Yes |
| Non Enumerable | No | Yes | No |
| Prototype | No | No | Yes |
| Class | No Method | No Method | No Method |
Object.keys()最中規中矩,不能顯示 non enumerable,也不能顯示 prototypeObject.getOwnPropertyNames()有特異功能可顯示 non enumerable,但無法顯示 prototypefor in有特異功能能顯示 Prototype,但無法顯示 non enumerable- 三種寫法都無法顯示 class 的 method
Conclusion
- 三種方式都各有其限制,要視需求選擇合適的方式
Object.keys()與for..in搭配class時,竟然結果一樣,比較出乎意外
Reference
MDN, Object.keys()
MDN, Object.getOwnPropertyNames()
MDN, for in