JavaScript數(shù)據(jù)結(jié)構(gòu)之Object

Object 是 ECMAScript 中最常用的數(shù)據(jù)類型之一,很適合存儲(chǔ)和在應(yīng)用程序之間交互數(shù)據(jù)。Object 定義一組屬性的無序集合,可以將其想象成一張散列表,其中的內(nèi)容就是一組名/值對(duì),值可以是數(shù)據(jù)或者函數(shù)。 而數(shù)組是一個(gè)有序集合,為了保證元素排列有序,相比 Object 來說會(huì)占用更多的內(nèi)存空間。
本文將介紹 Object 使用中將用得到的方法。
1. Object.defineProperty
在了解 JavaScript 對(duì)象之前先來了解一下 Object 中的 defineProperty方法是什么。當(dāng)一個(gè)對(duì)象在初始處理過程中由引擎創(chuàng)建時(shí),JavaScript 將基本屬性賦予新創(chuàng)建的對(duì)象,以處理來自外部的請(qǐng)求,例如訪問或刪除屬性。
可以修改或設(shè)置的屬性如下:
value: 屬性的值enumerable:如果為true,則該屬性可通過for-in循環(huán)或Object.keys()進(jìn)行搜索,默認(rèn)為false。writable:如果為false,則無法修改該屬性,它在嚴(yán)格模式下引發(fā)錯(cuò)誤,默認(rèn)為false。- 可配置 : 如果為
false,則這會(huì)使對(duì)象的屬性不可枚舉、不可寫、不可刪除和不可配置,默認(rèn)為false。 get: 當(dāng)嘗試訪問該屬性時(shí)提前調(diào)用的函數(shù),默認(rèn)為undefined。set: 當(dāng)嘗試為屬性設(shè)置某個(gè)值時(shí)提前調(diào)用的函數(shù),默認(rèn)為undefined。
下面來看一些簡(jiǎn)單的代碼:
可枚舉
const obj = {};
Object.defineProperty(obj, "a", {
value: 100,
enumerable: false,
});
for (const key in obj) {
console.log(key);
}
// 未定義
Object.keys(obj);
// []
可寫
const obj = {};
Object.defineProperty(obj, "a", {
value: 100,
writable: false,
});
obj.a = 200;
obj.a === 100; // 真的
(() => {
"use strict";
obj.a = 100;
// 嚴(yán)格模式下的類型錯(cuò)誤
})();
可配置
const obj = {};
Object.defineProperty(obj, "a", {
value: 100,
configurable: false,
});
// 1. non-enumerable
for (const key in obj) {
console.dir(key);
}
// undefined
Object.keys(obj);
// [
// 2. non-writable
(() => {
"use strict";
obj.a = 200;
// TypeError in the strict mode
})();
// 3. non-deletable
delete obj.a;
obj.a === 100; // true
但是,當(dāng) writable 或 enumerable 為 true 時(shí),將忽略 configure:false。
2. Object.freeze()
Object.freeze() 方法可以防止對(duì)象中的數(shù)據(jù)被修改,即凍結(jié)一個(gè)對(duì)象,這樣不能向這個(gè)對(duì)象 添加、更新或刪除屬性。
語(yǔ)法
Object.freeze(obj)
obj:要被凍結(jié)的對(duì)象。
返回值
返回被凍結(jié)的對(duì)象。
實(shí)例
const author = {
name: "Quintion",
city: "Shenzhen",
age: 18,
validation: true,
};
Object.freeze(author);
author.name = "QuintionTang";
author.province = "Guangdong";
delete author.age;
console.log(author); // { name: 'Quintion', city: 'Shenzhen', age: 18, validation: true }
如上面的代碼,更新屬性name、新增屬性province、刪除屬性age,最終對(duì)象都沒有任何改變。
3. Object.seal()
Object.seal()方法有點(diǎn)類似于 Object.freeze() 。阻止向?qū)ο筇砑有碌膶傩院蛣h除屬性,但允許更改和更新現(xiàn)有屬性。
語(yǔ)法
Object.seal(obj)
obj:將要被密封的對(duì)象。
返回值
返回被密封的對(duì)象。
實(shí)例
const author = {
name: "Quintion",
city: "Shenzhen",
age: 18,
validation: true,
};
Object.seal(author);
author.name = "QuintionTang";
author.province = "Guangdong";
delete author.age;
console.log(author); // { name: 'QuintionTang', city: 'Shenzhen', age: 18, validation: true }
從上面代碼可以看到,新增屬性和刪除屬性都無效,只有更新屬性name生效了。
Object.Seal() 和 Object.freeze()
談到 Object.seal 和 Object.freeze 就不得不談到數(shù)據(jù)的可變性,數(shù)據(jù)不變性在編程語(yǔ)言中一直非常重要,在 JavaScript 中也是如此。Object.freeze 和 Object.seal 方法可以部分保證數(shù)據(jù)的不變性。
上面已經(jīng)介紹了這兩個(gè)方法的使用,這里就從代碼的結(jié)果來對(duì)比一下其區(qū)別。來看下面的 Object.seal 例子:
Object.seal 的解析
const obj = { author: "DevPoint" };
console.log(Object.getOwnPropertyDescriptors(obj));
/*
{
author: {
value: 'DevPoint',
writable: true,
enumerable: true,
configurable: true
}
}
*/
Object.seal(obj);
console.log(Object.getOwnPropertyDescriptors(obj));
/*
{
author: {
value: 'DevPoint',
writable: true,
enumerable: true,
configurable: false
}
}
*/
obj.author = "天行無忌";
console.log(obj.author); // 天行無忌
delete obj.author;
console.log(obj.author); // 天行無忌
obj.city = "Shenzhen";
console.log(obj.city); // undefined
上面代碼定義了一個(gè)對(duì)象 obj 有一個(gè)屬性 author ,其中的值為 DevPoint,初始的描述屬性如下:
{
author: {
value: 'DevPoint',
writable: true,
enumerable: true,
configurable: true
}
}
然后用 Object.seal 密封了對(duì)象,再次查看哪些描述符發(fā)生了變化,哪些沒有,從結(jié)果看只有可配置的更改為 false。
{
author: {
value: 'DevPoint',
writable: true,
enumerable: true,
configurable: false
}
}
obj.author = "天行無忌";
盡管 Object.seal 后的可配置現(xiàn)在為 false,但還是通過代碼改變其屬性值為 天行無忌 ,正如之前所解釋的,將可配置設(shè)置為 false 會(huì)使屬性不可寫,但是如果 writable 明確為 true ,則它不起作用。當(dāng)創(chuàng)建一個(gè)對(duì)象并設(shè)置一個(gè)新屬性時(shí),它默認(rèn)為 writable:true 。
delete obj.author;
Object.seal 會(huì)使每個(gè)屬性都不可配置,從而防止被刪除。從上面的代碼看,對(duì)對(duì)象執(zhí)行 Object.seal 后,delete obj.author; 將變得無效。
obj.city = "Shenzhen";
Object.freeze 的解析
同樣先來看一下代碼,如下:
const obj = { author: "DevPoint" };
console.log(Object.getOwnPropertyDescriptors(obj));
/*
{
author: {
value: 'DevPoint',
writable: true,
enumerable: true,
configurable: true
}
}
*/
Object.freeze(obj);
console.log(Object.getOwnPropertyDescriptors(obj));
/*
{
author: {
value: 'DevPoint',
writable: false,
enumerable: true,
configurable: false
}
}
*/
obj.author = "天行無忌";
console.log(obj.author); // DevPoint
delete obj.author;
console.log(obj.author); // DevPoint
obj.city = "Shenzhen";
console.log(obj.city); // undefined
從上面代碼結(jié)果看,與 Object.seal 的區(qū)別在于 writable 在執(zhí)行 Object.freeze 后屬性值也變?yōu)?false 。因此后續(xù)代碼對(duì)其屬性進(jìn)行更新都無效。同樣與 Object.seal 一樣,Object.freeze 也使對(duì)象不可配置,這使得對(duì)象的每個(gè)屬性都不可刪除。
共同點(diǎn)
- 執(zhí)行后的對(duì)象變得不可擴(kuò)展,這意味著對(duì)象將無法添加新屬性。
- 執(zhí)行后的對(duì)象中的每個(gè)元素都變得不可配置,這意味著無法刪除屬性。
- 如果在“使用嚴(yán)格”模式下調(diào)用操作,則兩種方法都可能引發(fā)錯(cuò)誤,例如在嚴(yán)格模式下執(zhí)行
obj.author = "天行無忌"會(huì)出現(xiàn)錯(cuò)誤。
不同
對(duì)象執(zhí)行 Object.seal 后允許修改屬性,而執(zhí)行 Object.freeze 則不允許。
不足
Object.freeze 和 Object.seal 在“實(shí)用性”方面存在不足,它們都只是對(duì)對(duì)象的第一層有效。
const obj = { author: "DevPoint", detail: { view: 100 } };
console.log(Object.getOwnPropertyDescriptors(obj.detail));
/*
{
view: { value: 100, writable: true, enumerable: true, configurable: true }
}
*/
Object.seal(obj);
console.log(Object.getOwnPropertyDescriptors(obj.detail));
/*
{
view: { value: 100, writable: true, enumerable: true, configurable: true }
}
*/
obj.detail.view = 500;
console.log(obj.detail.view); // 500
delete obj.detail.view;
console.log(obj.detail); // {}
obj.detail.hits = 666;
console.log(obj.detail.hits); // 666
Object.freeze(obj);
console.log(Object.getOwnPropertyDescriptors(obj.detail));
/*
{
view: { value: 100, writable: true, enumerable: true, configurable: true }
}
*/
如果希望避免對(duì)更深層次的對(duì)象屬性有效,需要像深拷貝一樣,需要寫一些代碼來實(shí)現(xiàn)(deepFreeze):
const obj = { author: "DevPoint", detail: { view: 100 } };
console.log(Object.getOwnPropertyDescriptors(obj.detail));
/*
{
view: { value: 100, writable: true, enumerable: true, configurable: true }
}
*/
const deepFreeze = (object) => {
const propNames = Object.getOwnPropertyNames(object);
for (const name of propNames) {
const value = object[name];
if (value && typeof value === "object") {
deepFreeze(value);
}
}
return Object.freeze(object);
};
const freezeObj = deepFreeze(obj);
console.log(Object.getOwnPropertyDescriptors(freezeObj.detail));
/*
{
view: { value: 100, writable: false, enumerable: true, configurable: false }
}
*/
obj.detail.view = 500;
console.log(obj.detail.view); // 100
delete obj.detail.view;
console.log(obj.detail); // {view:100}
obj.detail.hits = 666;
console.log(obj.detail.hits); // undefined
如果希望對(duì)嵌套對(duì)象實(shí)現(xiàn) Object.seal 效果,同樣需要編寫代碼來實(shí)現(xiàn)(deepSeal):
const obj = { author: "DevPoint", detail: { view: 100 } };
console.log(Object.getOwnPropertyDescriptors(obj.detail));
/*
{
view: { value: 100, writable: true, enumerable: true, configurable: true }
}
*/
const deepSeal = (object) => {
const propNames = Object.getOwnPropertyNames(object);
for (const name of propNames) {
const value = object[name];
if (value && typeof value === "object") {
deepSeal(value);
}
}
return Object.seal(object);
};
const freezeObj = deepSeal(obj);
console.log(Object.getOwnPropertyDescriptors(freezeObj.detail));
/*
{
view: { value: 100, writable: true, enumerable: true, configurable: false }
}
*/
obj.detail.view = 500;
console.log(obj.detail.view); // 500
delete obj.detail.view;
console.log(obj.detail); // {view:500}
obj.detail.hits = 666;
console.log(obj.detail.hits); // undefined
4. Object.keys()
Object.keys() 方法會(huì)返回一個(gè)數(shù)組,該數(shù)組包含參數(shù)對(duì)象的所有鍵的名稱,數(shù)組中屬性名的排列順序和正常循環(huán)遍歷該對(duì)象時(shí)返回的順序一致 。
語(yǔ)法
Object.keys(obj)
obj:要返回其枚舉自身屬性的對(duì)象。
返回值
一個(gè)表示給定對(duì)象的所有可枚舉屬性的字符串?dāng)?shù)組。
實(shí)例
看看下面的代碼:
const author = {
name: "Quintion",
city: "Shenzhen",
age: 18,
validation: true,
};
console.log(Object.keys(author)); // [ 'name', 'city', 'age', 'validation' ]
可以看到上面的代碼中打印的結(jié)果是一個(gè)包含鍵作為輸出的數(shù)組。輸出的結(jié)果可以使用數(shù)組的方法進(jìn)行處理或者迭代。
console.log(Object.keys(author).length); // 4
5. Object.values()
Object.values() 和 Object.keys() 類似,不過Object.values() 是獲取對(duì)象內(nèi)素有屬性的值,返回值組成的數(shù)組。
語(yǔ)法
Object.values(obj)
obj:被返回可枚舉屬性值的對(duì)象。
返回值
一個(gè)包含對(duì)象自身的所有可枚舉屬性值的數(shù)組。
實(shí)例
const author = {
name: "Quintion",
city: "Shenzhen",
age: 18,
validation: true,
};
console.log(Object.values(author)); // [ 'Quintion', 'Shenzhen', 18, true ]
6. Object.create()
Object.create() 基于現(xiàn)有對(duì)象的原型__proto__創(chuàng)建一個(gè)新對(duì)象,先來看下面代碼:
語(yǔ)法
Object.create(proto,[propertiesObject])
proto:新創(chuàng)建對(duì)象的原型對(duì)象。propertiesObject:可選,需要傳入一個(gè)對(duì)象,該對(duì)象的屬性類型參照Object.defineProperties()的第二個(gè)參數(shù)。如果該參數(shù)被指定且不為undefined,該傳入對(duì)象的自有可枚舉屬性(即其自身定義的屬性,而不是其原型鏈上的枚舉屬性)將為新創(chuàng)建的對(duì)象添加指定的屬性值和對(duì)應(yīng)的屬性描述符。
返回值
一個(gè)新對(duì)象,帶著指定的原型對(duì)象和屬性。
實(shí)例
const author = {
firstName: "Quintion",
lastName: "Tang",
fullName() {
return `${this.firstName} ${this.lastName}`;
},
};
const newAuthor = Object.create(author);
console.log(newAuthor); // {}
newAuthor.firstName = "Ronb";
newAuthor.lastName = "Joy";
console.log(newAuthor.fullName()); // Ronb Joy
在上面的代碼中,使用object. create()創(chuàng)建一個(gè)具有author對(duì)象原型的新對(duì)象newAuthor。這樣在新對(duì)象newAuthor中可以像改變author對(duì)象所擁有的屬性值一樣改變相應(yīng)的屬性值,這個(gè)看起來是不有點(diǎn)像繼承,沒錯(cuò), 使用 Object.create 可以實(shí)現(xiàn)類式繼承。
7. Object.entries()
Object.entries() 允許獲取對(duì)象的鍵和值,返回一個(gè)多維數(shù)組,其中每一維包含每個(gè)鍵和值,如[鍵 , 值]
語(yǔ)法
Object.entries(obj)
obj:可以返回其可枚舉屬性的鍵值對(duì)的對(duì)象。
返回值
給定對(duì)象自身可枚舉屬性的鍵值對(duì)數(shù)組。
實(shí)例
const author = {
firstName: "Quintion",
lastName: "Tang",
fullName() {
return `${this.firstName} ${this.lastName}`;
},
};
console.log(Object.entries(author));
輸出的結(jié)果如下:
[
[ 'firstName', 'Quintion' ],
[ 'lastName', 'Tang' ],
[ 'fullName', [Function: fullName] ]
]
8. Object.assign()
Object.assign() 方法用于將所有可枚舉屬性的值從一個(gè)或多個(gè)源對(duì)象復(fù)制到目標(biāo)對(duì)象。它將返回目標(biāo)對(duì)象。
語(yǔ)法
Object.assign(target, ...sources)
- 參數(shù):
- target:目標(biāo)對(duì)象。
- sources:源對(duì)象。
- 返回值:目標(biāo)對(duì)象。
- 描述:
如果目標(biāo)對(duì)象中的屬性具有相同的鍵,則屬性將被源中的屬性覆蓋。后來的源的屬性將類似地覆蓋早先的屬性。
注意
Object.assign方法只會(huì)拷貝源對(duì)象自身的并且可枚舉的屬性到目標(biāo)對(duì)象。
拷貝過程中將調(diào)用源對(duì)象的 getter 方法,并在 target 對(duì)象上使用setter 方法實(shí)現(xiàn)目標(biāo)對(duì)象的拷貝。因此,它分配屬性,而不僅僅是復(fù)制或定義新的屬性。
如果合并源包含getter,這可能使其不適合將新屬性合并到原型中。
-
String類型和 Symbol 類型的屬性都會(huì)被拷貝。
-
在出現(xiàn)錯(cuò)誤的情況下,例如,如果屬性不可寫,會(huì)引發(fā)TypeError,如果在引發(fā)錯(cuò)誤之前添加了任何屬性,則可以更改target對(duì)象。
-
Object.assign會(huì)跳過那些值為 null 或 undefined 的源對(duì)象。
實(shí)例
- 復(fù)制一個(gè)對(duì)象
const obj = {name:"devpoint"};
const copy = Object.assign({}, obj);
console.log(copy); //{name:"devpoint"}
- 深度拷貝問題: 針對(duì)深拷貝,需要使用其他方法,
Object.assign拷貝的是屬性值。假如源對(duì)象的屬性值是一個(gè)指向?qū)ο蟮囊茫仓豢截惸莻€(gè)引用值。
function test() {
let obj1 = { name: "devpoint1", address: { city: "Shenzhen1" } };
let obj2 = Object.assign({}, obj1);
console.log(JSON.stringify(obj2)); // {"name":"devpoint1","address":{"city":"Shenzhen1"}}
obj1.name = "devpoint2";
console.log(JSON.stringify(obj1)); // {"name":"devpoint2","address":{"city":"Shenzhen1"}}
console.log(JSON.stringify(obj2)); // {"name":"devpoint1","address":{"city":"Shenzhen1"}}
obj2.name = "devpoint3";
console.log(JSON.stringify(obj1)); // {"name":"devpoint2","address":{"city":"Shenzhen1"}}
console.log(JSON.stringify(obj2)); // {"name":"devpoint3","address":{"city":"Shenzhen1"}}
obj2.address.city = "Shenzhen3";
console.log(JSON.stringify(obj1)); // {"name":"devpoint2","address":{"city":"Shenzhen3"}}
console.log(JSON.stringify(obj2)); // {"name":"devpoint3","address":{"city":"Shenzhen3"}}
// Deep Clone
obj1 = { name: "devpoint1", address: { city: "Shenzhen1" } };
let obj3 = JSON.parse(JSON.stringify(obj1));
obj1.name = "devpoint4";
obj1.address.city = "Shenzhen4";
console.log(JSON.stringify(obj3)); // {"name":"devpoint1","address":{"city":"Shenzhen1"}}
}
test();
- 忽略 null 和 undefined:JavaScript 的
Object.assign()方法在復(fù)制對(duì)象時(shí)會(huì)忽略null和undefined。請(qǐng)看下面列出的代碼:
const obj1 = {
title: "devpoint",
};
const obj2 = Object.assign({}, obj1, null, undefined, { city: "Shenzhen" });
console.log(obj2); // { title: 'devpoint', city: 'Shenzhen' }
總結(jié)
本文對(duì)對(duì)象常見的方法做了簡(jiǎn)單的介紹,并提供了相應(yīng)的示例代碼,在實(shí)際編碼處理對(duì)象的過程中,使用上面的方法可以讓代碼變得更加優(yōu)雅。當(dāng)只需要簡(jiǎn)單的結(jié)構(gòu)來存儲(chǔ)數(shù)據(jù)并且知道所有鍵都是字符串或整數(shù)(或符號(hào))時(shí),對(duì)象是很好的選擇。