學習對象屬性的屬性

引言

每一個對象都有本身的屬性,這些對象的屬性描述了對象的信息和特徵。而這些對象的屬性也有本身的屬性,即對象屬性的屬性(或稱爲特徵),他們描述了對象中某個屬性的特徵,例如一個屬性是否能夠枚舉、是否能夠修改、是否能夠刪除等等。數組

注意:爲便於區分,如下部分將對象的屬性簡稱爲屬性,將對象屬性的屬性簡稱爲屬性的特徵。函數

1 屬性的類型

屬性有兩種類型:ui

  1. 數據屬性:保存了值,是最經常使用的屬性類型,例以下面 person 對象的 name 屬性就是一個數據屬性:this

    let person = {
        name: 'Yuri'
    };
    複製代碼
  2. 訪問器屬性:是個函數,決定了一個屬性被讀取或寫入的方式,函數名就是要讀取或者寫入的屬性名,讀取這個屬性的函數用 get後跟屬性名來定義,寫入一個屬性的函數用set後跟屬性名來定義。舉個例子,假設訪問器屬性名爲 age, 則定義的語法爲:spa

    /* 假設訪問器屬性名爲 age, 則定義的語法爲: */
    let person = {
        xxx: 99,
        // 定義讀取的函數
        get age(){
            // do something;
            return this.xxx;  // xxx 將在 age 被讀取時返回
        },
    
        // 定義寫入的函數
        set age(value){
            this.xxx = value;  // 其實是將 xxx 的屬性值設置成 value
        }
    }
    複製代碼

    這種方式從外部看來是在訪問 age,實際上是在訪問 xxx,實際上,經過getset咱們能夠自由的處理外界的讀取與寫入請求,能夠返回一個真的值,也能夠給外部一個假的值。code

這兩類屬性定義的方式不一樣,可是訪問的方式倒是同樣的,均可以經過點號訪問或者中括號來訪問,例如:對象

// 讀取 name 值
console.log(person.name);  // Yuri
// 寫入 name 值
person.name = 'new name';
console.log('修改後的 name 屬性值是 ' + person.name);  // 修改後的 name 屬性值是 new name

// 讀取 age 值
console.log(person.age);  // 99
// 寫入 age 值
person.age = 1;
console.log('修改後的 age 屬性值是 ' + person.age); // 修改後的 age 屬性值是 1
複製代碼

1.1 兩種屬性的關係

雖然兩種屬性均可以設置和讀取其中保存的值,但在做用上仍是有區別的。blog

若是隻須要保存和讀取數據,則用數據屬性來作就夠了,沒有必要使用訪問器屬性;可是若是須要修改讀取或者寫入的默認行爲時,則應該將屬性用訪問器的方式來定義成一個訪問器屬性~~,而後就能夠隨心所欲了~~。ip

在定義訪問器屬性時,不必定非要同時定義set和get。當只定義get時,則屬性就成了只讀屬性,不能寫入;若是隻定義了set,則屬性就只能寫入,不能讀取。字符串

訪問器屬性的優先級較高。當將一個屬性名同時由數據屬性和訪問器屬性定義時,訪問器屬性生效,數據屬性被忽略:

let person = {
    name: 'Yuri', // 將 name 設置爲數據屬性
    xxx: 99,

    get name(){ // 將 name 設置爲訪問器屬性
        // do something;
        return this.xxx;
    },

    // 定義寫入的函數
    set name(value){ // 將 name 設置爲訪問器屬性
        this.xxx = '這是經過訪問器屬性設置的值: ' + value; 
    }
}

// 讀取 name 值
console.log(person.name);  // 99
// 寫入 name 值
person.name = 'new name';
console.log(person.name);  // 這是經過訪問器屬性設置的值: new name
複製代碼

2 屬性的屬性(特徵)

如引言中所說,對象的屬性不只有值(value),並且還有描述這個屬性的特徵,例如可讀性、可配置性,這些特徵是用一個對象來保存的,這個對象稱爲屬性的描述對象,全部的屬性都有一個描述對象來描述它的特徵。

屬性的描述對象在屬性被建立的時候就已經生成了,JavaScript會自動建立它,併爲它指定默認值。固然,咱們也能夠在建立一個屬性的時候就手動指定這個屬性的特徵,這個方法在2.2節中進行了討論。

對於上文提到的兩種屬性(數據屬性和訪問器屬性)有兩個共同的特徵,可是因爲功能和特色的不一樣,它們各自也有本身獨有的特徵,下面分別來討論。

