深入理解JavaScript中的屬性和特性

ArdenBNFQ 8年前發布 | 6K 次閱讀 JavaScript開發 JavaScript

JavaScript中屬性和特性是完全不同的兩個概念,這里我將根據自己所學,來深入理解JavaScript中的屬性和特性。

主要內容如下:

  • 理解JavaScript中理解對象的本質、理解對象與類的關系、對象與引用類型的關系

  • 對象屬性如何進行分類

  • 屬性中特性的理解

第一部分:理解JavaScript中理解對象的本質、理解對象與類的關系、對象與引用類型的關系

對象的本質: ECMA-262把對象定義為:無序屬性的集合,其屬性可以包含基本值、對象或者函數 。即對象是一組沒有特定順序的值,對象的每個屬性或方法都有一個名字,而這個名字都映射到一個值。故對象的本質是一個散列表:其中是一組名值對,值可以是數據或函數。、

對象和類的關系: 在JavaScript中,對象和類沒有任何關系 。這是因為 ECMAScript中根本就沒有類的概念,它的對象與其他基于類的語言中的對象是不同的 。

對象和引用類型的關系: 對象和引用類型并不是等價的,因為每個對象都是基于一個引用類型創建的 。

第二部分:對象屬性如何進行分類

由構造函數或對象字面量方法創建的對象中具有屬性和方法(只要提到屬性和方法,它們一定是屬于對象的;只要提到對象,它一定是具有屬性和方法的(自定義除外)),其中 屬性又可分為數據屬性和訪問器屬性,他們的區別如下:

  • 數據屬性一般用于存儲數據數值,訪問器屬性不包含數據值

  • 訪問器屬性多用于get/set操作

第三部分:屬性中特性的理解

ECMAScript為了描述對象屬性(property)的各種特征,定義了特性(attribute)這個概念。也就是說特性不同于屬性,特性是為了描述屬性的。下面,我將分別講解:

  • 數據屬性及其特性

  • 訪問器屬性及其特性

  • 如何利用Object.defineProperties()方法定義多個特性

  • 如何利用Object.getOwnPropertyDescripter()方法讀取屬性的描述符以讀取屬性的特性

1.數據屬性及其特性

剛剛我們說過,數據屬性是用于存儲數據數值的,因此數據屬性具有一個數據值的位置,在這個位置可以讀取和寫入值。數據屬性有4個描述其行為的特性,由于ECMAScript規定:在JavaScript中不能 直接 訪問屬性的特性(注意:不是不能訪問),所以我們把它放在兩組方括號中。如下:

  • [[Configurable]]:默認值為true,a、表示能否通過delete刪除屬性從而重新定義屬性 b、能否修改屬性的特性 c、 能夠把屬性由數據屬性修改為訪問器屬性

  • [[Enumerable]]:默認值為true,表示能否通過for-in循環返回該屬性(所以:如果為false,那么for-in循環沒法枚舉它所在的屬性)

  • [[Writable]]:默認值為true,表示能否 修改屬性的值, 這是與[[Configurable]]不同之處。

  • [[Value]]:默認值為undefined,這個值即為屬性的屬性值,我們可以在這個位置上讀取屬性值,也可以在這個位置上寫入屬性值。

  • 注意:上述的默認是指通過構造函數或對象字面量創建的對象所自身擁有的屬性,而不是下面要介紹的Object.defineProperty()方法

這些特性都具有默認值,但是如果這些默認值不是我們想要的,該怎么辦呢?當然就是修改啦!我們可以通過Object.defineProperty()方法來修改屬性默認的特性。英文difineProperty即為定義屬性的意思。 這個方法接收三個參數:屬性所在的對象、屬性的名字和一個描述符對象。 其中第三個參數描述符對象是對象字面量的方法創建的,里面的屬性和屬性值實際上保存的是要修改的特性和特性值。

下面通過幾個例子來深入理解。

a

