絕對乾貨!徹底理解Js物件導向程式設計(一)

mpsky發表於2021-09-09

物件導向(Object-Oriented,OO)的語言有一個標誌,那就是它們都有類的概念,而透過類可
以建立任意多個具有相同屬性和方法的物件。前面提到過,ECMAScript 中沒有類的概念,因
此它的物件也與基於類的語言中的物件有所不同。
ECMA-262 把物件定義為:“無序屬性的集合,其屬性可以包含基本值、物件或者函式。”嚴格來講,
這就相當於說物件是一組沒有特定順序的值。物件的每個屬性或方法都有一個名字,而每個名字都對映
到一個值。正因為這樣(以及其他將要討論的原因),我們可以把 ECMAScript 的物件想象成雜湊表:無
非就是一組名值對,其中值可以是資料或函式。
每個物件都是基於一個引用型別建立的,這個引用型別可以是原生型別,也可以是開發人員定義的型別。

理解屬性型別

在ES5中定義了特性(attribute)描述了屬性(property)的各種特徵。
定義這些特性是為了實現 JavaScript 引擎用的,因此在 JavaScript 中不能直接訪問它們。為了
表示特性是內部值,該規範把它們放在了兩對兒方括號中,例如[[Enumerable]]。
ECMAScript 中有兩種屬性:資料屬性和訪問器屬性。

1. 資料屬性

資料屬性包含一個資料值的位置。在這個位置可以讀取和寫入值。資料屬性有 4 個描述其行為的特性。

  • [[Configurable]]:表示能否透過 delete 刪除屬性從而重新定義屬性,能否修改屬性的特
    性,或者能否把屬性修改為訪問器屬性。像前面例子中那樣直接在物件上定義的屬性,它們的
    這個特性預設值為 true。

  • [[Enumerable]]:表示能否透過 for-in 迴圈返回屬性。像前面例子中那樣直接在物件上定
    義的屬性,它們的這個特性預設值為 true。

  • [[Writable]]:表示能否修改屬性的值。像前面例子中那樣直接在物件上定義的屬性,它們的
    這個特性預設值為 true。

  • [[Value]]:包含這個屬性的資料值。讀取屬性值的時候,從這個位置讀;寫入屬性值的時候,
    把新值儲存在這個位置。這個特性的預設值為 undefined。
    對於像前面例子中那樣直接在物件上定義的屬性,它們的[[Configurable]]、[[Enumerable]]
    和[[Writable]]特性都被設定為 true,而[[Value]]特性被設定為指定的值。例如:
    var person = {
    name: "Nicholas"
    }
    這裡建立了一個名為 name 的屬性,為它指定的值是"Nicholas"。也就是說,[[Value]]特性將
    被設定為"Nicholas",而對這個值的任何修改都將反映在這個位置。
    要修改屬性預設的特性,必須使用 ECMAScript 5 的 Object.defineProperty()方法。這個方法
    接收三個引數:屬性所在的物件、屬性的名字和一個描述符物件。其中,描述符(descriptor)物件的屬
    性必須是:configurable、enumerable、writable 和 value。設定其中的一或多個值,可以修改
    對應的特性值。例如:

let person = {}; 
Object.defineProperty(person, "name", {     writable: false,     value: "Nicholas"  }); 

alert(person.name);  //"Nicholas" person.name = "Greg"; 
alert(person.name);  //"Nicholas"

這個例子建立了一個名為 name 的屬性,它的值"Nicholas"是隻讀的。這個屬性的值是不可修改 的,如果嘗試為它指定新值,則在非嚴格模式下,賦值操作將被忽略;在嚴格模式下,賦值操作將會導 致丟擲錯誤。
類似的規則也適用於不可配置的屬性。例如:

var person = {}; 
Object.defineProperty(person, "name", {     configurable: false,     value: "Nicholas" }); 

alert(person.name);  //"Nicholas" delete person.name; 
alert(person.name);  //"Nicholas

把 configurable 設定為 false,表示不能從物件中刪除屬性。如果對這個屬性呼叫 delete,則 在非嚴格模式下什麼也不會發生,而在嚴格模式下會導致錯誤。而且,一旦把屬性定義為不可配置的, 就不能再把它變回可配置了。此時,再呼叫 Object.defineProperty()方法修改除 writable 之外 的特性,都會導致錯誤

var person = {};Object.defineProperty(person, "name", {     configurable: false,     value: "Nicholas" }); 
Object.defineProperty(person, "name", {     configurable: true,     value: "Nicholas" });  //丟擲錯誤