咱們能夠手動的去修改這些特徵,例如將一個屬性的設置爲不可枚舉的,則用for in方法或者Object.keys()方法都不會取得這個屬性。

修改屬性的特徵的方法是Object.defineProperty(obj, propertyName, descriptor);,其中參數定義以下:

  1. obj:屬性所在的對象
  2. propertyName:屬性的名稱,是個字符串
  3. descriper:描述屬性特徵的對象,經過修改這個對象來修改屬性的特徵

上面的Object.defineProperty()函數一次只能修改一個屬性的特徵,若是想一次性同時改變多個屬性的特徵,須要使用Object.defineProperties(obj, descriptor)函數,其中的第一個參數也是要被修改屬性的對象,第二個參數也是一個特徵描述對象,不過在這個對象裏能夠同時修改多個屬性的特徵。

下面的內容會使用上面提到的兩個方法來改變屬性的特徵,經過閱讀下面的內容,能夠同時學會屬性每一個特徵的含義和這兩個函數的用法。

2.1 通用的特徵

所謂通用的特徵是指數據屬性和訪問器屬性都有的特徵。這類特徵有兩個:

  1. enumerable:這個特徵描述了屬性是不是能夠被枚舉的,它取布爾值,若爲true,則能夠枚舉,不然不可被枚舉。不可枚舉的屬性不能被for in方法或者Object.keys()方法獲得。咱們本身定義的每一個屬性的enumerable特徵的默認值都是true,即都是可枚舉的。

    let obj = {
        name: 'Yuri'
    };
    // 默認 name 屬性是可枚舉的, 能夠經過 Object.keys 方法獲得 name 這個屬性名
    console.log(Object.keys(obj));  // ["name"]
    
    Object.defineProperty(obj, 'name', {
        enumerable: false  // 設置爲不可枚舉
    });
    
    // 此時 Object.keys 獲得的結果中就沒有 name 屬性了
    console.log(Object.keys(obj));  // [] 空數組
    複製代碼
  2. configurable:描述這個屬性是否能夠被配置,取布爾值。是否能夠被配置的意思是這個屬性的descriptor描述對象是否能被修改,同時也肯定了這個屬性是否能被刪除。咱們本身定義的每一個屬性的configurable特徵的默認值都是true,即都是可修改和刪除的。

    首先在默認狀況下嘗試刪除一個對象的屬性:

    let obj = {
        name: 'Yuri'
    };
    
    // 嘗試刪除 name 這個屬性,成功
    console.log(obj.name);  // Yuri
    delete obj.name;
    console.log(obj.name);  // undefined,說明 obj 對象已經沒有了 name 屬性 
    複製代碼

    能夠看到在默認狀況下,咱們本身給對象加的屬性是能夠被刪除的,由於這個屬性的configurable特徵取值爲true

    下面將configurable屬性設置爲false,再嘗試刪除操做,發現刪除失敗:

    Object.defineProperty(obj, 'name', {
        configurable: false  // 設置爲不可配置
    });
    
    delete obj.name;
    console.log(obj.name);  // Yuri,說明 obj 對象仍有 name 屬性 
    複製代碼

2.2 數據屬性特有的特徵

數據屬性有兩個訪問器屬性所沒有的特徵:

  1. writable:定義了屬性值可否被修改,去布爾值,若爲true則可被修改,不然不能被修改。默認值爲true

    let obj = {
        name: 'Yuri'
    };
    
    console.log(obj.name);  // Yuri
    obj.name = 'Yuri`s Revenge';
    console.log(obj.name);  // Yuri`s Revenge, 修改爲功,由於 writable 默認爲 true
    
    Object.defineProperty(obj, 'name', { 
        writable: false  // 設置爲禁止修改
    });
    obj.name = 'Red alert';
    console.log(obj.name);  // Yuri`s Revenge, 依然是修改以前的值,修改失敗
    複製代碼
  2. value:它保存了屬性的值,當咱們想取出屬性值時,其實取出的就是這個value所保存的值。例如當咱們使用obj.name來得到name屬性的值時,獲得的就是value的值:

let obj = {
    name: 'Yuri'
};

console.log(obj.name);  // Yuri

Object.defineProperty(obj, 'name', {
    value: '這是經過 value 特徵修改後的值'
});

console.log(obj.name);  // 這是經過 value 特徵修改後的值
複製代碼

經過給屬性的value特徵賦值,甚至能夠給對象建立一個原本沒有的屬性並賦上值:

// 建立一個沒有 age 屬性的對象
let obj = {
    name: 'Yuri'
};

