JavaScript 中的對象是動態的,可在代碼執行的任意時刻發生改變。基於類的語言會根據類的定義鎖定對象。chrome
當一個屬性第一次被添加到對象時,JavaScript 會在對象上調用一個名爲 [[Put]]
的內部方法。數組
[[Put]]
方法會在對象上建立一個新節點來保存屬性。函數
當一個已有的屬性被賦予一個新值時,調用的是一個名爲 [[Set]]
的方法。ui
檢查對象是否已有一個屬性。JavaScript 開發新手錯誤地使用如下模式檢測屬性是否存在。this
if(person.age){
// do something with ag
}
複製代碼
上面的問題在於 JavaScript 的類型強制會影響該模式的輸出結果。
當 if 判斷中的值以下時,會判斷爲真:spa
當 if 判斷中的值以下時,會判斷爲假:prototype
所以判斷屬性是否存在的方法是使用 in
操做符。code
in
操做符會檢查自有屬性和原型屬性。對象
全部的對象都擁有的 hasOwnProperty()
方法(實際上是 Object.prototype
原型對象的),該方法在給定的屬性存在且爲自有屬性時返回 true
。ip
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
複製代碼
設置一個屬性的值爲 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
複製代碼
全部人爲添加的屬性默認都是可枚舉的。可枚舉的內部特徵 [[Enumerable]]
都被設置爲 true
。
for-in
循環會枚舉一個對象全部的可枚舉屬性。
ECMAScript 5 的 Object.keys() 方法能夠獲取可枚舉屬性的名字的數組。
var person = {
name: "Nicholas",
age: 18
}
Object.keys(person); // ["name", "age"];
複製代碼
for-in
與 Object.keys()
的一個區別是:前者也會遍歷原型屬性,然後者返回自有(實例)屬性。
實際上,對象的大部分原生方法的 [[Enumerable]]
特徵都被設置爲 false
。可用 propertyIsEnumerable()
方法檢查一個屬性是否爲可枚舉的。
var arr = ["abc", 2];
console.log(arr.propertyIsEnumerable("length")); // false
複製代碼
屬性有兩種類型:數據屬性和訪問器屬性。
數據屬性
包含一個值。[[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 其一時,該屬性就會變成只讀或只寫。
在 ECMAScript 5 以前沒有辦法指定一個屬性是否可枚舉。實際上根本沒有方法訪問屬性的任何內部特徵。
爲了改變這點,ECMAScript 5 引入了多種方法來和屬性特徵值直接互動。
數據屬性和訪問器屬性均由如下兩個屬性特製:
[[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
})
複製代碼
沒法將一個不可配置的屬性變爲可配置,相反則能夠。
數據屬性額外擁有兩個訪問器屬性不具有的特徵。
[[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()
改變一個已有的屬性時,只有你指定的特徵會被改變。
訪問器屬性額外擁有兩個特徵。[[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
。
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;
}
}
})
複製代碼
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
複製代碼
對象和屬性同樣具備指導其行爲的內部特性。
其中, [[Extensible]]
是布爾值,指明該對象自己是否能夠被修改。默認是 true
。當值爲 false
時,就能禁止新屬性的添加。
建議在 "use strict"; 嚴格模式下進行。
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
複製代碼
一個被封印的對象是不可擴展的且其全部屬性都是不可配置的(即不能添加、刪除屬性或修改其屬性類型(從數據屬性變成訪問器屬性或相反))。只能讀寫它的屬性。
Object.seal()
調用此方法後,該對象的 [[Extensible]]
特徵被設置爲 false
,其全部屬性的 [[configurable]]
特徵被設置爲 false
。
Object.isSealed()
判斷一個對象是否被封印。
被凍結的對象不能添加或刪除屬性,不能修改屬性類型,也不能寫入任何數據屬性。簡言而之,被凍結對象是一個數據屬性都爲只讀的被封印對象。
Object.freeze()
凍結對象。Object.isFrozen()
判斷對象是否被凍結。
Object.freeze()
比Object.seal()
更嚴格,它阻止更改任何現有屬性
in
操做符檢測自有屬性和原型屬性,而 hasOwnProperty()
只檢查自有屬性。delete
操做符刪除對象屬性。[[Enumerable]]
和 [[Configurable]]
的兩種屬性都有的,而數據屬性還有 [[Value]]
和 [[Writable]]
,訪問器屬性還有 [[Get]]
和 [[Set]]
。可經過 Object.defineProperty()
和 Object.defineProperties()
改變這些特徵。用 Object.getOwnPropertyDescriptor()
獲取它們。3
種能夠鎖定對象屬性的方式。