也就是說,可以多次呼叫 Object.defineProperty()方法修改同一個屬性,但在把 configurable 特性設定為 false 之後就會有限制了。 在呼叫 Object.defineProperty()方法時,如果不指定,configurable、enumerable 和 writable 特性的預設值都是 false。多數情況下,可能都沒有必要利用 Object.defineProperty() 方法提供的這些高階功能。不過,理解這些概念對理解 JavaScript物件卻非常有用。

2.訪問器屬性

訪問器屬性不包含資料值;它們包含一對兒 getter和 setter函式(不過,這兩個函式都不是必需的)。 在讀取訪問器屬性時,會呼叫 getter函式,這個函式負責返回有效的值;在寫入訪問器屬性時,會呼叫 setter函式並傳入新值,這個函式負責決定如何處理資料。訪問器屬性有如下 4個特性:

  • [[Configurable]]:表示能否透過 delete 刪除屬性從而重新定義屬性,能否修改屬性的特 性,或者能否把屬性修改為資料屬性。對於直接在物件上定義的屬性,這個特性的預設值為 true。

  • [[Enumerable]]:表示能否透過 for-in 迴圈返回屬性。對於直接在物件上定義的屬性,這 個特性的預設值為 true。

  • [[Get]]:在讀取屬性時呼叫的函式。預設值為 undefined。

  • [[Set]]:在寫入屬性時呼叫的函式。預設值為 undefined。 訪問器屬性不能直接定義,必須使用 Object.defineProperty()來定義。請看下面的例子:

let book = { _year: 2004, edition: 1 }; 
Object.defineProperty(book, "year",
 {   get: function() {     return this._year;
   },     
   set: function(newValue) { 
     if (newValue > 2004) {            this._year = newValue;             
            this.edition += newValue - 2004;        
      }     
   }
}); 
book.year = 2005; 
alert(book.edition);  //2

以上程式碼建立了一個 book 物件,並給它定義兩個預設的屬性:_year 和 edition。_year 前面 的下劃線是一種常用的記號,用於表示只能透過物件方法訪問的屬性。而訪問器屬性 year 則包含一個 getter函式和一個 setter函式。getter函式返回_year 的值,setter函式透過計算來確定正確的版本。因此, 把 year 屬性修改為 2005會導致_year 變成 2005,而 edition 變為 2。這是使用訪問器屬性的常見方 式,即設定一個屬性的值會導致其他屬性發生變化。
不一定非要同時指定 getter和 setter。只指定 getter意味著屬性是不能寫,嘗試寫入屬性會被忽略。 在嚴格模式下,嘗試寫入只指定了 getter 函式的屬性會丟擲錯誤。類似地,只指定 setter 函式的屬性也 不能讀,否則在非嚴格模式下會返回 undefined,而在嚴格模式下會丟擲錯誤。

3.定義多個屬性

由於為物件定義多個屬性的可能性很大,ECMAScript 5 又定義了一個 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.defineProperties()方法的瀏覽器有 IE9+、Firefox 4+、Safari 5+、Opera 12+和 Chrome。

6.1.3 讀取屬性的特性

使用 ECMAScript 5的 Object.getOwnPropertyDescriptor()方法,可以取得給定屬性的描述 符。這個方法接收兩個引數:屬性所在的物件和要讀取其描述符的屬性名稱。返回值是一個物件,如果 是訪問器屬性,這個物件的屬性有 configurable、enumerable、get 和 set;如果是資料屬性,這 個物件的屬性有 configurable、enumerable、writable 和 value。例如:

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; 
            } 
         } 
     } 
 }); 
var descriptor = Object.getOwnPropertyDescriptor(book, "_year"); 
alert(descriptor.value);         //2004alert(descriptor.configurable); //false alert(typeof descriptor.get);    //"undefined"var descriptor = Object.getOwnPropertyDescriptor(book, "year");
alert(descriptor.value);        //undefined alert(descriptor.enumerable);   //falsealert(typeof descriptor.get);   //"function"

對於資料屬性_year,value 等於初的值,configurable 是 false,而 get 等於 undefined。 對於訪問器屬性 year,value 等於 undefined,enumerable 是 false,而 get 是一個指向 getter 函式的指標。 在JavaScript中,可以針對任何物件——包括 DOM和BOM物件,使用 Object.getOwnPropertyDescriptor()方法。支援這個方法的瀏覽器有 IE9+、Firefox 4+、Safari 5+、Opera 12+和 Chrome。



作者:alex夏夜
連結:


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/1806/viewspace-2814871/,如需轉載,請註明出處,否則將追究法律責任。

相關文章