《JavaScript面向物件精要》之三:理解物件
JavaScript/">JavaScript 中的物件是動態的,可在程式碼執行的任意時刻發生改變。基於類的語言會根據類的定義鎖定物件。
3.1 定義屬性
當一個屬性第一次被新增到物件時,JavaScript 會在物件上呼叫一個名為[[Put]]
的內部方法。
[[Put]]
方法會在物件上建立一個新節點來儲存屬性。
當一個已有的屬性被賦予一個新值時,呼叫的是一個名為[[Set]]
的方法。
3.2 屬性探測
檢查物件是否已有一個屬性。JavaScript 開發新手錯誤地使用以下模式檢測屬性是否存在。
if(person.age){ // do something with ag } 複製程式碼
上面的問題在於 JavaScript 的型別強制會影響該模式的輸出結果。
當 if 判斷中的值如下時,會判斷為真 :
- 物件
- 非空字串
- 非零
- true
當 if 判斷中的值如下時,會判斷為假 :
- null
- undefined
- false
- NaN
- 空字串
因此判斷屬性是否存在的方法是使用in
操作符。
in
操作符會檢查自有屬性和原型屬性
。
所有的物件都擁有的hasOwnProperty()
方法(其實是Object.prototype
原型物件的),該方法在給定的屬性存在且為自有屬性
時返回true
。
var person = { name: "Nicholas" } console.log("name" in person); // true console.log(person.hasOwnpropert("name")); // true console.log("toString" in person); // true console.log(person.hasOwnproperty("toString")); // false 複製程式碼
3.3 刪除屬性
設定一個屬性的值為null
並不能從物件中徹底移除那個屬性,這只是呼叫[[Set]]
將null
值替換了該屬性原來的值而已。
delete
操作符針對單個物件屬性呼叫名為[[Delete]]
的內部方法。刪除成功時,返回true
。
var person = { name: "Nicholas" } person.name = null; console.log("name" in person); // true delete person.name; console.log(person.name); // undefined 訪問一個不存在的屬性將返回 undefined console.log("name" in person); // false 複製程式碼
3.4 屬性列舉
所有人為新增的屬性預設都是可列舉的。可列舉的內部特徵[[Enumerable]]
都被設定為true
。
for-in
迴圈會列舉一個物件所有的可列舉屬性。
ECMAScript 5 的 Object.keys() 方法可以獲取可列舉屬性的名字的陣列。
var person = { name: "Ljc", age: 18 } Object.keys(person); // ["name", "age"]; 複製程式碼
for-in
與Object.keys()
的一個區別是:前者也會遍歷原型屬性,而後者返回自有(例項)屬性。
實際上,物件的大部分原生方法的[[Enumerable]]
特徵都被設定為false
。可用propertyIsEnumerable()
方法檢查一個屬性是否為可列舉的。
var arr = ["abc", 2]; console.log(arr.propertyIsEnumerable("length")); // false 複製程式碼
3.5 屬性型別
屬性有兩種型別:資料屬性 和訪問器屬性 。
資料屬性
包含一個值。[[Put]]
方法的預設行為是建立資料屬性
。
訪問器屬性
不包含值而是定義了一個當屬性被讀取時呼叫的函式(稱為getter
)和一個當屬性被寫入時呼叫的函式(稱為setter
)。訪問器屬性僅需要getter
或setter
兩者中的任意一個,當然也可以兩者。
// 物件字面形式中定義訪問器屬性有特殊的語法: var person = { _name: "Nicholas", get name(){ console.log("Reading name"); return this._name; }, set name(value){ console.log("Setting name to %s", value); this._name = value; } }; console.log(person.name); // "Reading name" 然後輸出 "Nicholas" person.name = "Greg"; console.log(person.name); // "Setting name to Greg" 然後輸出 "Greg" 複製程式碼
前置下劃線 _ 是一個約定俗成的命名規範,表示該屬性是私有的,實際上它還是公開的。
訪問器就是定義了我們在物件讀取或設定屬性時,觸發的動作(函式),_name
相當於一個內部變數。
當你希望賦值(讀取)操作會觸發一些行為,訪問器就會非常有用。
當只定義 getter 或 setter 其一時,該屬性就會變成只讀或只寫。
3.6 屬性特徵
在 ECMAScript 5 之前沒有辦法指定一個屬性是否可列舉。實際上根本沒有方法訪問屬性的任何內部特徵。
為了改變這點,ECMAScript 5 引入了多種方法來和屬性特徵值直接互動。
3.6.1 通用特徵
資料屬性和訪問器屬性均由以下兩個屬性特製:
[[Enumerable]] [[Configurable]]
所有人為定義的屬性預設都是可列舉、可配置的。
可以用Object.defineProperty()
方法改變屬性特徵。
其引數有三:擁有該屬性的物件、屬性名和包含需要設定的特性的屬性描述物件。
var person = { name: "Nicholas" } Object.defineProperty(person, "name", { enumerable: false }) console.log("name" in person); // true console.log(person.propertyIsEnumerable("name")); // false var properties = Object.keys(person); console.log(properties.length); // 0 Object.defineProperty(person, "name",{ configurable: false }) delete person.name; // false console.log("name" in person); // true Object.defineProperty(person, "name",{ // error! // 在 chrome:Uncaught TypeError: Cannot redefine property: name configurable: true }) 複製程式碼
無法將一個不可配置的屬性變為可配置,相反則可以。
3.6.2 資料屬性特徵
資料屬性額外擁有兩個訪問器屬性不具備的特徵。
[[Value]] [[Writable]]
var person = {}; Object.defineProperty(person, "name", { value: "Nicholas", enumerable: true, configurable: true, writable: true }) 複製程式碼
在Object.defineProperty()
被呼叫時,如果屬性本來就有,則會按照新定義屬性特徵值去覆蓋預設屬性特徵(enumberable
、configurable
和writable
均為true
)。
但如果用該方法定義新的屬性時,沒有為所有的特徵值指定一個值,則所有布林值的特徵值會被預設設定為false
。即不可列舉、不可配置、不可寫的。
當你用Object.defineProperty()
改變一個已有的屬性時,只有你指定的特徵會被改變。
3.6.3 訪問器屬性特徵
訪問器屬性額外擁有兩個特徵。[[Get]]
和[[Set]]
,內含getter
和setter
函式。
使用訪問器屬性特徵比使用物件字面形式定義訪問器屬性的優勢在於:可以為已有的物件定義這些屬性。而後者只能在建立時定義訪問器屬性 。
var person = { _name: "Nicholas" }; Object.defineProperty(person, "name", { get: function(){ return this._name; }, set: function(value){ this._name = value; }, enumerable: true, configurable: true }) for(var x in person){ console.log(x); // _name \n(換行) name(訪問器屬性) } 複製程式碼
設定一個不可配置、不可列舉、不可以寫的屬性:
Object.defineProperty(person, "name",{ get: function(){ return this._name; } }) 複製程式碼
對於一個新的訪問器屬性,沒有顯示設定值為布林值的屬性,預設為false
。
3.6.4 定義多重屬性
Object.defineProperties()
方法可以定義任意數量的屬性,甚至可以同時改變已有的屬性並建立新屬性。
var person = {}; Object.defineProperties(person, { // data property to store data _name: { value: "Nicholas", enumerable: true, configurable: true, writable: true }, // accessor property name: { get: function(){ return this._name; }, set: function(value){ this._name = value; } } }) 複製程式碼
3.6.5 獲取屬性特徵
Object.getOwnPropertyDescriptor()
方法。該方法接受兩個引數:物件和屬性名。如果屬性存在,它會返回一個屬性描述物件,內涵4
個屬性:configurable
和enumerable
,另外兩個屬性則根據屬性型別決定。
var person = { name: "Nicholas" } var descriptor = Object.getOwnPropertyDescriptor(person, "name"); console.log(descriptor.enumerable); // true console.log(descriptor.configuable); // true console.log(descriptor.value); // "Nicholas" console.log(descriptor.wirtable); // true 複製程式碼
3.7 禁止修改物件
物件和屬性一樣具有指導其行為的內部特性。
其中,[[Extensible]]
是布林值,指明該物件本身是否可以被修改。預設是true
。當值為false
時,就能禁止新屬性的新增。
建議在 "use strict"; 嚴格模式下進行。
3.7.1 禁止擴充套件
Object.preventExtensions()
建立一個不可擴充套件的物件(即不能新增新屬性
)。Object.isExtensible()
檢查[[Extensible]]
的值。
var person = { name: "Nocholas" } Object.preventExtensions(person); person.sayName = function(){ console.log(this.name) } console.log("sayName" in person); // false 複製程式碼
3.7.2 物件封印
一個被封印的物件是不可擴充套件的且其所有屬性都是不可配置的(即不能新增、刪除屬性或修改其屬性型別(從資料屬性變成訪問器屬性或相反) )。只能讀寫它的屬性 。
Object.seal()
呼叫此方法後,該物件的[[Extensible]]
特徵被設定為false
,其所有屬性的[[configurable]]
特徵被設定為false
。
Object.isSealed()
判斷一個物件是否被封印。
3.7.3 物件凍結
被凍結的物件不能新增或刪除屬性,不能修改屬性型別,也不能寫入任何資料屬性。簡言而之,被凍結物件是一個資料屬性都為只讀 的被封印物件。
Object.freeze() Object.isFrozen()
Object.freeze()
比Object.seal()
更嚴格,它阻止更改任何現有屬性