js知識梳理1:理解物件的屬性特性

QCJay發表於2018-08-19

寫在前面

注:這個系列是本人對js知識的一些梳理,其中不少內容來自書籍:Javascript高階程式設計第三版和JavaScript權威指南第六版,感謝它們的作者和譯者。有發現什麼問題的,歡迎留言指出。

1.資料屬性

資料屬性的4個特性:

  • Configurable:①表示能否通過delete刪除屬性從而重新定義,②能否修改屬性的特性,③能否把屬性修改為訪問器屬性。物件直接量裡預設值true。
  • Enumerable:表示能否通過for-in迴圈返回屬性。物件直接量裡預設值true。
  • Writable:表示能否修改屬性的值。物件直接量裡預設值true。
  • Value:包含這個屬性的資料值。物件直接量裡預設值undefined。
//檢視物件直接量的屬性的屬性特性預設值
var people = {
    name:`jaychou`,
    sayName:function () {
        console.log(this.name);
    }
};
/**{value: "jaychou", writable: true, enumerable: true, configurable: true}*/
console.log(Object.getOwnPropertyDescriptor(people,`name`));
/**{value: ƒ, writable: true, enumerable: true, configurable: true}*/
console.log(Object.getOwnPropertyDescriptor(people,`sayName`));
//getOwnPropertyDescriptor對於繼承屬性和不存在的屬性,返回undefined

要修改屬性預設的特性,使用Object.defineProperty()方法,接收3個引數:物件,屬性名字和描述符物件。

//修改屬性預設特性:
Object.defineProperty(person,`job`,{
    emumerable:false,//不可列舉
    value:`singer`,
    writable:false,//不可寫
    configurable:true
});
/**{name: "jaychou", sayName: ƒ, job: "singer"}*/
console.log(person);
for(var prop in person){
    //列印name,sayName
    console.log(prop);
}
//會報錯
try{
    person.job = `director`;
}catch (e) {
    //Cannot assign to read only property `job` of object
    console.log(e);
}

可以多次呼叫Object.defineProperty()方法修改同一個屬性,但在把configurable特性設定為false之後就會有限制了:

Object.defineProperty(person,`height`,{
    configurable:false,//不可配置
    writable:true,
    value:172
});
try{
    Object.defineProperty(person,`height`,{
        configurable:true,//出錯
        enumerable:true,//出錯
        value:175,//正常
        writable:false,//writable從true變false可以,false變true也會出錯
    });
}catch (e) {
    //Cannot redefine property: height at Function.defineProperty
    console.log(e);
}
try{
    delete person.height;
}catch (e) {
    //設定成不可配置後也不可刪除:Cannot delete property `height` of #<Object>
    console.log(e);
}

另外,呼叫 Object.defineProperty()方法時,如果不指定,configurable、enumerable 和 writable 特性的預設值都是 false。如果是修改已有屬性,則無此限制。

2.儲存器屬性

儲存器屬性不包含資料值,只包含包含 getter 和 setter 函式(非必需)。 在讀取儲存器屬性時,會呼叫 getter 函式,這個函式負責返回有效的值;在寫入儲存器屬性時,會呼叫 setter 函式並傳入新值,這個函式負責決定如何處理資料。4個屬性特性如下:

  • Configurable:①表示能否通過delete刪除屬性從而重新定義,②能否修改屬性的特性,③能否把屬性修改為資料屬性。物件直接量的預設值true
  • Enumerable:表示能否通過for-in迴圈返回屬性。物件直接量的預設值true
  • Get:在讀取屬性時呼叫的函式。物件直接量預設值undefined
  • Set:在寫入屬性時呼叫的函式。物件直接量的預設值undefined

定義儲存器屬性最簡單的方法是使用物件直接量語法的擴充寫法:

var p = {
    x:3.0,
    y:4.0,
    //r是可讀寫的存取器屬性
    get r(){return Math.sqrt(this.x*this.x+this.y*this.y);},
    set r(newValue){
        var oldvalue = Math.sqrt(this.x*this.x+this.y*this.y);
        var ratio = newValue/oldvalue;
        this.x *= ratio;
        this.y *= ratio;
    },
    //theta是隻讀存取器屬性
    get theta(){return Math.atan2(this.y,this.x);}
}
console.log(p.r);
p.r = 25;

