有時要將 JS 物件轉成其他資料結構,所以需要迭代物件中的所有 property,過去會用 for-in
陳述句,但只想列舉 own property,就必須用 hasOwnProperty()
才能解決。後來 ES5 新增的 Object.keys()
解決了非 own property 的問題,但還是要手動透過 property name 來存取 value,不方便!終於在 ES2017 (ES8) 新增了 Object.values()
和 Object.entries()
,可以直接取得 property value 了!
本篇來介紹 Object.values()
、Object.keys()
和 Object.entries()
,以及在 ECMAScript spec 是如何定義的。
本文同步發表於 Titangene Blog:JavaScript 之旅 (4):Object.keys() & Object.values() & Object.entries()
「JavaScript 之旅」系列文章發文於:
for-in
statement在過去只能用 for-in
statement (陳述句) 來迭代物件中的每個 enumerable (可列舉的) property (但會忽略 key 為 Symbol
的 property):
let rectangle = {width: 16, height: 9};
for (const key in rectangle) {
console.log(key, rectangle[key]);
}
// width 16
// height 9
而且 for-in
陳述句也會迭代到繼承的 enumerable property:
let rectangle = {width: 16, height: 9};
let customRectangle = Object.create(rectangle);
customRectangle.color = 'blue';
customRectangle.name = 'myCustom';
for (const key in customRectangle) {
console.log(key, customRectangle[key]);
}
// color blue
// name myCustom
// width 16
// height 9
若不想列舉繼承的 property (即只列舉 own property),可用 Object.prototype.hasOwnProperty()
來判斷:
for (const key in customRectangle) {
if (customRectangle.hasOwnProperty(key)) {
console.log(key, customRectangle[key]);
}
}
// color blue
// name myCustom
去查 spec 才發現,原來
for-in
陳述句在 ES1 (spec 的定義在 p.54) 就有了。
Object.keys()
若以只想取得物件中的所有 property key,那
Object.keys()
就很方便,但是,若想一次拿到 property key 和 property name,應該就可以說Object.keys()
是過度期吧?因為以現代的角度來看,還有更方便的Object.entries()
(後面會介紹)。
後來在 ES5 新增了 Object.keys()
,終於可以擺脫 ,終於可以讓 JS 物件 (間接) 使用 Array method 了!for-in
陳述句了
Object.keys()
可直接取得所有 property name。看一個簡單的範例:
let object = {a: 1, b: 2, c: 3};
console.log(Object.keys(object)); // ["a", "b", "c"]
因為 Object.keys()
回傳的是陣列,而 JS 的陣列是迭代物件,所以可以使用 for-of
陳述句來迭代:
let object = {a: 1, b: 2, c: 3};
for (const key of Object.keys(object)) {
console.log(key);
}
// a
// b
// c
當然也可以用 Array.prototype.forEach()
來迭代:
let object = {a: 1, b: 2, c: 3};
Object.keys(object).forEach(key => {
console.log(key);
});
// a
// b
// c
如果接續剛剛的範例物件,若不想列舉繼承的 property,可用 Object.keys()
,它會幫你自動忽略 Object.prototype.hasOwnProperty()
為 false
的 property:
let rectangle = {width: 16, height: 9};
let customRectangle = Object.create(rectangle);
customRectangle.color = 'blue';
customRectangle.name = 'myCustom';
Object.keys(customRectangle).forEach(key => {
console.log(key, customRectangle[key]);
});
// color blue
// name myCustom
後面會說明為何
Object.keys()
不用我們手動處理Object.prototype.hasOwnProperty()
,spec 其實都有定義。
而且 Object.keys()
還可搭配多個 Array method 做很多事。以一個情境為例:列出排序過的 property,並以 ,
串接所有 property。
先利用 Object.keys()
取得所有 property name,然後透過 Array.prototype.sort()
排序 property name,再用 Array.prototype.join()
串接陣列中的元素:
let letterCount = {b: 2, a: 5, c: 3};
let letters = Object.keys(letterCount)
.sort()
.join(', ');
console.log(letters); // a, b, c
若想知道此物件有幾個 property,可用 Object.keys()
搭配 Array.length
取得:
let object = {a: 1, b: 2, c: 3};
let propertyCount = Object.keys(object).length;
console.log(propertyCount); // 3
Object.values()
和 Object.entries()
後來在 ES2017 (ES8) 新增了 Object.values()
和 Object.entries()
,終於可以取得 property value 了!以前的 for-in
陳述句和 Object.keys()
都還要自己透過原物件的 property name 來存取 property value。
Object.values()
是直接取得所有 property value,並以陣列回傳。看一個簡單的範例:
let object = {a: 1, b: 2, c: 3};
console.log(Object.values(object)); // [1, 2, 3]
而 Object.entries()
是直接取得所有 property 的 name 和 value,並以陣列回傳。看一個簡單的範例:
let object = {a: 1, b: 2, c: 3};
console.log(Object.entries(object));
// [
// ["a", 1],
// ["b", 2],
// ["c", 3]
// ]
Object.values()
和 Object.entries()
跟 Object.keys()
一樣,都不會迭代到繼承的 property:
let rectangle = {width: 16, height: 9};
let customRectangle = Object.create(rectangle);
customRectangle.color = 'blue';
customRectangle.name = 'myCustom';
Object.entries(customRectangle).forEach(([key, value]) => {
console.log(key, value);
});
// color blue
// name myCustom
後面會說明為何
Object.values()
和Object.entries()
都不用我們手動處理Object.prototype.hasOwnProperty()
,spec 其實都有定義。
因為 Object.values()
和 Object.entries()
都是回傳陣列,而 JS 的陣列是迭代物件,所以可以使用 for-of
陳述句或 Array method 進行迭代:
let object = {a: 1, b: 2, c: 3};
for (const [key, value] of Object.entries(object)) {
console.log(key, value);
}
// a 1
// b 2
// c 3
Object.entries(object).forEach(([key, value]) => {
console.log(key, value);
});
// a 1
// b 2
// c 3
Object.values()
和 Object.entries()
常會搭配 Array method 一起使用,因為 Object.values()
和 Object.entries()
的回傳值都是 Array。下面實際舉個例子:
假設原本的資料是一個物件,一個 property 代表一種水果,key 是水果名稱,value 是數量:
let fruits = {
apple: 2,
banana: 3,
orange: 1
};
如果想計算水果總數,可用 Object.values()
搭配 Array.prototype.reduce()
來處理:
const fruitTotal = Object.values(fruits)
.reduce((accumulator, fruitCount) => accumulator + fruitCount);
console.log(fruitTotal); // 6
若希望後續處理資料比較方便,你可能會想更改資料結構,將一種水果變為一個物件,該物件有兩個 property,分別為 name
和 count
,此時就可用 Object.entries()
來處理:
let fruitBasket = Object.entries(fruits)
.map(([fruitName, fruitCount]) => {
return {name: fruitName, count: fruitCount}
});
console.log(fruitBasket);
// [
// {name: "apple", count: 2},
// {name: "banana", count: 3},
// {name: "orange", count: 1}
// ]
這個作法有一些好處:
Object.entries()
的回傳值 (即陣列),可搭配各種 Array method 來滿足你的需求 (變成你想要的樣子)Object.entries()
是產生新的陣列,不會影響到原物件,所以後續不管你用的 Array method 是否會修改該陣列,都不會影響到原物件的資料 (夠 pure)Object
物件轉 Map
物件可用 Object.entries()
將物件轉成 Map
物件,將 Object.entries()
回傳的可迭代 entries 傳給 new Map()
constructor 即可:
let fruits = {
apple: 2,
banana: 3,
orange: 1
};
let fruitMap = new Map(Object.entries(fruits));
console.log(fruitMap);
// Map(3) {"apple" => 2, "banana" => 3, "orange" => 1}
console.log(fruitMap.get('orange')); // 1
以下是 Object.keys()
在 spec 中的定義:
以下是 Object.values()
在 spec 中的定義:
以下是 Object.entries()
在 spec 中的定義:
Object.values()
、Object.keys()
和 Object.entries()
都是在步驟 2 使用 EnumerableOwnPropertyNames()
,而步驟 3 都是使用 CreateArrayFromList()
。
我們先來看 EnumerableOwnPropertyNames()
的定義:
key
就是 property nameundefined
,並且是可列舉的 (enumerable) 才會繼續下個步驟kind
為 key 時,會將該 key
push 至 properties
(即最後我們要取得的回傳值)kind
為 value 時,會將該 value
push 至 properties
key
和 value
組合成 entry
(即 [key, value]
)entry
push 至 properties
properties
所以步驟 4 就是我們不用手動處理
Object.prototype.hasOwnProperty()
的關鍵!
回來看步驟 3 的 CreateArrayFromList()
,下面是它的定義:簡單來說就是建立陣列,將每個 property value 放入陣列中:
下面是 MDN 提供的 Object.entries()
的 polyfill:
if (!Object.entries) {
Object.entries = function(obj) {
var ownProps = Object.keys(obj),
i = ownProps.length,
resArray = new Array(i); // preallocate the Array
while (i--)
resArray[i] = [ownProps[i], obj[ownProps[i]]];
return resArray;
};
}
其他 polyfill: