你懂 JavaScript 嗎?#17 物件(Object)
24 Oct 2018關於物件,本文會提到
- 語法:宣告式與建構形式。
- 型別:再次複習 typeof、使用 instanceof 判定物件子型別。
- 內容:屬性值的存取、物件的複製(淺拷貝與深拷貝)、屬性描述器、不可變的物件、取值器與設值器、檢視屬性是否存在、屬性列舉。
- 迭代:一些迭代出陣列的值的方法。
語法(Syntax)
建立物件有兩種方式
- 宣告式(declarative)或稱字面值(literal),例如:
const obj = { name: 'Jack' };
。 - 建構形式(constructed form),例如:
const obj = new Object(); obj.name = 'Jack';
,也就是原生功能,但由於一些雷區,這種方式其實很少用。
簡單來說,兩者主要的差別是新增屬性時,字面值可在物件建立時一次全部加入,但建構形式必須在物件建立後一筆一筆新增。
型別(Type)
JavaScript 的資料型態有七種-字串(string)、數字(number)、布林值(boolean)、null、undefined、物件(object)與 symbol。其中函式(function)和陣列(array)、日期(date)皆為物件的一種,function 是可呼叫的物件,而 array 是結構較嚴謹的物件。
typeof
關於型別就會想到型別檢測,想到型別檢測就不得不提一下 typeof 的議題…
typeof 'Hello World!'; // 'string'
typeof true; // 'boolean'
typeof 1234567; // 'number'
typeof null; // 'object'
typeof undefined; // 'undefined'
typeof { name: 'Jack' }; // 'object'
typeof Symbol(); // 'symbol'
typeof function () {}; // 'function'
typeof [1, 2, 3]; // 'object'
typeof NaN; // 'number'
這裡會看到幾個有趣的(奇怪的)地方…
- null 是基本型別之一,但
typeof null
卻得到 object,而非 null!這可說是一個 bug,可是若因為修正了這個 bug 則可能會導致很多網站壞掉,因此就不修了! - 雖然說 function 是物件的子型別,但
typeof function() {}
是得到 function 而非 object,和陣列依舊得到 object 是不一樣的。 - NaN 表示是無效的數字,但依舊還是數字,因此在資料型別的檢測
typeof NaN
結果就是 number,不要被字面上的意思「不是數字」(not a number)給弄糊塗了。另外,NaN 與任何數字運算都會得到 NaN,並且 NaN 不大於、不小於也不等於任何數字,包含 NaN 它自己。
…
…
每次看到 typeof 都很想問 JavaScript 的作者…
記這麼多東西,都累到要倒地不起了(哭
…
…
另外,如果想知道這個物件到底是屬於哪個子型別,則可使用 Object.prototype.toString
來檢視 [[Class]]
這個內部屬性。
Object.prototype.toString.call([1, 2, 3]); // "[object Array]"
Object.prototype.toString.call({ name: 'Jack' }); // "[object Object]"
Object.prototype.toString.call(function sayHi() {}); // "[object Function]"
Object.prototype.toString.call(/helloworld/i); // "[object RegExp]"
Object.prototype.toString.call(new Date()); // "[object Date]"
不過,這個方法其實是借用建構形式其實就是物件包裹器的概念,而能使用物件型別值內的 [[Class]]
屬性來辨別這個值是屬於物件的哪個子分類。
內建物件(Built-in Objects)
內建物件指的是使用內建函式所建立的物件,這些物件都屬於物件子型別的一種,除了上面提到的陣列、函式與日期外,這裡列出物件所有的子型別:String、Number、Boolean、Object、Function、 Array、Date、RegExp、Error,它們的用途是給予開發者取得屬性或方法的使用,也就是我們常聽到的原生功能。因此,當使用建構式建立字串、布林或數字等值時,建立的其實不是基本型別值而是物件,因此可用 instanceof 來檢查是由哪個建構式建立,也就是來判斷是否為指定的物件型別。
如下,使用字串字面值宣告了一個字串 str,當我們使用 str 進行 .length
的操作以取得其長度時,JavaScript 就會將這個字串基本型別的值強制轉型成對應的物件子型別,也就是上面提到的 String。
const str = 'Hello World!';
str instanceof String; // false
str.length; // 12
const strObj = new String('Hello World!');
strObj instanceof String; // true
strObj.length; // 12
注意
- null 和 undefined 只有基本資料型別,沒有物件包裹形式,意即沒有對應的物件子型別,所以無法使用 new 來產生。
- Date、RegExp、Error 沒有基本資料型別的形式,所以只能使用物件子型別 new 來產生。
更多關於原生功能的資訊,可參考這裡。
內容(Contents)
物件的內容是由屬性組成的,而屬性是由 key-value pair 構成,value 可為任意資料型別的值,並且值是以參考(reference)的方式(存位置)儲存。
如何存取物件的屬性呢?有兩種方式
- 特性存取(property access),意即
.
- 鍵值存取(key access),意即
[ ]
其中,特性存取 .
必須符合識別字的規範,簡單說就是只能是字母、數字、$
(錢字號)或 _
(底線),並且不能以數字開頭,之後可加上 a-z、A-Z、$
、_
和數字 0-9,可為關鍵字或保留字。
讓我們來看一些疑難雜症吧!
Q1:如果屬性名稱是特殊字符或動態產生的,該怎麼存取它的值呢?
若要使用一些包含特殊字符或動態產生的字串作為屬性名稱,就必須使用鍵值存取 [ ]
的方式。
包含特殊字符的屬性名稱。
const obj = {
'!!12345!!': 'Hello World',
};
obj.!!12345!! // Uncaught SyntaxError: Unexpected token !
obj['!!12345!!'] // "Hello World"
ES6 新增動態產生的字串作為屬性名稱功能,讓 key 的值可經由運算得出。
const prefix = 'fresh-';
const fruits = {
[prefix + 'apple']: 100,
[prefix + 'orange']: 60,
};
fruits['fresh-apple']; // 100
fruits['fresh-orange']; // 60
Q2:屬性真的只能是字串嗎?可以是數字、物件等其他型別的值嗎?
屬性名稱只能是字串,若不是字串則會被強制轉為字串。
如下,obj[obj]
的 key 值被強制轉為字串 '[object Object]'
,同理,obj[999]
的 key 值 999 也被轉為字串 '999'
了。
const obj = { Qoo: '有種果汁真好喝' };
obj[obj] = '喝的時候酷兒';
obj[999] = '喝完臉紅紅!';
obj['[object Object]']; // '喝的時候酷兒'
obj['999']; // '喝完臉紅紅!'
(2020/01/17 更新)
相較於物件的屬性名稱(或稱鍵值 key)一定要是字串,Map 允許鍵值可為任何資料型別,無論是字串、數字、布林或物件甚至是 NaN(備註)都是可以的。
如下所示,將 Map 依序存入不同資料別型的鍵值與其對應的字串值。
const map = new Map();
map.set('1', 'str1'); // 鍵值是字串
map.set(1, 'num1'); // 鍵值是是數字
map.set(true, 'bool1'); // 鍵值是布林
map.set({ a: 1 }, 'obj1'); // 鍵值是物件
執行以下程式碼,會發現鍵值數字 1 並沒有被轉為字串 1,因此會得到不同的結果。
map.get(1); // 得到 'num1'
map.get('1'); // 得到 'str1'
[備註] 在 Map 中,由於是依照演算法 SameValueZero 來比對鍵值,因此 NaN === NaN
成立,NaN 可當鍵值。
函式(Function)vs 方法(Method)
闢謠…澄清…!!??
在其他語言中,屬於某個物件的函式稱為方法,但在 JavaScript 中,函式並不會特別屬於某個物件,物件充其量也只是儲存對某個函式的參考而已,並非真的「屬於」這個物件,因此,在 JavaScript 中,函式與方法是同義的,並沒有區別。除了在 ES6 新增的 super 參考,super 與 class 一起使用時 super 會靜態綁定函式,經由這樣所綁定的函式就比較接近一般在其他語言所看到的方法了。
陣列(Array)
陣列使用非負整數作為索引,注意…
- 若使用具名特性(named property)作為索引,則不會增加陣列的長度。
const array = [1, 2, 3];
array.length; // 3
array[3] = 4;
array.length; // 4
array['foo'] = 'bar';
array.length; // 4, 陣列的長度不變!
- 具名特性(named property)若以字串格式存在,就會轉為數字。
const array = [1, 2, 3];
array['3'] = 'foo';
array; // [1, 2, 3, "foo"]
複製物件
複製物件的方式分為淺拷貝與深拷貝兩種。
為什麼要探討淺拷貝與深拷貝呢?這是根本於基本型別值是傳值而物件是傳參考的緣故,既然物件是傳參考,就要考慮是把整份資料都複製一份,還是複製參考就好?淺拷貝是複製參考,深拷貝是把整份資料都複製一份,常用於考慮物件資料是否要共用的狀況。
淺拷貝(Shallow Copy)
除了基本的資料型別中純值(非物件)的資料會真的複製另外一份值之外,其他的都只是複製一份參考而已。例如:Object.assign
在處理超過一層的物件時就只能做到淺拷貝,只有一層的話是可以做到深拷貝的。
深拷貝(Deep Copy)
複製整個物件,通常會使用 JSON-safe 的物件,先經由序列化為 JSON 字串後再剖析回物件。
const newObj = JSON.parse(JSON.stringify(oldObj));
範例如下。
單層物件時,Object.assign
與「先序列化再剖析」的方法都可以做到完全的拷貝,也就是深拷貝,由於物件的比對的是比較儲存位置,因此當比較拷貝結果時,兩者是不相等的。
const simpleObj = {
a: 1,
b: 2,
};
const newSimpleObj = Object.assign({}, simpleObj);
newSimpleObj === simpleObj; // false
const newSimpleObj2 = JSON.parse(JSON.stringify(simpleObj));
newSimpleObj2 === simpleObj; // false
那麼,物件再多層的狀況下,又是怎樣的狀況呢?如下,由於 Object.assign
只能做單層的拷貝,因此第二層開始就只是複製參考而已,儲存位置不變,故為 true;而「先序列化再剖析」的方法則是完整地把整個資料複製起來,存到另一個地方,因此儲存位置不同,得到 false。
const obj = {
a: 1,
b: {
c: 2,
d: 3,
},
};
const newObj = Object.assign({}, obj);
newObj.b === obj.b; // true
const newObj2 = JSON.parse(JSON.stringify(obj));
newObj2.b === obj.b; // false
這篇文章將淺拷貝與深拷貝寫得生動有趣、清楚明暸,歡迎閱讀。
屬性描述器(Property Descriptor)
屬性描述器可用來檢視屬性的特徵,例如:可否寫入(writable)、可否配置(configurable)與可否列舉(enumerable)。
例如,檢視 object.a 這個屬性的特徵。
const obj = {
name: 'Apple',
};
Object.getOwnPropertyDescriptor(obj, 'name');
得到結果。
{
value: "Apple",
writable: true,
enumerable: true,
configurable: true,
}
使用 defineProperty 定義物件的屬性與特性。通常使用這種方法的目的是…
- 新增屬性,通常是為了修改預設特徵的值。
- 若特性是 configurable 的話,則可用來修改屬性的特徵值。
範例如下,為物件 obj 定義一個新的屬性 name,並設定其特徵值。
const obj = {};
Object.defineProperty(obj, 'name', {
value: 'Apple',
writable: true,
configurable: true,
enumerable: true,
});
obj.name; // 'Apple'
Writable
屬性的值是否「可被寫入」。
例如,設定 name 這個屬性是不可寫入的,因此當嘗試更新這個值的時候,發現無法更新,並且在 strict mode 之下會丟出 TypeError 的錯誤訊息。
const obj = {};
Object.defineProperty(obj, 'name', {
value: 'Apple',
writable: false, // 不可寫入!
configurable: true,
enumerable: true,
});
obj.name; // 'Apple'
obj.name = 'Grape';
obj.name; // 'Apple',屬性 name 的值無法被變更!
Configurable
屬性是否是「可配置的」,意即當 configurable 為 false 的時候,無法再使用 defineProperty 更新特徵的值,否則會丟出 TypeError。但有一個例外,當 configurable 為 false 的時候,writable 仍可由 true 改為 false,但不能從 false 改為 true。
當 configurable 為 false 的時候,無法再使用 defineProperty 更新特徵的值,否則會丟出 TypeError。
const obj = {};
Object.defineProperty(obj, 'name', {
value: 'Apple',
writable: true,
configurable: false,
enumerable: true,
});
Object.defineProperty(obj, 'name', {
value: 'Apple',
writable: true,
configurable: true, // false -> true
enumerable: true,
});
// Uncaught TypeError: Cannot redefine property: name
當 configurable 為 false 的時候,writable 仍可由 true 改為 false,但不能從 false 改為 true。
const obj = {};
Object.defineProperty(obj, 'name', {
value: 'Apple',
writable: true,
configurable: false,
enumerable: true,
});
Object.defineProperty(obj, 'name', {
value: 'Apple',
writable: false,
configurable: false,
enumerable: true,
});
// 這是可行的!
當 configurable 為 false 的時候,writable 仍可由 true 改為 false,但不能從 false 改為 true。
const obj = {};
Object.defineProperty(obj, 'name', {
value: 'Apple',
writable: false,
configurable: false,
enumerable: true,
});
Object.defineProperty(obj, 'name', {
value: 'Apple',
writable: true,
configurable: false,
enumerable: true,
});
// Uncaught TypeError: Cannot redefine property: name
除了是否可更新特徵的設定外,configurable 另一個作用就是是否可被 delete 刪除該屬性。
configurable 設為 false,屬性不可刪除。
const obj = {};
Object.defineProperty(obj, 'name', {
value: 'Apple',
writable: true,
configurable: false,
enumerable: true,
});
delete obj.name;
obj.name; // 'Apple',name 屬性未被刪除!
configurable 設為 true,屬性可被刪除。
const obj = {};
Object.defineProperty(obj, 'name', {
value: 'Apple',
writable: true,
configurable: true,
enumerable: true,
});
delete obj.name;
obj.name; // undefined
obj; // {}
Enumerable
特徵是否為「可列舉的」,例如:此物件的特性是否可在 for…in 中被列舉,若設定 enumerable 為 false 表示不會被列舉出來。
如下,(1) 印出 hello 和 name,(2) 由於 name 被設定為不可列舉的,因此只會印出 hello。
const obj = {};
obj.hello = 'world';
Object.defineProperty(obj, 'name', {
value: 'Apple',
writable: true,
configurable: true,
enumerable: true,
});
for (let prop in obj) {
console.log(prop); // (1)
}
// hello
// name
Object.defineProperty(obj, 'name', {
value: 'Apple',
writable: true,
configurable: true,
enumerable: false,
});
for (let prop in obj) {
console.log(prop); // (2)
}
// hello
…
…
題外話,會用到 defineProperty 這個東西是為了寫雙向綁定的小工具而追 Vue.js 的原始碼的時候玩到的,推薦閱讀這篇文章。
…
…
不可變性(Immutability)
如何實作無法被變更的特性或物件?以下會介紹幾種作法,但都只能做到淺層的不可變性(shallow immutability),意即若此物件有指向其他物件的參考,這個被指到的物件的內容就仍是可變的。
如下,假設 foo 是不可變的,但 foo.list 指向一個陣列,這個陣列的內容是可變的。
foo.list = [1, 2, 3];
foo.list.push(4);
foo.list; // [1, 2, 3, 4]
物件常數(Object Constant)
使用 defineProperty 設定 writable 為 false 且 configurable 為 false,即可建立一個特性等同於常數的物件屬性,無法被更新、重新定義和刪除。
const obj = {};
Object.defineProperty(obj, 'CONST_PI', {
value: 3.14,
writable: false,
configurable: false,
enumerable: true,
});
obj.CONST_PI; // 3.14
避免擴充(Prevent Extensions)
使用 Object.preventExtensions
防止物件被加入新屬性。
const obj = {
name: 'Jack',
};
Object.preventExtensions(obj);
obj.hello = 'world';
obj.hello; // undefined
備註,在嚴格模式下,加入新屬性會丟出 TypeError。
密封(Seal)
使用 Object.seal
來達到密封的作用,意即物件不可再新增屬性、重新配置特徵或刪除屬性,但可能可以修改屬性值。
Object.seal
會做兩件事
- 將物件設定為
Object.preventExtensions
防止物件被加入新屬性。 - 將物件的屬性特性 configurable 設定為 false,物件的屬性不能被刪除,其特徵的值不能被更新。屬性值是否可被更新要看 writable 的值而定,若 writable 為 true 則可更新,若為 false 則不可更新。
const obj = {
name: 'Jack',
};
Object.seal(obj);
// 嘗試加入新的屬性 hello
obj.hello = 'world';
obj.hello; // undefined
// 嘗試刪除屬性 name
delete obj.name; // false
obj.name; // 'Jack'
// 嘗試重新設定特徵值
Object.defineProperty(obj, 'name', {
value: 3.14,
writable: false,
configurable: true,
enumerable: true,
});
// TypeError
凍結(Freeze)
使用 Object.freeze
建立一個已凍結的物件,意即這個物件不能新增屬性、更新屬性的值、刪除屬性和重新配置特徵值。
Object.freeze
會做以下的事情
- 對物件呼叫
Object.seal
,讓物件不可再新增屬性、重新配置特徵或刪除屬性。 - 將物件的屬性特性 writable 設定為 false,物件屬性的值不可被更改。
回顧前面提到的,以上四種解法都只能做到做到淺層的不可變性(shallow immutability),因此若希望能將整個物件(包含屬性參考的物件)都凍結,可遞迴呼叫 Object.freeze
,但可能有副作用,像是凍結了共用的物件。
const list = ['apple', 'grape'];
const obj = {
name: 'Jack',
favFruits: list,
};
const anotherObj = {
name: 'Apple',
favFruits: list,
};
Object.freeze(obj);
Object.freeze(obj.favFruits);
// 共用的物件被凍結!
list.push('orange'); // Uncaught TypeError: Cannot add property 2, object is not extensible
[[Get]]
[[Get]]
的功用是取得屬性值,例如:obj.a
時會呼叫 [[Get]]()
這個函式呼叫,它會先在此物件內尋找是否有符合名稱的屬性,若無就順著原型串鏈繼續尋找,如果都沒有找到,[[Get]]
就會回傳 undefined。注意,這和之前提到的在語彙範疇中查找變數(的名稱是否被定義)是不同的,若在語彙範疇中找不到該變數,會丟出 ReferrenceError。
[[Put]]
[[Put]]
的功用是…
若此屬性不存在,則新增此屬性並設定其值。但若此屬性存在,則做以下的事情…
- 若此屬性是存取器描述器的取值器與設值器嗎?若是,則呼叫其設值器(setter)。
- 若此屬性是不可寫入的,在非嚴格模式下會無聲的失敗,但在嚴格模式下會丟出 TypeError。
- 若此屬性是可寫入的,就將值設定給這個屬性。
備註:上面提到的兩個名詞,這裡來做解釋…
- 資料描述器(data descriptor):定義一個屬性時,此屬性不含取值器或設值器。
- 存取器描述器(access descriptor):存取器描述器是指當定義一個屬性時,若此屬性擁有取值器或設值器,這個定義就會成為存取器描述器。注意,此時屬性的 value 和特徵 writable 都會被忽略,而只需考慮 set、get、configurable 和 enumerable。
取值器(Getter)與設值器(Setter)
物件預設的 [[Get]]
與 [[Put]]
掌控了屬性的建立、設定和更新、取得值的方式。若要複寫這兩種預設 [[Get]]
與 [[Put]]
行為,可透過取值器與設值器來達成。
方法一,使用物件字面值的方式定義屬性。
const obj = {
get name() {
return this._name_;
},
set name(val) {
this._name_ = `Hi, I am ${val}`;
},
};
obj.name = 'Jack';
obj.name; //'Hi, I am Jack'
方法二,使用 defineProperty 的方式定義屬性。
const obj = {};
Object.defineProperty(obj, 'name', {
configurable: true,
enumerable: true,
get: function name() {
return this._name_;
},
set: function name(val) {
this._name_ = `Hi, I am ${val}`;
},
});
obj.name = 'Jack';
obj.name; //'Hi, I am Jack'
存在(Existence)
既然屬性不存在的時候,會回傳 undefined,但若屬性值原本就設定為 undefined 是不是就無法判定這個屬性到底存不存在了?
解法是使用 hasOwnProperty,若想進一步確認該屬性是否可在其他物件中找到,可搭配 prop in obj
檢查這個屬性是否存在於原型串鏈中。兩者差異是 prop in obj
會檢查原型串鏈,而 hasOwnProperty 只會檢查該物件。
範例如下。
var obj1 = {
job: undefined,
};
var obj = Object.create(obj1); // 建立 obj 與 obj1 的連結
obj.name = undefined;
屬性 name 真的存在於 obj 嗎?
obj.name; // undefined
obj.hasOwnProperty('name'); // true
屬性 name 其值雖然為 undefined,但它真的存在於 obj。
…
…
屬性 job 真的存在於 obj 嗎?
obj.job; // undefined
obj.hasOwnProperty('job'); // false
'job' in obj; // true,但在原型串鏈中可找到
obj1.hasOwnProperty('job'); // true
屬性 job 其值雖然為 undefined 且不存在於 obj 中,但可在原型串鏈中可找到,因此進一步檢視 obj1,確定為 obj1 的屬性。
…
…
屬性 hello 真的存在於 obj 嗎?
obj.hello; // undefined
obj.hasOwnProperty('hello'); // false
'hello' in obj; // false
雖然 hello 的值是 undefined,似乎與前面的例子無異,但使用 hasOwnProperty 檢視,發現不在 obj 物件中,且經由 prop in obj
確認後發現也無法在原型串鏈中找到,因此屬性不存在。
…
…
總結…
- hasOwnProperty 可檢測這個屬性是否存在於某個物件。
prop in obj
可檢查這個屬性是否可在原型串鏈中找到,讓我們能確認是否需要再往其他物件查找。
列舉(Enumeration)
檢視屬性是否可被列舉的方法。
in
in 運算子只會帶出可列舉的屬性。
例如,obj 有兩個屬性 name 和 hello,其中 name 為可列舉的,hello 為不可列舉的。
for (let k in obj) {
console.log(k, obj[k]);
}
// 'name', 'Jack'
propertyIsEnumerable
propertyIsEnumerable 檢視屬性是否可被列舉。
例如,obj 有兩個屬性 name 和 hello,其中 name 為可列舉的,hello 為不可列舉的。檢視 name 是否為可列舉的,會回傳 true。
obj.propertyIsEnumerable('name'); // true
Object.keys
vs Object.getOwnPropertyNames
Object.keys
與 Object.getOwnPropertyNames
都只回傳此物件的屬性,且皆不檢視原型串鏈,兩者差異在於
Object.keys
:回傳所有可列舉的屬性。Object.getOwnPropertyNames
:回傳所有屬性,不管是否可被列舉。
例如,obj 有兩個屬性 name 和 hello,其中 name 為可列舉的,hello 為不可列舉的。
Object.keys(obj); // ['name']
Object.getOwnPropertyNames(obj); // ['name', 'hello']
迭代(Iteration)
迭代出陣列的值的方法。
forEach
迭代陣列中所有的值。
const list = ['Apple', 'Bob', 'Cathy', 'Doll'];
list.forEach((item, index, array) => {
console.log(item, index, array);
});
every
檢查陣列中的每個值是否符合條件,若是則回傳 true。持續進行直到結束,或 callback 中回傳 false 就停止迭代。
const list = [
{
name: 'apple',
count: 20,
},
{
name: 'corn',
count: 100,
},
{
name: 'grape',
count: 50,
},
{
name: 'pineapple',
count: 80,
},
];
const result = list.every((item, index, array) => {
console.log(item, index, array);
return item.count > 50;
});
console.log(`result: ${result}`);
some
檢查陣列中的是否有值符合條件,若是則回傳 true。持續進行直到結束,或 callback 中回傳 true 就停止迭代。
const list = [
{
name: 'apple',
count: 20,
},
{
name: 'corn',
count: 100,
},
{
name: 'grape',
count: 50,
},
{
name: 'pineapple',
count: 80,
},
];
const result = list.some((item, index, array) => {
console.log(item, index, array);
return item.count > 50;
});
console.log(`result: ${result}`);
若想看更多陣列處理方法,可參考這裡
for...of
使用 ES6 的 for...of
迭代陣列。
const array = [1, 2, 3];
for (let v of array) {
console.log(v);
}
// 1
// 2
// 3
回顧
看完這篇文章,我們到底有什麼收穫呢?藉由本文可以理解到…
- 建立物件有兩種方式-宣告式與建構形式,前者較常用,而後者有一些雷區,非必要不建議使用。
- typeof 可檢查資料型別;當使用建構式建立字串、布林或數字等值時,建立的其實不是基本型別值而是物件,因此可用 instanceof 來檢查是由哪個建構式建立的,也可檢視該物件是屬於哪個子型別。
- 物件的內容是由屬性組成的,而屬性是由 key-value pair 構成,value 可為任意資料型別的值,並且值是以參考的方式儲存。
- 物件的屬性若包含特殊字符或為動態產生的字串,則必須使用鍵值
[ ]
的方法存取。 - 複製物件的方法分為淺拷貝與深拷貝兩種,
Object.assign
只能處理一層內的深拷貝,而真正的深拷貝通常是由 JSON-safe 的物件先經由序列化為 JSON 字串後再剖析回物件來達成。 - 屬性描述器可用來檢視屬性的特徵,例如:是否可寫入、是否可配置、是否可列舉。
- 實作不可變的物件的方法,例如:設定屬性描述器、避免擴充、密封、凍結。
- 物件預設的
[[Get]]
與[[Put]]
掌控了屬性的建立、設定和更新、取得值的方式,若要複寫這兩種預設[[Get]]
與[[Put]]
行為,可透過取值器與設值器來達成。 - hasOwnProperty 與
prop in obj
可判斷屬性是否存在。 - 判斷屬性是否可列舉的方法-in 運算子帶出此物件可列舉的屬性、propertyIsEnumerable 檢視屬性是否可被列舉、
Object.keys
與Object.getOwnPropertyNames
都只回傳此物件的屬性,差異在於前者只列出可列舉的屬性,而後者會列出所有此物件的屬性。 - 迭代出陣列中的值的方法,例如:forEach、every、some、
for...of
。
References
同步發表於2019 鐵人賽。