var person={};
        Object.defineProperty(person,"name",{
            writable:false,
            value:"zhuzhenwei"
        });
        console.log(person.name);//zhuzhenwei
        person.name="heting";
        console.log(person.name);//zhuzhenwei

這里我用對象字面量的方法創建了一個對象,但是沒有同時創建方法和屬性。而是利用了Object.defineProperty()方法來創建了屬性和修改了默認值。這里將writable設置為false,于是后面我試圖修改person.name時,是無效的。

b

var person={};
        Object.defineProperty(person,"name",{
            value:"zhuzhenwei"
        });
        console.log(person.name);//zhuzhenwei
        person.name="heting";
        console.log(person.name);//zhuzhenwei

注意看這個例子,這個例子中我刪去了writable:false,為什么還是不能修改呢?這是因為之前我在介紹特性時,前三個默認為ture,是在創建對象并創建屬性的情況下得到的。對于通過調用Object.defineProperty()方法創建的屬性,其前三個特性的默認值均為false,這里需要注意。

c

var person={};
        Object.defineProperty(person,"name",{
            value:"zhuzhenwei",
            configurable:false
        });
        console.log(person.name);//zhuzhenwei
        delete person.name;
        console.log(person.name);//zhuzhenwei

這里我們將新建的屬性name的特性設置為了configurable:false;因此下面刪除屬性的操作是無效的。根據b,可知configurable,默認就是false,即使去掉也不可修改。

d

var person={};
        Object.defineProperty(person,"name",{
            value:"zhuzhenwei",
            configurable:true
        });
        console.log(person.name);//zhuzhenwei
        delete person.name;
        console.log(person.name);//undefined

在這里我將默認的configurable的值由默認的false修改為了true,于是變成了可配置的,那么最后就成功刪除了。

e

var person={};
        Object.defineProperty(person,"name",{
            value:"zhuzhenwei",
            configurable:false
        });
        console.log(person.name);//zhuzhenwei
        Object.defineProperty(person,"name",{
            value:"zhuzhenwei",
            configurable:true
        });
        console.log(person.name);//Uncaught TypeError: Cannot redefine property: name(…)

如果之前已經設置成為了false,那么后面再改成true也是徒勞的,即: 一旦把屬性設置成為不可配置的,就不能再把它變回可配置了。

f

console.log(person.name);//Uncaught TypeError: Cannot redefine property: name(…)
        var person={};
        Object.defineProperty(person,"name",{
            value:"zhuzhenwei",
        });
        console.log(person.name);//zhuzhenwei
        Object.defineProperty(person,"name",{
            value:"zhuzhenwei",
            configurable:true
        });
        console.log(person.name);//Uncaught TypeError: Cannot redefine property: name(…)

這里可以說明,即使前一步我們不管默認的configurable:false,后面得到的仍是不可配置。于是, 可以得出結論,為了可配置,必須在第一次調用Object.defineProperty()函數時就將默認的值修改為true。

2.訪問器屬性及其特性

之前提到,訪問器屬性不包含數據值,他們包含一對而getter函數和setter函數(這兩個函數不是必須的)。在讀取訪問器屬性時,會調用getter函數,這個函數負責返回有效的值;在寫入訪問器屬性是,會調用setter函數并傳入新值,這個函數負責決定如何處理數據。同樣,由于不能通過JavaScript來直接訪問得到訪問器屬性的特性,所以下面列出的特性將由[[]]括起來以作區分。

  • [[Configurable]]:默認值為true,a、表示能否通過delete刪除屬性從而重新定義屬性 b、能否修改屬性的特性 c、 能夠把屬性由訪問器屬性修改為數據屬性
  • [[Enumerable]]:默認值為true,表示能否通過for-in循環返回該屬性(所以:如果為false,那么for-in循環沒法枚舉它所在的屬性)
  • [[Get]]: 在讀取屬性時調用的 函數 。默認值為undefined  關鍵:特性可以是一個函數
  • [[Set]]:   在寫入屬性時調用的 函數 。默認值為undefined 關鍵:特性可以是一個函數 由于get和set函數也屬于屬性的特性,那么他們就有可能(說有可能是因為這兩個函數也不是必須的)出現在Object.defineproperty的第三個參數描述符對象的屬性中。