使用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;
        }
    }
})
/**{get: ƒ, set: ƒ, enumerable: false, configurable: false}*/
console.log(Object.getOwnPropertyDescriptor(book,`year`));

如例子所示,使用儲存器屬性的常見方式,即設定一個屬性的值會導致其他屬性發生變化。還有一種常見就是現在流行的類似於Vue的響應式原理,就是把data中的屬性都使用defineProperty修改為儲存器屬性,可以監聽到資料的變化。

3.定義多個屬性

經常要建立或修改多個屬性,這時候可以使用Object.defineProperties()方法,它接收2個引數,要新增或修改屬性的物件和一個對映表,包含名稱和屬性描述符。

var book1 = {};
Object.defineProperties(book1,{
   _year:{
       value:`2008`
   },
   editor:{
       enumerable:true,
       value:`2`
   },
   year:{
       get:function () {
           return this._year;
       },
       set:function (newValue) {
           this._year = newValue;
           this.edition += newValue - 2004;
       }
   }
});

4.物件的可擴充套件性

物件的可擴充性表示是否可以給物件新增新屬性。所有內建物件和自定義物件都是顯式可擴充套件的,宿主物件的可擴充套件性是由Javascript引擎定義的。

1.查詢物件可擴充性
var teacher = {age:25};
//true:代表可擴充
console.log(Object.isExtensible(teacher));
2.轉換為不可擴充(“鎖定物件”)
Object.preventExtensions(teacher);
//false
console.log(Object.isExtensible(teacher));
try{
    teacher.subject = `math`;
}catch (e) {
    //TypeError: Cannot add property subject, object is not extensible
    console.log(e);
}

轉換成不可擴充的操作是不可逆的,而且只能影響到物件本身的可擴充性,如果給一個不可擴充物件的原型新增屬性,這個不可擴充物件同樣會繼承這些新屬性。

5.密封物件

密封物件比鎖定物件更高一層,除了不可擴充以外,物件的所有自身屬性都設定成了不可配置的。同樣密封物件操作是不可逆的。

var tea1 = {subject:`math`};
//false:代表未密封
console.log(Object.isSealed(tea1));
Object.seal(tea1);
try{
    Object.defineProperty(tea1,`subject`,{
        //enumerable:false,//出錯
        //configurable:true,//出錯
        writable:false//和上面說的一樣,writable從true變成false可以,false變成true則出錯
    });
}catch (e) {
    console.log(`出錯..`);
    console.log(e);
}
//true:已密封
console.log(Object.isSealed(tea1));

6.凍結物件

凍結比密封物件多的效果是:可以將它自有的所有資料屬性設定為只讀(如果物件的存取器屬性具有setter方法,存取器屬性將不受影響,仍可以通過給屬性賦值呼叫它們)。

var tea2 = {subject:`Chinese`};
//false:代表未凍結
console.log(Object.isFrozen(tea2));
Object.freeze(tea2);
try{
    tea2.subject = `math`;
}catch (e) {
    //TypeError: Cannot assign to read only property `subject` of object
    console.log(e);
}
//true:已凍結
console.log(Object.isFrozen(tea2));

7.屬性特性規則總結

  • 如果物件是不可擴充的,則可以編輯已有的自有屬性,但不能給它新增新屬性。
  • 如果屬性是不可配置的,則不能修改它的可配置性和可列舉性。
  • 如果存取器屬性是不可配置的,則不能修改其getter和setter方法,也不能將它轉換為資料屬性。
  • 如果資料屬性是不可配置的,則不能將它轉換為存取器屬性。
  • 如果資料屬性是不可配置的,則不能將它的可寫性從false修改為true,但可以從true修改為false。
  • 如果資料屬性是不可配置且不可寫的,則不能修改它的值。然而可配置但不可寫屬性的值是可以修改的(做法:先將它標記為可寫的,然後修改它的值,最後轉換為不可寫的)。

相關文章