console.log(obj.age);  // undefined, 由於沒有 age 這個屬性,因此返回 undefined 

// 使用 Object.defineProperty 給對象設置 age 屬性的值
Object.defineProperty(obj, 'age', { 
    value: 99
});

console.log(obj.age);  // 99 已成功 age 屬性,並賦了值
複製代碼

實際上,上面代碼的執行步驟爲:先觀察對象有沒有 age 屬性,發現沒有,則建立一個,而後將value值賦了上去。

***注意:***在用這種方法建立對象時,要記得不能只設置value屬性,還要顯式的設置其餘三個屬性(enumerable、configurable、writable)的值,不然這三個特徵的值都會被默認設置爲false,即不可遍歷、不可配置、不可寫。這是和其餘建立屬性的方式所不一樣的。

2.3 訪問器屬性特有的特徵

訪問器屬性也有兩個特有的特徵,因爲訪問器屬性不須要存儲值,因此沒有writable和value特徵,其特有的特徵是:getset特徵。

這彷佛和第 1 節中討論的訪問器屬性重複了,由於訪問器屬性也是用get和set來定義的數據訪問方式。可是他們的不一樣點在於訪問器屬性只能在建立對象時定義,而使用getset特徵能夠隨時改變屬性,這樣就不用再去建立一個新的對象了。

例如改變一個已經定義了訪問器屬性的get和set特徵:

let person = {
    _age: 99,

    get age(){
        return this._age;
    },

    set age(value){
        this._age = value;
    }
};

Object.defineProperty(person, 'age', { 
    get(){  // 因爲已經指定了要設置的屬性名,因此沒必要向第1小節同樣 get age(){...}
        return this._age * 2;
    },
    set(value){  // 因爲已經指定了要設置的屬性名,因此沒必要向第1小節同樣 set age(){...}
        this._age = 0;  // 不管外界想將 name 設置成什麼 value, 都忽略, 任性的設置成 0
    }
});

console.log(person.age);  // 198, 就是 99 * 2
person.age = 18;
console.log(person.age);  // 0
複製代碼

注意:當僅設置了get 時,該屬性爲只讀;當僅設置了set時,該屬性爲僅可寫。

2.4 使用 Object.defineProperties() 一次性設置多個屬性的特徵

Object.defineProperties(object, descriptors)方法的重點在於對特徵描述對象的定義,在第二個參數descriptors中能夠給多個屬性定義各自的描述對象,屬性和描述對象是以鍵值對的形式出現的,除此以外,和Object.defineProperty的效果沒有區別。例子以下:

let game = {
    name: 'Yuri`s Revenge'
};

console.log(game.name);  // Yuri`s Revenge

Object.defineProperties(game, {
    name: {  // 給 name 屬性修改特徵描述對象
        value: 'Red Alert',
        configurable: false,
        writable: false
    },
    
    creater: {  // 建立一個新的屬性,並只提供 get 方法,則 creater 屬性是隻讀的
        get(){
            return 'West Wood'
        },
        configurable: false,
        enumerable: true
    }
});

console.log(game.name);  // Red Alert
game.name = 'Command & Conquer';
console.log(game.name);  // Red Alert

console.log(game.creater); // West Wood
game.creater = 'EA';
console.log(game.creater); // West Wood
複製代碼

3 獲取屬性的特徵描述對象

若是想知道一個屬性如今的特徵是什麼,則能夠調用Object.defineProperties(object, propertyName),其中第一個參數 object是屬性所在的對象,第二個參數propertyName是屬性名。返回結果是這個屬性的特徵描述對象。例如獲取上個例子中的game對象的 namecreater 屬性的特徵描述對象:

console.log(Object.getOwnPropertyDescriptor(game, 'name'));
// {value: "Red Alert", writable: false, enumerable: true, configurable: false}

console.log(Object.getOwnPropertyDescriptor(game, 'creater'));
// {get: ƒ, set: undefined, enumerable: true, configurable: false}
// 其中 get: f 中的 f 是 function 的縮寫,表明函數
複製代碼

總結

對象有兩種屬性:數據屬性和訪問器屬性。

對象的屬性除了有屬性值之外,也有本身的特徵,好比是否可遍歷、是否可配置等。

數據屬性和訪問器屬性都有4個特徵,除了2個共同的特徵以外,也各自擁有2個本身獨特的特徵。能夠用圖來總結一下:![](D:\NOTES IN GIT\blogs\屬性特徵.png)

相關文章
相關標籤/搜索