JavaScript對象的數據屬性與訪問器屬性

首先介紹了JavaScript對象數據屬性與訪問器屬性的相關概念,而後介紹了屬性定義與讀取的相關方法,最後對JavaScript對象數據屬性與訪問器屬性的知識作了一些擴展,並手動實現了一個簡單的數據雙向綁定實例。javascript

文中若是有疏漏錯誤之處,各位可在文末評論中提出。html

一. 建立JavaScript對象

建立JavaScript對簡單的方法有兩種:一是直接建立一個object實例,而後爲他添加屬性與方法,二是經過對象字面量語法的方式建立。vue

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的實例對象,併爲其添加了三個屬性(nameagejob)和一個方法(sayName)。java

1.2 對象字面量

上述示例一樣可經過對象字面量的語法實現以下。dom

var person = {
    name: "Nicholas",
    age: 29,
    job: "Software Engineer",
    sayName: function() {
        alert(this.name);
    }
};
複製代碼

二. 屬性類型

對於1.11.2的建立的每一個實例都有本身的屬性和方法,這些屬性在建立時都帶有一些特徵值,這些特徵值僅供內部使用,在JavaScript中不能直接訪問他們,按規定這些特徵值須要放在兩對方括號中,例如[[Enumerable]]ide

ECMAScript中有兩類屬性,分別是數據屬性選擇器屬性函數

1.1 數據屬性

數據屬性包含一個數據值的位置。在這個位置能夠讀取和寫入值。數據屬性有 4 個描述其行爲的 特性。ui

  • [[Configurable]]:表示可否經過delete刪除屬性從而從新定義屬性,可否修改屬性的特性,或者可否把屬性修改成訪問器屬性。
  • [[Enumerable]]:表示可否經過for-in循環返回屬性。
  • [[Writable]]:表示可否修改屬性的值。
  • [[Value]]:包含這個屬性的數據值。

經過new object或者對象字面量方式建立對象定義的屬性,他們的[[Configurable]][[Enumerable]][[Writable]]特徵均爲true,而[[Value]]值被設置爲指定的值。this

var person = {
    name: "Nicholas"
}
複製代碼

以上代碼經過對象字面量的方式定義了一個person對象,併爲它指定了name屬性,此時name屬性的四個特徵值,[[Configurable]][[Enumerable]][[Writable]]特徵均爲true,而[[Value]]Nicholasspa

要修改屬性默認的特性,必須使用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 訪問器屬性

訪問器屬性不包含數據值;它們包含一對兒gettersetter 函數(不過,這兩個函數都不是必需的)。在讀取訪問器屬性時,會調用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
複製代碼

以上代碼定義了hideYearedition兩個數據屬性,同時定義了year這個訪問器屬性。year訪問器屬性包含getset兩個方法,get方法返回hideYear屬性的值,set方法經過計算來肯定正確的版本。

get和set能夠不一樣時使用,但在嚴格模式下只其中一個,會拋出錯誤

三點總結:

  1. 全部直接經過new object或者對象字面量爲對象添加的屬性均爲數據屬性,訪問器屬性必須用Object.defineProperty()定義。
  2. 經過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
複製代碼
  1. 一個屬性要麼爲數據屬性,要麼爲訪問器屬性,不可能既是數據屬性又是訪問器屬性,即數據描述符與存取描述符不可混用,會拋出錯誤。
var obj = {};
Object.defineProperty(obj, 'a', {
    value: 'a1',
    get: function() {
       return 'a2'
    } // 錯誤!數據描述符與存取描述符不可混用
});
複製代碼
  1. 訪問器屬性使用的常見方式,即設置一個值會致使其餘屬性發生變化,該思想是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定義了三個屬性,其中_yearedition爲數據屬性,year爲訪問器屬性。

四. 讀取數據的屬性

Object.getOwnPropertyDescriptor()方法能夠取得給定屬性的描述符。這個方法返回值是一個對象,若是是訪問器屬性,這個對象的屬性有configurableenumerablegetset;若是是數據屬性,這 個對象的屬性有configurableenumerablewritablevalue。對於第三部分(定義多個屬性)的代碼,調用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輸入框內。

注意是將inputperosn.name數據綁定,而不是inputbutton綁定,button的做用僅僅是改變perosn.name的值,方便演示而已。 具體步驟爲

  1. 首先寫一個簡單HTML頁面,包括一個input輸入框與一個button按鈕
  2. perosn.name定義訪問器屬性的特性,重寫setget方法,set方法會獲取input輸入框的值並返回,而set方法會爲input輸入框從新賦值。當perosn.name被賦值時,input輸入框的值會同步變化。
  3. input輸入框添加監聽方法,input輸入值發生改變時會被觸發,觸發此方法時input內的值會被賦值給perosn.name,致使perosn.name同步變化。
  4. btn按鈕添加點擊的監聽方法,主要是爲了方便爲perosn.name賦值,以觀察第3步所說的現象。若是要觀察第2步的現象,能夠按F12觀察console頁面的打印數據。

下圖爲上述代碼的示意圖。

六. 總結

本文從對象的建立引出對象數據屬性與訪問器屬性的概念,首先介紹了數據屬性與訪問器屬性的相關知識,而後介紹了屬性定義與讀取的相關方法。主要結論以下:

  1. javascript對象的屬性分爲數據屬性與訪問器屬性兩類,經過new object與對象字面量定義的屬性都爲數據屬性,訪問器屬性必須經過Object.defineProperty()方法定義。
  2. 經過new object或者對象字面量爲對象添加的屬性,其[[Configurable]][[Enumerable]][[Writable]]屬性默認均爲true,經過Object.defineProperty()定義的屬性,對於defineProperty方法沒定義的特徵,默認爲false
  3. 一個屬性要麼爲數據屬性,要麼爲訪問器屬性,不可能既是數據屬性又是訪問器屬性。
  4. 訪問器屬性使用的常見方式,即設置一個值會致使其餘屬性發生變化,該思想是vue雙向綁定數據實現的核心內涵。
  5. [[Configurable]]表示可否經過delete刪除屬性從而從新定義屬性,可否修改屬性的特性,或者可否把屬性修改成訪問器屬性,[[Enumerable]]表示可否經過for-in循環返回屬性。
相關文章
相關標籤/搜索