注意:1.相對于數據屬性,我們發現訪問器屬性中沒有writable特性和value特性。 這是因為訪問器屬性不包含數據值 ,那么我們怎么當然就不可修改屬性的值(用不到writable特性),更不用考慮value了。

2.訪問器屬性不能直接定義,必須是用Object.defineProperty()來定義。(通過這個規定我們就能準確地判斷出訪問器屬性和數據屬性了)

通過下面這個例子來深入理解:

var 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;
    console.log(book.edition);//2

幾個需要深入理解的地方:

  1. 訪問器屬性不能直接定義,必須使用Object.defineProperty()來定義,且該屬性具有set和ger特性,于是可以判斷,_year和edition是數據屬性,而year是訪問器屬性。
  2. 我們看到_year這個數據屬性前面是以_(下劃線)開頭的,這個一種常用的記號,用于表示 只能通過對象方法訪問的屬性。 從上面的例子中可以看到 get相當于描述符對象的一個方法 ,而_year正是在這個對象方法訪問的屬性。而edition既可以通過對象方法訪問,也可以由對象直接訪問。
  3. book.year表示正在 讀取訪問器屬性, 這時會調用get函數,并返回了2004這個有效的值。
  4. book.year=2005表示 寫入訪問器屬性 ,這時會調用set函數并傳入新值,即將2005傳給newValue,這個函數決定如何處理數據。
  5. 這時使用訪問器屬性的常見方法-即設置一個屬性的值會導致其他屬性發生變化。

3.如何利用Object.defineProperties()方法定義多個特性

顯然,一個對象不可能只具有一個屬性,因此,定義多個屬性的可能性很大,于是JavaScript提供了Object.defineProperties()方法解決這個問題。這個方法接收兩個參數,第一個是要定義屬性所在的對象,第二個 是一個對象字面量方法創建的對象,對象的屬性名即為要定義的特姓名,對象的屬性值又是一個對象,這個對象里的屬性名和屬性值分別是特性名和特性值( 這里不是很好理解,看例子即可 )。

var book={};
    Object.defineProperties(book,{
        _year:{
            writable:true,
            value:2004
        },
        edition:{
            writable:true,
            value:1
        },
        year:{
            get:function(){
                return this._year;
            },
            set:function(){
                if(newValue>2004){
                    this._year=newValue;
                    this.edition+=newValue-2004;
                }
            }
        }
    });

4. 如何利用 Object.getOwnPropertyDescripter()方法 讀取屬性的描述符以讀取屬性的特性

我們可以使用Object.getOwnPropertyDescripter()方法來取得給定屬性的描述符。getOwnPropertyDescripter即為取得自身屬性描述符的意思。這個方法接收兩個參數:屬性所在的對象要要讀取其描述符的屬性名稱。返回一個對象。

對于訪問器屬性而言,這個對象的屬性有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(){
                if(newValue>2004){
                    this._year=newValue;
                    this.edition+=newValue-2004;
                }
            }
        }
    });
    var descriptor=Object.getOwnPropertyDescriptor(book,"_year");
    console.log(descriptor.value);//2004
    console.log(descriptor.configurable);//false  因為通過Object.defineProperties()方法創建的屬性的特性configurable enumerable都是false
    console.log(typeof descriptor.get);//undefined 注意:這是數據屬性,是不具有get特性的

    var descriptor=Object.getOwnPropertyDescriptor(book,"year");
    console.log(descriptor.value);//undefined
    console.log(descriptor.enumerable);//false
    console.log(typeof descriptor.get);//function get雖然是屬性的一個特性,但是它也是函數。

 

來自:http://www.cnblogs.com/zhuzhenwei918/p/6025077.html

 

 本文由用戶 ArdenBNFQ 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!