Object.keys()
與 for...in
的差異在於 Object.keys()
只能顯示目前 Object 的 Property Key,而 for...in
會連同 Prototype 的 Property Key 一併顯示。
若 for...in
搭配 Constructor Function 或 Object.create()
時一切正常,但搭配 class
時,就無法顯示 Prototype 的 Property Key 了,為什麼會這樣呢 ?
Version
ECMAScript 2015
Constructor Function
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function Person(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; }
Person.prototype.fullName = function() { return this.firstName + ' ' + this.lastName; }; const person = new Person('Sam', 'Xiao');
for(let key in person) console.log(key);
|
由 Constructor Function 建立 object,並將 method 定義在 Prototype,這是 ECMAScript OOP 標準寫法。
當對 object 使用 for...in
,會顯示所有的 Property Key,連 Prototype 也會顯示。
- 使用 Prototype 定義
fullName()
method
- 使用
for...in
列出 object 所有的 Property Key
- 連 Prototype 的
fullName
也會列出
Object.create
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const prototype = { fullName: function() { return this.firstName + ' ' + this.lastName; }, };
const person = Object.create(prototype); person.firstName = 'Sam'; person.lastName = 'Xiao';
for(let key in person) console.log(key);
|
Prototype 除了事後在 Constructor Function 動態指定外,也可以事先建立 Prototype object,然後傳入 Object.create()
。
一樣使用 for...in
,也如預期列出 Prototype 的 Property Key。
- 先定義 Prototype object
- 使用
Object.create()
建立 object,並將 Prototype object 傳入
- 一樣使用
for...in
列出 object 所有的 Property Key
- 連 Prototype 的
fullName
也會列出
Class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class Person { constructor(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; }
fullName() { return this.firstName + ' ' + this.lastName; } }
const person = new Person('Sam', 'Xiao');
for(let key in person) console.log(key);
|
ECMAScript 2015 導入 class
語法後,method 可以直接定義在 class 內,會自己幫我們定義在 Prototype。
但使用 for..in
,卻發現 Prototype 的 fullName
無法顯示,退化成與 Object.keys()
功能一樣,為什麼會這樣呢 ?
- 改用 class 寫法
- 將 method 定義在 class 內
- 一樣使用
for...in
列出 object 所有的 Property Key
- 沒有列出 Prototype 的
fullName
Why ?
for...in
與 Object.keys()
都僅能列出 Enumerable Property,所以很有可能 class
的寫法,造成 Prototype 的 property 為 Non Enumerable,使得 for...in
無法顯示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class Person { constructor(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; }
fullName() { return this.firstName + ' ' + this.lastName; } }
const person = new Person('Sam', 'Xiao'); console.log(Object.getOwnPropertyDescriptor(person, 'firstName')); console.log(Object.getOwnPropertyDescriptor(person, 'lastName')); console.log(Object.getOwnPropertyDescriptor(person.__proto__, 'fullName'));
for(let key in person) console.log(key);
|
Object.getOwnPropertyDescriptor()
會傳回每個 property 的屬性,藉此觀察是否為 Non Enumerable。
- 特別加上
Object.getOwnPropertyDescriptor()
觀察之
- 果然如假設所言,Prototype 的
fullName
其 enumerable
為 false
,因此 for...in
無法顯示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| 'use strict';
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var Person = function () { function Person(firstName, lastName) { _classCallCheck(this, Person);
this.firstName = firstName; this.lastName = lastName; }
_createClass(Person, [{ key: 'fullName', value: function fullName() { return this.firstName + ' ' + this.lastName; } }]);
return Person; }();
var person = new Person('Sam', 'Xiao');
for (var key in person) { console.log(key); }
|
從另外一個角度,我們由 Babel 所 transpile 的 ES5 觀察。
1
| descriptor.enumerable = descriptor.enumerable || false;
|
對於 Prototype 的 property,Babel 也是將 enumerable
設定為 false
。
由這兩點都可以證明,當使用 class 寫法時,根據 ECMAScript 2015 規格,都會將 Prototype 的 property 設定為 Non Enumerable Property,也就是 for...in
功能將等同 Object.keys()
Conclusion
for...in
為 ECMAScript 3 所定義,而 Object.keys()
為 ECMAScript 5.1 所加入,理論上兩者的差異就在於 Prototype 部分,但 ECMAScript 2015 支援 class 後,又使得 for..in
與 Object.keys()
功能趨於一至,個人是不太喜歡這種改變,這使得 for...in
與過去的觀念不同,算 breaking change,但既然 ECMAScript 2015 規格就是這樣定義,也只能自己注意這個微小的差異了