最詳細的JavaScript高級教程(十五)對象的屬性

概念

ES對於對象的定義是:無序屬性的集合,其屬性可以包括基本值,對象或者函數。從中我們可以發現,js中的對象有下面的特徵

  • 無序的集合,這個就讓js中的對象更像是散列表,是一系列無序的鍵值對的集合
  • 屬性包括基本值,對象或者函數。這個把屬性可以包括哪些數據類型說的很清楚了

屬性類型

ES中有下面兩種屬性

  1. 數據屬性
  2. 訪問器屬性

顧名思義,數據屬性可以保存一個數據值,而訪問器屬性本身不能保存數據值,但是可以通過getter setter進行訪問控制,它的屬性存儲還需要額外的數據屬性。

數據屬性中值是如何存儲的呢?我們就需要知道數據屬性中的四個特性,事先說明,這四個屬性的修改在日常開發中很難用到,但是對於我們理解數據屬性爲什麼能夠存值,對於理解對象大有益處。

數據屬性擁有四個描述其特性的特性值,分別是:

  • Configurable 是否可以配置,是否可以被刪除等(注意一旦這個值設置了false,就再也不能修改配置了,連這個值也不能再進行設置了)
  • Enumerable 是否可以使用for in枚舉
  • Writable 是否可寫
  • Value 存儲其值,默認值是undefined,對於屬性的讀寫操作其實是對於這個值的讀寫

我們需要注意一點,在嚴格模式下,對於設置了 不可配置或者不可寫 特性的屬性,對他們進行寫操作會報錯,在非嚴格模式下,不生效不報錯。

我們使用Object.defineProperty來修改特徵值,看下面的例子:

var person = {};
// 注意在設置的時候如果不指定,前三個特性默認值都是false,與默認不一致
Object.defineProperty(person, "name", {
    writable: false,
    value: "Nic"
});
alert(person.name);
person.name = "Pop"; // 嚴格模式下報錯 Cannot assign to read only property 'name' of object '#<Object>'
alert(person.name);

與之相對應的,訪問器屬性也有四個特性,我們分析一下就知道,訪問器屬性一定有Get Set 特性,自然不需要writable特性,所以訪問器屬性有下面四個特性

  • Configurable
  • Enumerable
  • Get
  • Set

定義訪問器屬性需要使用下面的方法

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

注意:

  • getter 和 setter不是都必須需要指定,當指定其中一個的時候,另一個默認不能操作,即不可讀或者不可寫
  • 嚴格模式下嘗試對於不可寫的屬性進行寫操作會直接報錯

屬性的創建

可以可以使用book.name的方法一個一個創建屬性,也可以使用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;
            }
        }
    }
});

這樣創建的屬性是同時的。

讀取屬性特徵

讀取屬性特徵值通過使用Object.getOwnPropertyDescriptor來獲取屬性特徵值

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 des = Object.getOwnPropertyDescriptor(book, 'year');
alert(des.configurable); //false
alert(des.value); // undefined
alert(des.get); //function
Object.defineProperty(book, 'year', {
    writable: false,
    configurable: true
}); // 報錯

屬性定義的特徵值默認值

我們需要注意的是:普通定義通過a.b=1的方式定義的屬性,前三個屬性都是true,使用 defineProperty 或者 defineProperties定義的屬性,前三個的特性的默認值都是false。

屬性的遍歷

在學習屬性的遍歷的知識的時候,需要先學習下一課的原型對象的知識。

  • 訪問所有可讀的可枚舉的屬性—使用for in
    var o = {
    toString: function() {
      return '123';
    }
    };
    // for in 會枚舉所有可以訪問的並且可以枚舉的屬性
    // 原來的toString方法雖然可以訪問,但是被設置爲不能枚舉
    // 複寫了之後的toString方法可以枚舉,所以可以被遍歷到
    for (var prop in o) {
        if (prop == 'toString') {
          alert('Found toString');
        }
    }
    
  • 訪問所有可枚舉的實例屬性(不帶原型屬性)—使用Object.keys
    function Person() {}
    Person.prototype.name = 'w';
    Person.prototype.age = 20;
    var keys = Object.keys(Person.prototype);
    alert(keys); //name,age
    var p1 = new Person();
    p1.name = 's';
    alert(Object.keys(p1)); //name
    
  • 訪問所有實例屬性(無論是否可以枚舉)—getOwnPropertyNames
    function Person() {}
    Person.prototype.name = 'w';
    Person.prototype.age = 20;
    var props = Object.getOwnPropertyNames(Person.prototype);
    alert(props); //constructor,name,age 其中constructor屬性不可枚舉
    

屬性不可以改變的對象

我們知道使用const創建不能改變的值類型,當我們想創建一個屬性不可變的對象的時候需要使用下面的方法:

創建不能修改的對象應該用Object.freeze

var o = Object.freeze({ name: '123' });
alert(o.name);
o.name = '345'; //嚴格模式報錯
alert(o.name);

這個方法只能凍結對象的屬性,如果對象的屬性還是對象,如果都需要凍結,需要用到遞歸

var constantize = (obj) => {
Object.freeze(obj);
Object.keys(obj).forEach( (key, i) => {
    if ( typeof obj[key] === 'object' ) {
      constantize( obj[key] );
    }
    });
};

ES6 - 對象的解構賦值

我們之前講的數組的解構賦值是將數組中的值拿出來賦值給一系列的變量,對象的解構賦值就是將對象中的屬性拿出來爲一系列的變量賦值。

與數組解構賦值同理,對象解構賦值的語法:左邊的變量要用大括號括起來,右邊傳遞一個對象。

數組解構賦值的關鍵是 結構相同就可以賦值,即需要位置相同,對象解構賦值的關鍵是名字相同,即變量名要與屬性名相同才能正確賦值。

先看一個對象解構賦值的例子:

// 與順序無關,以名稱爲基準賦值
let { a, b } = { b: '1' };
alert(a); //undefined
alert(b); //1

我們也是注意下面一些原則:

  • 對象解構賦值也可以用於方法,即可以把一個方法賦值給一個變量
    // 簡化名稱
    let log = console;
    log('132');
    
  • 雖然是靠屬性名匹配的,但是不是說就不能把變量名隨便起了,我們可以使用下面的方式把屬性賦值給其他名稱的變量
    // 冒號前面是模式,標識屬性名
    let { b: test } = { b: '1' };
    alert(test);
    
  • 對象的解構賦值可以嵌套,看下面的例子
    // 如果有下面這個對象,我們需要把y的值取出來賦值給變量test
    let obj = {
      p: [
        'Hello',
        { y: 'World' }
      ]
    };
    // 我們需要使用這種方式
      let {
        p: [, { y: test }] //需要注意我們之前學的,數組的解構是根據位置的,所以這裏一定要加逗號讓索引到第二個
      } = obj;
      alert(test); //World
    
  • 解構賦值的時候,如果屬性在原型對象上,也是可以正常賦值的
  • 對象的解構賦值也可以添加默認值,添加方法和數組的解構賦值一樣,使用等號
    let { a: test = 1 } = { b: '1' };
    alert(test); //1
    
  • 已經聲明過的變量進行解構賦值會報錯,必須加括號
    let test;
    { test } = { test: '1' }; // 報錯,解釋器會以爲{ test }是一個代碼塊
    
    let test;
    ({ test } = { test: '1' }); //不報錯,正確寫法
    
  • 數組是特殊的對象,他的屬性名是0 1 2,所以可以對數組使用對象的解構賦值
    var arr = [1, 2];
    let { 0: a, 1: b } = arr;
    alert(a); // 1
    alert(b); // 2
    
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章