首先介紹了JavaScript
物件資料屬性與訪問器屬性的相關概念,然後介紹了屬性定義與讀取的相關方法,最後對JavaScript
物件資料屬性與訪問器屬性的知識做了一些擴充套件,並手動實現了一個簡單的資料雙向繫結例項。
文中如果有疏漏錯誤之處,各位小夥伴可在文末評論中提出,
fanweiren
一定及時修正,以避免誤導。另外,如果對本文有不理解的地方,也歡迎評論提出,fanweiren
一定積極為大家解答。
一. 建立JavaScript物件
建立JavaScript
對簡單的方法有兩種:一是直接建立一個object
例項,然後為他新增屬性與方法,二是通過物件字面量語法的方式建立。
1.1 new object
var person = new Object();
person.name = "Nicholas";
person.age = 29;
person.job = "Software Engineer";
person.sayName = function(){
alert(this.name);
};
複製程式碼
上述例項建立了一個名為person的例項物件,併為其新增了三個屬性(name
,age
,job
)和一個方法(sayName
)。
1.2 物件字面量
上述示例同樣可通過物件字面量的語法實現如下。
var person = {
name: "Nicholas",
age: 29,
job: "Software Engineer",
sayName: function() {
alert(this.name);
}
};
複製程式碼
二. 屬性型別
對於1.1
與1.2
的建立的每個例項都有自己的屬性和方法,這些屬性在建立時都帶有一些特徵值,這些特徵值僅供內部使用,在JavaScript
中不能直接訪問他們,按規定這些特徵值需要放在兩對方括號中,例如[[Enumerable]]
。
ECMAScript
中有兩類屬性,分別是資料屬性
與選擇器屬性
1.1 資料屬性
資料屬性包含一個資料值的位置。在這個位置可以讀取和寫入值。資料屬性有 4 個描述其行為的 特性。
[[Configurable]]
:表示能否通過delete
刪除屬性從而重新定義屬性,能否修改屬性的特性,或者能否把屬性修改為訪問器屬性。[[Enumerable]]
:表示能否通過for-in迴圈返回屬性。[[Writable]]
:表示能否修改屬性的值。[[Value]]
:包含這個屬性的資料值。
通過
new object
或者物件字面量方式建立物件定義的屬性,他們的[[Configurable]]
、[[Enumerable]]
、[[Writable]]
特徵均為true
,而[[Value]]
值被設定為指定的值。
var person = {
name: "Nicholas"
}
複製程式碼
以上程式碼通過物件字面量的方式定義了一個person
物件,併為它指定了name
屬性,此時name
屬性的四個特徵值,[[Configurable]]
、[[Enumerable]]
、[[Writable]]
特徵均為true
,而[[Value]]
為Nicholas
。
要修改屬性預設的特性,必須使用Object.defineProperty()方法,可同時修改一個或多個特性值,該方法接收三個引數:
object
:屬性所在的物件propertyName
:屬性的名字descripor
:屬性的描述符,必須是[[Configurable]]
、[[Enumerable]]
、[[Writable]]
、[[Value]]
中的一個或者多個值。
var person = {
name: fanweiren
};
Object.defineProperty(person, "name", {
writable: false,
value: "Nicholas"
});
alert(person.name); //"Nicholas"
person.name = "Greg";
alert(person.name); //"Nicholas"
複製程式碼
以上程式碼將name
屬性的值設定為設定為Nicholas
,並將name
屬性的[[writable]]
特性值設為false
,表示該屬性只可讀不可修改。如果嘗試為它指定新值,非嚴格模式下,賦值操作會被忽略,在嚴格模式下,會丟擲錯誤。
var person = {
name: fanweiren
};
Object.defineProperty(person, "name", {
configurable: false,
value: "Nicholas"
});
alert(person.name); //"Nicholas"
delete person.name;
alert(person.name); //"Nicholas"
複製程式碼
把[[Configurable]]
特性設定為false後,屬性name
既不能從物件中刪除,此時呼叫delete在非嚴格模式下什麼也不會發生,在嚴格模式下會丟擲錯誤。
一旦把
[[Configurable]]
特性設定為false即不可配置後,就不能在變回可配置了。且設定為false後,[[Enumerable]]
、[[Writable]]
也都不能修改了。
發現一個奇怪的現象,
[[Configurable]]
特性設定為false
後,如果[[Writable]]
以前為true
,則還可以將其修改為false
,如果[[Writable]]
以前為false
,則不可修改。
1.2 訪問器屬性
訪問器屬性不包含資料值;它們包含一對兒getter
和setter
函式(不過,這兩個函式都不是必需的)。在讀取訪問器屬性時,會呼叫getter
函式,這個函式負責返回有效的值;在寫入訪問器屬性時,會呼叫
setter
函式並傳入新值,這個函式負責決定如何處理資料。訪問器屬性有如下 4 個特性。
[[Configurable]]
:特性同1.1資料屬性的該特徵值[[Enumerable]]
:特性同1.1資料屬性的該特徵值[[Get]]
:在讀取屬性時呼叫的函式。預設值為 undefined。[[Set]]
:在寫入屬性時呼叫的函式。預設值為 undefined。
訪問器屬性不能直接定義,必須使用 Object.defineProperty()
來定義。請看下面的例子。
var book = {
hideYear: 2004,
edition: 1
};
Object.defineProperty(book, "year", {
get: function(){
return this.hideYear;
},
set: function(newValue){
if (newValue > 2004) {
this.hideYear = newValue;
this.edition += newValue - 2004;
}
}
});
book.year = 2005;
alert(book.edition); //2
複製程式碼
以上程式碼定義了hideYear
與edition
兩個資料屬性,同時定義了year
這個訪問器屬性。year
訪問器屬性包含get
與set
兩個方法,get
方法返回hideYear
屬性的值,set
方法通過計算來確定正確的版本。
get和set可以不同時使用,但在嚴格模式下只其中一個,會丟擲錯誤
三點總結:
- 所有直接通過
new object
或者物件字面量為物件新增的屬性均為資料屬性,訪問器屬性必須用Object.defineProperty()
定義。 - 通過
new object
或者物件字面量為物件新增的屬性,其[[Configurable]]
、[[Enumerable]]
、[[Writable]]
屬性預設均為true
,通過Object.defineProperty()
定義的屬性,對於defineProperty
方法沒定義的特徵,預設為false
。
// 通過`new object`或者物件字面量為物件新增的屬性
var person = {
name: 'fanweiren'
} // configurable、enumerable、writable均為true
// 通過`Object.defineProperty()`定義的屬性
Object.defineProperty(book, "name", {
value: 'fanweiren'
} // configurable、enumerable、writable均為false
Object.defineProperty(book, "name", {
configurable: true
value: 'fanweiren'
}// configurable為true,enumerable、writable為false
複製程式碼
- 一個屬性要麼為資料屬性,要麼為訪問器屬性,不可能既是資料屬性又是訪問器屬性,即資料描述符與存取描述符不可混用,會丟擲錯誤。
var obj = {};
Object.defineProperty(obj, 'a', {
value: 'a1',
get: function() {
return 'a2'
} // 錯誤!資料描述符與存取描述符不可混用
});
複製程式碼
- 訪問器屬性使用的常見方式,即設定一個值會導致其他屬性發生變化,該思想是
vue
雙向繫結資料實現的核心內涵。
三. 定義多個屬性
利用Object.defineProperty()
定義單個屬性。利用Object.defineProperties()
同時定義多個屬性,該方法接收兩個引數,具體使用方式見如下程式碼。
var book = {};
Object.defineProperties(book, {
_year: {
value: 2004
},
edition: {
value: 1
},
year: {
get: function(){
return this._year;
},
set: function(newValue){
if (newValue > 2004) {
this._year = newValue;
this.edition += newValue - 2004;
}
}
}
});
複製程式碼
以上程式碼為物件book
定義了三個屬性,其中_year
與edition
為資料屬性,year
為訪問器屬性。
四. 讀取資料的屬性
Object.getOwnPropertyDescriptor()
方法可以取得給定屬性的描述符。這個方法返回值是一個物件,如果是訪問器屬性,這個物件的屬性有configurable
、enumerable
、get
和set
;如果是資料屬性,這
個物件的屬性有configurable
、enumerable
、writable
和value
。對於第三部分(定義多個屬性)的程式碼,呼叫Object.getOwnPropertyDescriptor()
方法後結果如下:
var descriptor = Object.getOwnPropertyDescriptor(book, "_year");
console.log(JSON.stringify(descriptor))
//{"value":2004,"writable":false,"enumerable":false,"configurable":false}
alert(typeof descriptor.get); //"undefined"
var descriptor = Object.getOwnPropertyDescriptor(book, "year");
console.log(JSON.stringify(descriptor)) // {"enumerable":false,"configurable":false}
alert(typeof descriptor.get); // function
alert(typeof descriptor.set); // function
複製程式碼
五 相關擴充套件
5.1 全域性環境中的賦值導致configurable
預設為false
全域性環境下,a=1
中的a
相當於window
的一個屬性,而var a=1
中的a
相當於定義的一個變數。
a = 1; //a是其所在物件的一個屬性, configurable與物件字面量定義屬性一樣,預設為true
複製程式碼
var a = 1; // 通過var或let初始化的,configurable屬性預設為false
複製程式碼
5.2. 利用訪問器屬性實現簡單的資料雙向繫結
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<input id="input"/></br>
<button id="btn">點我修改值</button>
<script>
let inputNode = document.getElementById('input');
let person = {}
Object.defineProperty(person, 'name' ,{
configurable: true,
get: function () {
console.log('person.name.get():'+ inputNode.value)
return inputNode.value
},
set: function (newValue) {
console.log('person.name.set():' + newValue)
inputNode.value = newValue
}
})
inputNode.oninput = function () {
console.log('inputNode.oninput: ' + inputNode.value)
person.name = inputNode.value;
}
let btn = document.getElementById('btn');
btn.onclick = function () {
person.name = Math.random()
}
</script>
</body>
</html>
複製程式碼
以上程式碼實現了一個將input
輸入框的資料與perosn
物件的name
屬性值繫結的例項,input
輸入框內的資料發生改變會同步影響perosn.name
的值,而對perosn.name
的任何修改也會同步到input
輸入框內。
注意是將
input
與perosn.name
資料繫結,而不是input
與button
繫結,button
的作用僅僅是改變perosn.name
的值,方便演示而已。 具體步驟為
- 首先寫一個簡單HTML頁面,包括一個
input
輸入框與一個button
按鈕 - 為
perosn.name
定義訪問器屬性的特性,重寫set
與get
方法,set
方法會獲取input
輸入框的值並返回,而set
方法會為input
輸入框重新賦值。當perosn.name
被賦值時,input
輸入框的值會同步變化。 - 為
input
輸入框新增監聽方法,input
輸入值發生改變時會被觸發,觸發此方法時input
內的值會被賦值給perosn.name
,導致perosn.name
同步變化。 - 為
btn
按鈕新增點選的監聽方法,主要是為了方便為perosn.name
賦值,以觀察第3步所說的現象。如果要觀察第2步的現象,可以按F12
觀察console
頁面的列印資料。
下圖為上述程式碼的示意圖。
六. 總結
本文從物件的建立引出物件資料屬性與訪問器屬性的概念,首先介紹了資料屬性與訪問器屬性的相關知識,然後介紹了屬性定義與讀取的相關方法。主要結論如下:
javascript
物件的屬性分為資料屬性與訪問器屬性兩類,通過new object
與物件字面量定義的屬性都為資料屬性,訪問器屬性必須通過Object.defineProperty()
方法定義。- 通過
new object
或者物件字面量為物件新增的屬性,其[[Configurable]]
、[[Enumerable]]
、[[Writable]]
屬性預設均為true
,通過Object.defineProperty()
定義的屬性,對於defineProperty
方法沒定義的特徵,預設為false
。 - 一個屬性要麼為資料屬性,要麼為訪問器屬性,不可能既是資料屬性又是訪問器屬性。
- 訪問器屬性使用的常見方式,即設定一個值會導致其他屬性發生變化,該思想是vue雙向繫結資料實現的核心內涵。
[[Configurable]]
表示能否通過delete刪除屬性從而重新定義屬性,能否修改屬性的特性,或者能否把屬性修改為訪問器屬性,[[Enumerable]]
表示能否通過for-in
迴圈返回屬性。