[JS] JavaScript 物件(Object)
- Property flags and descriptors @ javascript.info
- 🔖 Object @ MDN > Web technology for developers > JavaScript
- 🔖 JavaScript object basics @ MDN > Learn Web Development > JavaScript
- 🔖 重新認識 JavaScript: Day 22 深入理解 JavaScript 物件屬性 @ iThome 鐵人賽
使用物件:
// 同時對 object destructuring 的變數「重新命名」和給予「預設值」
const { message: msg = 'Something went wrong' } = { message: 'Hello'};
Object.keys(obj); // 列出物件的所有鍵(需為可列舉的屬性)
Object.values(obj); // 取得物件的所有值
Object.entries(obj); // 把物件的 key 和 value 攤成陣列
let copyObj = { ...obj, weight: 67 }; // 複製物件,並以更新值取代
/* 檢驗物件是否具有特定屬性 */
Object.hasOwn(instance, prop); // 檢驗是否為該物件原本就具有的屬性(繼承而來的「不會」顯示)
prop in instance; // 檢驗是否為該物件原本就具有的屬性(繼承而來的「會」顯示)
obj.hasOwnProperty(propertyName); // 建議改用 Object.hasOwn()
Object.is(value1, value2); // 比較 value1 和 value2 是否為相同的 value
delete object.property; // 刪除物件中的屬性
定義與建立物件:
const newObject = Object.create(obj); // newObject 會繼承 obj 的物件
// 定義物件 property 的 attributes/flag (descriptors)
Object.defineProperty(obj, propertyName, {
// attributes/flags (descriptors)
value: undefined,
writable: false,
enumerable: false,
configurable: false,
});
Object.defineProperties(obj, {
propertyName1: {/* descriptors */},
propertyName2: {/* descriptors */}
})
// 檢視物件中某個 property 的 attributes/flag (descriptors)
const descriptor = Object.getOwnPropertyDescriptor(obj, propertyName);
const descriptors = Object.getOwnPropertyDescriptors(obj);
// 連同 descriptors 一併複製出新的物件
const clonedObj = {};
Object.defineProperties(clonedObj, Object.getOwnPropertyDescriptors(obj));
觀念
在 JavaScript 中,差不多所有事物都是物件,除了 null
及 undefined
以外,其它所有原始類型都可以看成是物件。
Snippets
依據條件指定物件屬性(conditionally assign object key)
keywords: dynamic object key
, dynamic object property
// Will result in { foo: 'foo', bar: 'bar'}
const item = {
foo: 'foo',
...(true && { bar: 'bar' }),
...(false && { falsy: 'falsy' }),
};
console.log(item);
- Assign, and set js object property conditionally @ Stack Overflow
- In Javascript, how to conditionally add a member to an object? @ Stack Overflow
還有一些類似的用法,像是:
let planA: object | undefined;
planA = {
foo: 'foo',
};
let planB: object | undefined;
planB = {
bar: 'bar',
};
const plan = {
...(planA && { planA }), // planA 存在的話,就把 { planA } 放進去物件
// ...(planA ? { planA } : {}), // 相同結果的寫法
...(planB || undefined), // planB 存在的話就直接把它解開來
...(planB || {}), // 相同結果的寫法
};
// plan
// {
// "planA": {
// "foo": "foo"
// },
// "bar": "bar"
// }
將物件扁平化
keywords: array to object
, object flatten
let arr = [{ a: 10 }, { b: 20 }, { c: 30 }];
let obj = arr.reduce((acc, current, index, array) => {
return Object.assign(acc, current);
}, {});
console.log(obj); // { a: 10, b: 20, c: 30 }
console.log({ ...arr }); // { 0: { a: 10 }, 1: { b: 20 }, 2: { c: 30 } }
簡潔的列出物件的屬性
keywords: object to array
, Object.entries()
const cars = {
BMW: 3,
Tesla: 2,
Toyota: 1,
};
Object.entries(cars); // [ [ 'BMW', 3 ], [ 'Tesla', 2 ], [ 'Toyota', 1 ] ]
for (let [key, value] of Object.entries(cars)) {
console.log(key, value);
}
移除物件屬性(immutable, pure function)
const obj = {
foo: 'bar',
stack: 'overflow',
};
// delete foo
const { foo, ...newObject } = obj;
console.log(newObject); // { stack: 'overflow' }
// delete foo through dynamic key
const keyToDelete = 'stack';
const { [keyToDelete]: value, ...newObject } = obj;
console.log(newObject); // { foo: 'bar' }
資料來源:Remove a property in an object immutably @ Stack Overflow
物件的擴展(object literal extension)
基本使用
在 ES6 中,我們可以不對物件屬性賦值,它會自動以屬性名稱當作屬性值:
let obj = { name, country }; // 等同於 obj = {name: name, country: country}
當我們寫:
let name = 'Aaron';
let country = 'Taiwan';
let obj = { name, country }; // 等同於 obj = {name: name, country: country}
console.log(obj); // [Object]{name: "Aaron", country: "Taiwan"}
所以賦值的情況如下:
因此,我們可以想像,如果我們寫成這樣:
let website = 'pjchender';
let obj = { abc: website };
console.log(obj); // [object]{abc: "pjchender}
要留意的是如果有直接在 object literal 中賦值,會覆蓋掉在更上面時所做的宣告:
let name = 'PJCHENder'; // 會被覆蓋
let country = 'Taiwan';
let obj_es6 = {
name: 'Aaron',
country,
};
console.log(obj_es6); // [Object]{name: "Aaron", country: "Taiwan"}
簡寫物件中的函式/方法
let obj = {
location() {
// 等同於 location: function () { ... }
// ...
},
};
obj.location();
允許將表達式作為屬性名稱(動態賦予屬性名稱)
keywords: dynamic object property key
允許將表達式作為屬性的名稱,只需要使用 [ ]
就可以了:
let websiteName = 'pjchender';
let a = 2;
let b = 3;
let obj_es = {
[websiteName]: 'welcome',
[a + b]: 'sumNumber',
};
console.log(obj_es); // [Object]{5: "sumNumber", pjchender: "welcome"}
let data = [
{
name: 'Aaron',
height: '171',
},
{
name: 'Lynn',
height: '160',
},
];
let obj = data.map((item) => {
return { [item.name]: item.height };
});
console.log(obj); // [ { Aaron: '171' }, { Lynn: '160' } ]
物件的解構賦值(object destructuring)
物件的解構賦值 @ 阮一峰
概念
陣列的解構賦值強調的順序,而物件的解構賦值強調的則是屬性名稱,屬性名稱必須相互對應才能夠取得到值。 在物件解構賦值中,冒號前是用來對應物件的屬性名稱,冒號後才是真正建立的變數名稱和被賦值的對象:
let { website, country } = { website: 'pjchender', country: 'Taiwan' };
let { website: website, country: country } = {
website: 'pjchender',
country: 'Taiwan',
}; // 等同於
console.log(website); // pjchender
console.log(country); // Taiwan
修改變數名稱
// 變數物件後面的值才是真正被建立和賦值的對象
let { website: wb, country: ct } = { website: 'pjchender', country: 'Taiwan' };
console.log(website, country); // website in not defined
console.log(wb, ct); // "pjchender", "Taiwan"
解構 undefined 會是空物件
要留意的是,如果被解構的內容是 undefined
的話,那麼等於什麼都沒做,解構後的變數就會是空物件 {}
:
const foo = { ...undefined };
console.log(foo); // {}
這點對於在使用 TS 型別推斷時有一個重要的影響,當物件的 key 有可能是 undefined,且使用了解構賦值,那麼即使一開始宣告的屬性是 required 的(T
),但推斷出來的物件屬性值還是會變成 undefined(bar
的型別):
type T = { a: number; b: string };
const obj: { someKey?: T } = {};
const foo = obj['someKey']; // T | undefined
const bar = { ...obj['someKey'] };
// {
// a?: number | undefined;
// b?: string | undefined;
// }
為變數建立預設值
let { name, country = 'Taiwan' } = {};
name; // undefined
country; // 'Taiwan'
var { x = 3 } = {};
x; // 3
var { x, y = 5 } = { x: 1 };
x; // 1
y; // 5
var { x: y = 3 } = {};
y; // 3
var { x: y = 3 } = { x: 5 };
y; // 5
同時對變數重新命名和給予預設值
var { message: msg = 'Something went wrong' } = {};
msg; // "Something went wrong"
物件解構賦值的用途和使用時機
快速取出 JSON 物件的屬性
let data_JSON = {
id: 74,
website: 'pjchender',
country: 'Taiwan',
detail: {
add: 'Tainan',
phone: '0933333333',
},
};
let { id, website, country, detail } = data_JSON;
console.log(id, website, country, detail);
在函式時取得想要的物件屬序
// @airbnb 5.1
let user = {
firstName: 'Aaron',
lastName: 'Chen',
detail: {
height: 171,
weight: 68,
},
};
// good
function getFullName(user) {
const { firstName, lastName } = user;
return `${firstName} ${lastName}`;
}
// best
function getFullName({ firstName, lastName }) {
return `${firstName} ${lastName}`;
}
在 callback function 中取得所需的屬性
let arr = [
{ name: 'Aaron', age: 28 },
{ name: 'John', age: 32 },
{ name: 'Albert', age: 26 },
];
// 直接取得物件的屬性
arr.forEach(({ name, age }) => console.log(`${name} is ${age} year's old`));
// 只取得想要使用的屬性
arr.map(({ name }) => name); // [ 'Aaron', 'John', 'Albert' ]
物件常用方法
避免改變原本的物件(immutable)
使用展開語法(spread syntax)
// 使用避免改變原本物件的方法
let car = { type: 'vehicle ', wheels: 4 };
// 使用展開語法(spread syntax)
let fordGtWithSpread = {
make: 'Ford', // 添加新的屬性
...car,
wheels: 3, // 覆蓋原有的屬性
};
console.log(fordGt); // {make: "Ford", type: "vehicle ", wheels: 3}
// 使用 Object.assign
let fordGtWithObjectAssign = Object.assign({}, car, {
make: 'Ford',
wheels: 3,
});
console.log(fordGtWithObjectAssign); //{type: "vehicle ", wheels: 3, make: "Ford"}
檢驗物件使否有該屬性
- in operator @ MDN
- has Own @ MDN
keywords: Object.hasOwn(<instance>, <prop>)
, Object.getOwnPropertyNames(<obj>)
, <obj>.hasOwnProperty(<key>)
, Object.keys(<obj>)
, <key> in <obj>
Object.hasOwn(<instance>, <prop>)
:建議使用,用來取代Object.prototype.hasOwnProperty(<prop>)
,檢驗是否為該物件原本就具有的屬性(不可列舉的仍會顯示,但繼承而來的不會顯示),這個方法(參考 MDN)。<obj>.hasOwnProperty(<key>)
:不建議使用,檢驗是否為該物件原本就具有的屬性(不可列舉的仍會顯示,但繼承而來的不會顯示)。Object.getOwnPropertyNames(<obj>)
:列出該物件所就具有的屬性(不可列舉的仍會顯示,但繼承而來的不會顯示)。Object.keys(<obj>)
:以陣列的方式列出該物件可列舉的屬性鍵
目前較建議使用 Object.hasOwn()
來判斷該物件是否具有特定屬性,因為即使是透過 Object.create(null)
所建立出來的物件依然可以使用這個方法(hasOwnProperty
無法);另外,因為 Object.prototype.hasOwnProperty()
的方法有機會被污染而無法得到正確的結果,因此如果瀏覽器有支援的話,會更建議使用 Object.hasOwn()
。
// 定義一個 function constructor
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
this.say_hello = function () {
return `Hello, ${firstName} ${lastName}`;
};
}
// 該 function constructor 繼承一個 getFullName 方法
Person.prototype.getFullName = function () {
return `Your name is ${this.firstName} ${this.lastName}`;
};
// 利用 function constructor 建立一個 aaron 物件
aaron = new Person('Aaron', 'Chen');
// 為 aaron 物件定義一個 not enumerable 的屬性 'height'
Object.defineProperty(aaron, 'height', {
value: 171,
enumerable: false,
});
// Object.hasOwn(instance, prop) 只要是非繼承而來的屬性鍵都回傳 true,不論是否為 enumerable
Object.hasOwn(aaron, 'firstName'); // true, enumerable
Object.hasOwn(aaron, 'height'); // true, non-enumerable
Object.hasOwn(aaron, 'getFullName'); // false, inheritance
// Object.getOwnPropertyNames(<obj>) 會列出所有非繼承而來的屬性鍵,不論是否為 enumerable
Object.getOwnPropertyNames(aaron); // [ 'firstName', 'lastName', 'say_hello', 'height' ]
// Object.keys(<obj>) 列出所有 enumerable 和 non-inheritance 的屬性鍵
Object.keys(aaron); // [ 'firstName', 'lastName', 'say_hello' ]
除了可以使用 Object.hasOwn()
之外,另一種是可以使用 in
operator,例如:
// 使用 in operator 來判斷該物件是否有該屬性
// <prop> in <obj>
'firstName' in aaron; // true
'height' in aaron; // true
'getFullName' in aaron; // true
可以看到,當使用的是 in
operator 的時候,即使是繼承而來的方法也會回傳 true
。
總結來說,大部分的時候都是使用 Object.hasOwn()
來判斷該物件是否帶有該屬性,除非你想要檢查的是該物件是否有特定的「方法」,且這個方法可能是繼承而來是(例如,toString()
),這時候才會使用 in
來說檢查(參考:The Difference Between in and hasOwnProperty in JavaScript)。
Object.hasOwn
或 in
是用來檢驗該元件是否有該屬性,和該屬性的值(value)無關,所以即使該 property 的 value 是 null
或 undefined
,只要該屬性存在就會回傳 true
。
NOTE:有一個小小特殊的情況是,如果是 Array 的 empty element,則會回傳 false
(因為 Array 的 element 如果被 delete 後即是 empty
)。
由於 Array 在 JavaScript 中也是物件,所以可以用 Object.hasOwn(arr, 2)
或 2 in arr
來檢驗該 Array 的 index 是否存在。
其實除了 Object.hasOwn()
、Object.prototype.hasOwnProperty()
、in
之外,還有一個是 Reflect.has()
,而它的作用方式和 in
是相通的。
複製物件
keywords: copy object
在 JavaScript 中用了複製物件的方式有很多,最常見的是使用 object spread operators,如果在標的物件裡的屬性名稱(key)和來源物件的屬性名稱相同,將會被覆寫。若來源物件之間又有相同的屬性名稱,則後者會將前者覆寫。
/**
* Object.assign(target, ...sources)
* @airbnb 3.8
**/
var obj1 = { foo: 'bar', x: 42 };
var obj2 = { foo: 'baz', y: 13 };
// 使用 Object destructing
var clonedObj = { ...obj1 }; // Object { foo: "bar", x: 42 }
var mergedObj = { ...obj1, ...obj2 }; // Object { foo: "baz", x: 42, y: 13 }
// bad: 使用 Object.assign() 會觸發 setters,而 spread syntax 不會
objCopy = Object.assign({}, obj, { height: 173 });
console.log(objCopy); // { name: 'Aaron', height: 173}
一般來說使用 Object destructuring 就可以複製物件的 key-value pair,但這麼做並不會複製這些 property 的 descriptors,如果想要完整的連 descriptors 都一併複製,則可以搭配使用 Object.getOwnPropertyDescriptors(obj)
和 Object.defineProperties()
:
let cloneUser = {};
Object.defineProperties(cloneUser, Object.getOwnPropertyDescriptors(user));
Object.create(obj):根據指定的 prototype 來建立新的物件
透過 Object.create(obj)
的方式,我們可以省去先建立建構函式(constructors)才建立物件的麻煩:
let person = {
firstName: 'Aaron',
lastName: 'Chen',
greet: function () {
console.log(`${this.firstName} ${this.lastName}`);
},
};
let john = Object.create(person); // John 會繼承 person 這個物件
延伸補充:使用 Object.create(null) 和 {} 建立物件的差別?
Is creating JS object with Object.create(null) the same as {}? @ StackOverflow
但我們使用 {}
來建立空物件時,實際上這個物件會繼承有 Object
所提供的方法;如果是使用 Object.create(null)
所建立出來的物件,才會是不包含任何內建方法的「純物件」。也就是說, {}
等同於 Object.create(Object.prototype)
。
繼承相關
keywords: Object.getPrototypeOf(obj)
, obj.constructor
Object.getPrototypeOf(obj); // 取得某一物件的 __proto__
obj.constructor; // 取得某物件的建構式名稱
getter/setter
在使用 object literal notation 在定義 getters 和 setters 時,你只需要在方法前使用 get
或 set
即可,其中 get
不需要填入任何參數;set
則可以填入取得的新值作為參數:
let obj = {
name: 'Aaron',
interest: [],
get name() {
return 'The name property is forbidden to get';
},
set setInterest(newVal) {
this.interest.push(newVal);
},
};
obj.name = 'Aaron';
obj.setInterest = 'Programming'; // setter
console.log(obj);
/*
{
name: [Getter],
interest: [ 'Programming' ],
setInterest: [Setter]
}
*/
console.log(obj.name); // "The name property is forbidden to get"
console.log(obj.interest); // [ 'Programming' ]
要直接在物件中使用 get
和 set
或者是透過 Object.defineProperty()
來定義 getter 和 setter 取決於寫程式的風格和當前的任務。如果你在定義 prototype 時使用的是 object literal notation (object initializer),那麼可能會像這裡一樣,直接在物件中使用 set
和 get
;但如果你在定義物件的當下沒有設定,或者想要稍後設定,那麼可以使用 Object.defineProperty()
。
刪除物件屬性 delete
obj = {
firstName: 'Aaron',
lastName: 'Chen',
};
delete obj.firstName;
console.log('firstName' in obj); // "false"
Reflect
- Reflect @ MDN
- Comparing Reflect and Object methods @ MDN
傳統上,物件的存取和寫入並沒有可以直接使用的「方法」,都是使用 []
、.
、delete
等等直接來做操作,例如:
const person = {};
person.firstName = 'Aaron';
person['lastName'] = 'Chen';
delete person.lastName;
console.log(person.firstName); // 'Aaron'
但現在 JavaScript 提供了 Reflect
這個內建的物件讓開發者可以針對物件來進行操作:
const person = {};
Reflect.set(person, 'firstName', 'Aaron');
Reflect.set(person, 'lastName', 'Chen');
Reflect.deleteProperty(person, 'lastName');
console.log(Reflect.get(person, 'firstName')); // Aaron
除此之外,Reflect 也提供了其他像是 defineProperty()
、has()
(類似 in
)、ownKeys()
等方法。
另外,因為 Reflect 的許多方法在成功時會回傳 true
,失敗時會回傳 false
,因此適合和 Proxy
搭配使用。
Object Descriptor (attributes / flags)
物件除了最基本 key-value 的使用外,還有其他特別的 attributes 可以使用,這些 attributes 也被稱作 "flag":
writable
:如果是true
則可以被修改,否則會是 read-onlyenumerable
:如果是true
則可以在跑回圈時被列出configurable
:如果是true
則物件中的 property 可以被刪除,且上述的 attributes 也可以被修改
預設來說,直接建立的物件這些 attributes 的值都會是 true
。
透過 Object.getOwnPropertyDescriptor(obj, propertyName)
可以檢視某物件的這些 properties 屬性:
// https://javascript.info/property-descriptors
const user = {
name: 'John',
};
const descriptor = Object.getOwnPropertyDescriptor(user, 'name');
console.log(descriptor);
// { value: 'John', writable: true, enumerable: true, configurable: true }
property 和 attribute 雖然中文都稱作「屬性」,但一般來說,"property" 指的是物件的 「key」 ;而上述的 "writable", "enumerable", "configurable" 則稱作是 "attribute"。
如果想要修改這些 attributes 則可以使用 Object.defineProperty(obj, propertyName, descriptor)
來達到,在使用 defineProperty
時:
- 如果該 property 已經存在物件中,則會直接更新該 property 的 flag/attribute
- 如果該 property 尚不存在物件中,則該 property 會被建立,且沒有被指定的 flag 則預設都會是
false
let user = {};
Object.defineProperty(user, 'name', {
value: 'Aaron',
enumerable: true
});
const descriptor = Object.getOwnPropertyDescriptor(user, 'name');
/*
{
value: 'Aaron',
writable: false,
enumerable: true,
configurable: false
}
*/
writable
如果把某物件 property 的 attribute(flag) 設為 writable: false
,則給 property 的 value 將無法被修改。
對 non-writable 的物件屬性進行操作時,只有在 strict mode 才會拋出 TypeError,否則會「沒有任何事情發生」,但物件的屬性值並不會改變。
enumerable
如果把某物件 property 的 attribute(flag) 設為 enumerable: false
,則在這個 property 在疊代時將不會被列出,不論是使用 for...in
、Object.keys()
或 Object.values()
都不會看到該 property。
configurable
如果把某物件 property 的 attribute(flag) 設為 configurable: false
,則:
- 該物件的 property 將無法被刪除
- 該 property 的 attribute 也不能被改變。然而,有一個例外是,writable 依然可以從 true 變為 false,但反過來則不可以。
許多 JavaScript 的內建物件都是屬於 configurable: false,例如,Math.PI
。
將 configurable 設成 false 後就是一條不歸路,一旦這個 property 的 configurable 變成 false 之後,就沒辦法在做任何調整,即使再使用 Object.defineProperty()
也無法改變。
// configurable: false 有一個例外是 writable 可以從 true 設為 false,但反過來則不行
let user = {
name: 'Aaron',
};
Object.defineProperty(user, 'name', {
writable: true,
configurable: false
})
// ✅ 將 writable 從 true 改為 false 是可以的
Object.defineProperty(user, 'name', {
writable: false,
})
// ❌ 但若要把 writable 從 false 在改回 true 則不被允許
// TypeError: Cannot redefine property: name
Object.defineProperty(user, 'name', {
writable: true,
})
Object.getOwnPropertyDescriptors(obj, prop)
透過 Object.getOwnPropertyDescriptors
可以取得一個物件中的所有 descriptors,包含 getter
和 setter
在內。
Object.defineProperty(obj, prop, descriptor)
keywords: configurable
, enumerable
, writable
, value
, set
, get
Object.defineProperty() @ MDN - Web technology for developers
Object.defineProperty(obj, prop, descriptor);
Object.defineProperties(obj, {
prop1: descriptor1,
prop2: descriptor2
// ...
});
物件中的屬性描述器(Property descriptors)主要分成兩種-資料描述器(data descriptors)和提取描述器(accessor descriptors)。
資料描述器(data descriptors)是有值的屬性,這個屬性可能可以被修改(writable);提取描述器(accessor descriptors)則是透過 getter-setter
的函式來描述該屬性。描述器只能是 data descriptors 或 accessor descriptors 其中一種,不能同時是兩種。
不論是資料描述器或提取描述器都是物件,並且都包含相同的鍵:
configurable
: object descriptor 的設定能否被改變或刪除。預設false
。enumerable
: 該描述器是否會在被列舉的(for...in
,Object.keys()
)過程中顯示。預設false
。
data descriptor 包含以下選擇性(optional)的鍵:
value
:屬性的值。預設undefined
。writable
:能否透過=
改變該屬性值。預設false
。
accessor descriptor 包含以下選擇性(optional)的鍵:
get
:可以作為該屬性的getter
,預設是undefined
。set
:可以作為該屬性的setter
,預設是undefined
。
為了要確保這些預設值都有被保留,你可以凍結(freeze)Object.prototype
、明確的定義所有的項目、或者使用 Object.create(null)
。
// data descriptor with default value
Object.defineProperty(obj, 'key', {
enumerable: false,
configurable: false,
writable: false,
value: undefined,
});
建立物件屬性(Creating a property)
當所定義的屬性不存在物件中時,Object.defineProperty()
會建立一個新的 property:
資料描述器(Data Descriptor)
/**
* Data Descriptor
* 一般以 Object Literal 定義物件時,所有的 attribute/flag 預設都是 true
**/
var o = {}; // Creates a new object
Object.defineProperty(o, 'a', {
value: 37,
writable: true, // 設為 false 則無法改變該屬性值
enumerable: true, // 如果設為 false,則 o => { }
configurable: true,
});
console.log(o); // { a: 37 }
console.log(o.a); // 37
提取描述器(Accessor Descriptor)
/**
* Accessor Descriptor
**/
let o = {};
var bValue = 38;
Object.defineProperty(o, 'b', {
get() {
return bValue;
},
set(newValue) {
bValue = newValue;
},
enumerable: true,
configurable: true,
});
console.log(o.b); // 38
o.b = 18;
console.log(o.b); // 18
改變物件屬性(Modifying a property)
當物件屬性已經存在時,Object.defineProperty()
會根據 descriptor 中的 value 和物件當時的 configuration 來試著改變物件屬性。如果舊的描述器中的 configurable
為 false
,則稱作 non-configurable
,此時沒有屬性可以被更改,也沒辦法將描述器在 data 和 accessor 中轉換。
non-configurable:當物件的
configurable
被設成false
時,稱作 "non-configurable",此時沒辦法修改該物件的 descriptor。但有一個例外時,non-configurable 的 property 依然可以將 writable 從true
改為false
(反過來則不可🙅♂️)。
和 descriptor 其他相關的 methods
Object.preventExtensions(obj)
Object.isExtensible(obj)
obj.propertyIsEnumerable(prop)
Object.getOwnPropertyDescriptor(obj, prop)
Object.prototype.propertyIsEnumerable(prop)
Object.preventExtensions(<obj>) // 用來避免物件被新增新的屬性
Object.isExtensible(<obj>) // 檢驗物件可否擴充(新增屬性)
<obj>.propertyIsEnumerable(<prop>) // 檢驗該物件的某 key 能否在疊代時顯示
Object.getOwnPropertyDescriptor(<obj>, <prop>) // 取得某物件屬性的 descriptor
Object.prototype.propertyIsEnumerable(<prop>) // 檢驗該物件的屬性鍵是否為 enumerable
Extensible:當一個物件不是 extensible 的時候,表示沒辦法為該物件新增屬性。
封裝整個物件的方法
keywords: Object.preventExtensions(obj)
, Object.seal(obj)
, Object.freeze(obj)
前面提到可以透過 Object.defineProperties()
來修改物件中個別 property 的 descriptors。如果想要針對的是一整個物件的話,則可以透過下述方法:
方法 | extensible | writable | enumerable | configurable |
---|---|---|---|---|
Object.preventExtensions(obj) | false | true | true | true |
Object.seal(obj) | false | true | true | false |
Object.freeze(obj) | false | false | true | false |
extensible
指的是能不能對該物件添加新的 property。
prevent extensions:不可以添加新的 property
var o = {
name: 'Foobar',
};
/**
* 使用 Object.preventExtensions(obj) 時:
* 不可新增 property(non-extensible)
* writable: true, enumerable: true, configurable: true
**/
Object.isExtensible(o); // true
Object.preventExtensions(o);
Object.isExtensible(o); // false
Object.isSealed(o); // false
Object.isFrozen(o); // false
Object.getOwnPropertyDescriptor(o, 'name');
seal:non-extensible + non-configurable
/**
* 使用 Object.seal(obj) 時:
* 不可新增屬性(non-extensible)
* writable: true, enumerable: true, configurable: false
**/
Object.isExtensible(o); // true
Object.seal(o);
Object.isExtensible(o); // false
Object.isSealed(o); // true
Object.isFrozen(o); // false
Object.getOwnPropertyDescriptor(o, 'name');
freeze:non-extensible + non-configurable + non-writable
/**
* 使用 Object.freeze(obj) 時:
* 不可新增屬性(non-extensible)
* writable: false, enumerable: true, configurable: false
**/
Object.isExtensible(o); // true
Object.freeze(o);
Object.isExtensible(o); // false
Object.isSealed(o); // true
Object.isFrozen(o); // true
Object.getOwnPropertyDescriptor(o, 'name');
物件屬性的順序
物件中屬性的順序取決於屬性的類型和它的值,一般來說數值會在最前面、接著是字串、最後是 Symbol:
var obj = {
2: 'integer: 2',
foo: 'string: foo',
'01': 'string: 01',
1: 'integer: 1',
[Symbol('first')]: 'symbol: first',
};
Object.keys(obj); // ["1", "2", "foo", "01"];
Reflect.ownKeys(obj); // ["1", "2", "foo", "01", Symbol(first)]
Code Style
使用 ES6 提供的縮寫
@ airbnb 3.5
// good
const atom = {
value: 1,
addValue(value) {
return atom.value + value;
},
};
// good,使用縮寫的放在最上面
const obj = {
lukeSkyWalker,
lucySkyWalker,
episodeOne: 1,
twoJediWalkIntoACantina: 2,
episodeThree: 3,
mayTheFourth: 4,
};
不要直接呼叫 Object.prototype 的方法
@ airbnb 3.7
之所以不建議直接使用 Object.prototype
的方法,是因為這些方法是有機會被污染(覆蓋)後而得到錯誤的結果,因此可以使用 .call
確保拿到沒被污染的方法:
// bad
console.log(object.hasOwnProperty(key));
// good
console.log(Object.prototype.hasOwnProperty.call(object, key));
// best
const has = Object.prototype.hasOwnProperty; // cache the lookup once, in module scope.
/* or */
import has from 'has';
// ...
console.log(has.call(object, key));
參考
- JavaScript Function 參考文件 @ MDN
- 物件的使用 @ MDN > JavaScript 指南
- 物件的所有方法 @ MDN