深刻理解javascript對象系列第三篇——神祕的屬性描述符

描述符類型

  對象屬性描述符的類型分爲兩種:數據屬性和訪問器屬性html

數據屬性函數

  數據屬性(data property)包含一個數據值的位置,在這個位置能夠讀取和寫入值。數據屬性有4個特性測試

【1】Configurable(可配置性)this

  可配置性決定是否可使用delete刪除屬性,以及是否能夠修改屬性描述符的特性,默認值爲truespa

【2】Enumerable(可枚舉性)prototype

  可枚舉性決定屬性是否出如今對象的屬性枚舉中,好比是否能夠經過for-in循環返回該屬性,默認值爲truecode

【3】Writable(可寫性)htm

  可寫性決定是否能夠修改屬性的值,默認值爲true對象

【4】Value(屬性值)blog

  屬性值包含這個屬性的數據值,讀取屬性值的時候,從這個位置讀;寫入屬性值的時候,把新值保存在這個位置。默認值爲undefined

訪問器屬性

  對象屬性是名字、值和一組屬性描述符構成的。而屬性值能夠用一個或兩個方法替代,這兩個方法就是getter和setter。而這種屬性類型叫訪問器屬性(accessor property)

【1】Configurable(可配置性)

  可配置性決定是否可使用delete刪除屬性,以及是否能夠修改屬性描述符的特性,默認值爲true

【2】Enumerable(可枚舉性)

  可枚舉性決定屬性是否出如今對象的屬性枚舉中,好比是否能夠經過for-in循環返回該屬性,默認值爲true

【3】getter

  在讀取屬性時調用的函數。默認值爲undefined

【4】setter

  在寫入屬性時調用的函數。默認值爲undefined

  和數據屬性不一樣,訪問器屬性不具備可寫性(Writable)。若是屬性同時具備getter和setter方法,那麼它是一個讀/寫屬性。若是它只有getter方法,那麼它是一個只讀屬性。若是它只有setter方法,那麼它是一個只寫屬性。讀取只寫屬性老是返回undefined

描述符方法

  前面介紹了屬性描述符,要想設置它們,就須要用到描述符方法。描述符方法總共有如下4個:

【1】Object.getOwnPropertyDescriptor()

  Object.getOwnPropertyDescriptor(o,name)方法用於查詢一個屬性的描述符,並以對象的形式返回

  查詢obj.a屬性時,可配置性、可枚舉性、可寫性都是默認的true,而value是a的屬性值1

  查詢obj.b屬性時,由於obj.b屬性不存在,該方法返回undefined

var obj = {a:1};
//Object {value: 1, writable: true, enumerable: true, configurable: true}
console.log(Object.getOwnPropertyDescriptor(obj,'a'));
//undefined
console.log(Object.getOwnPropertyDescriptor(obj,'b'));

【2】Object.defineProperty()

  Object.defineProperty(o,name,desc)方法用於建立或配置對象的一個屬性的描述符,返回配置後的對象

  使用該方法建立或配置對象屬性的描述符時,若是不針對該屬性進行描述符的配置,則該項描述符默認爲false

複製代碼
var obj = {};
//{a:1}
console.log(Object.defineProperty(obj,'a',{
        value:1,
        writable: true
    }));

//因爲沒有配置enumerable和configurable,因此它們的值爲false
//{value: 1, writable: true, enumerable: false, configurable: false}
console.log(Object.getOwnPropertyDescriptor(obj,'a'));
複製代碼

【3】Object.defineProperties()

  Object.defineProperty(o,descriptors)方法用於建立或配置對象的多個屬性的描述符,返回配置後的對象

複製代碼
var obj = {
    a:1
};
//{a: 1, b: 2}
console.log(Object.defineProperties(obj,{
        a:{writable:false},
        b:{value:2}
    }));

//{value: 1, writable: false, enumerable: true, configurable: true}
console.log(Object.getOwnPropertyDescriptor(obj,'a'));
//{value: 2, writable: false, enumerable: false, configurable: false}
console.log(Object.getOwnPropertyDescriptor(obj,'b'));
複製代碼

【4】Object.create()

  Object.create(proto,descriptors)方法使用指定的原型和屬性來建立一個對象

var o = Object.create(Object.prototype,{
    a:{writable: false,value:1,enumerable:true}
});
//{value: 1, writable: false, enumerable: true, configurable: true}
console.log(Object.getOwnPropertyDescriptor(obj,'a'));

 

描述符詳述

  前面分別介紹了數據屬性和訪問器屬性的描述符,但沒有詳細說明其含義及使用,接下來逐一進行說明 

可寫性(writable)

  可寫性決定是否能夠修改屬性的值,默認值爲true

var o = {a:1};
o.a = 2;
console.log(o.a);//2

  設置writable:false後,賦值語句會靜默失效

複製代碼
var o = {a:1};
Object.defineProperty(o,'a',{
    writable:false
});
console.log(o.a);//1
//因爲設置了writable爲false,因此o.a=2這個語句會靜默失效
o.a = 2;
console.log(o.a);//1
Object.defineProperty(o,'a',{
    writable:true
});
//因爲writable設置爲true,因此o.a能夠被修改成2
o.a = 2;
console.log(o.a);//2
複製代碼

  在嚴格模式下經過賦值語句爲writable爲false的屬性賦值,會提示類型錯誤TypeError

複製代碼
'use strict';
var o = {a:1};
Object.defineProperty(o,'a',{
    writable:false
});
//Uncaught TypeError: Cannot assign to read only property 'a' of object '#<Object>'
o.a = 2;
複製代碼

  [注意]設置writable:false後,經過Object.defineProperty()方法改變屬性value的值不會受影響,由於這也意味着在重置writable的屬性值爲false

複製代碼
var o = {a:1};
Object.defineProperty(o,'a',{
    writable:false
});
console.log(o.a);//1
Object.defineProperty(o,'a',{
    value:2
});
console.log(o.a);//2
複製代碼

 

可配置性(Configurable)

  可配置性決定是否可使用delete刪除屬性,以及是否能夠修改屬性描述符的特性,默認值爲true

  【1】設置Configurable:false後,沒法使用delete刪除屬性

複製代碼
var o = {a:1};
Object.defineProperty(o,'a',{
    configurable:false
});
delete o.a;//false
console.log(o.a);//1
複製代碼

  在嚴格模式下刪除爲configurable爲false的屬性,會提示類型錯誤TypeError

複製代碼
'use strict';
var o = {a:1};
Object.defineProperty(o,'a',{
    configurable:false
});
//Uncaught TypeError: Cannot delete property 'a' of #<Object>
delete o.a;
複製代碼

  [注意]使用var命令聲明變量時,變量的configurable爲false

var a = 1;
//{value: 1, writable: true, enumerable: true, configurable: false}
Object.getOwnPropertyDescriptor(this,'a');

  【2】通常地,設置Configurable:false後,將沒法再使用defineProperty()方法來修改屬性描述符

複製代碼
var o = {a:1};
Object.defineProperty(o,'a',{
    configurable:false
});
//Uncaught TypeError: Cannot redefine property: a
Object.defineProperty(o,'a',{
    configurable:true
});
複製代碼

  有一個例外,設置Configurable:false後,只容許writable的狀態從true變爲false

複製代碼
var o = {a:1};
Object.defineProperty(o,'a',{
    configurable:false,
    writable:true
});
o.a = 2;
console.log(o.a);//2
Object.defineProperty(o,'a',{
    writable:false
});
//因爲writable:false生效,對象a的o屬性沒法修改值,因此o.a=3的賦值語句靜默失敗
o.a = 3;
console.log(o.a);//2
複製代碼

 

可枚舉性(Enumerable)

  可枚舉性決定屬性是否出如今對象的屬性枚舉中,具體來講,for-in循環、Object.keys方法、JSON.stringify方法是否會取到該屬性

  用戶定義的普通屬性默認是可枚舉的,而原生繼承的屬性默認是不可枚舉的

//因爲原生繼承的屬性默認不可枚舉,因此只取得自定義的屬性a:1
var o = {a:1};
for(var i in o){
    console.log(o[i]);//1
}
複製代碼
//因爲enumerable被設置爲false,在for-in循環中a屬性沒法被枚舉出來
var o = {a:1};
Object.defineProperty(o,'a',{enumerable:false});
for(var i in o){
    console.log(o[i]);//undefined
}
複製代碼

propertyIsEnumerable()

  propertyIsEnumerable()方法用於判斷對象的屬性是否可枚舉

var o = {a:1};
console.log(o.propertyIsEnumerable('a'));//true
Object.defineProperty(o,'a',{enumerable:false});
console.log(o.propertyIsEnumerable('a'));//false

 

get和set

  get是一個隱藏函數,在獲取屬性值時調用。set也是一個隱藏函數,在設置屬性值時調用,它們的默認值都是undefined。Object.definedProperty()中的get和set對應於對象字面量中get和set方法

  [注意]getter和setter取代了數據屬性中的value和writable屬性

  【1】給只設置get方法,沒有設置set方法的對象賦值會靜默失敗,在嚴格模式下會報錯

複製代碼
var o = {
    get a(){
        return 2;
    }
}    
console.log(o.a);//2
//因爲沒有設置set方法,因此o.a=3的賦值語句會靜默失敗
o.a = 3;
console.log(o.a);//2
複製代碼
複製代碼
Object.defineProperty(o,'a',{
    get: function(){
        return 2;
    }
})
console.log(o.a);//2
//因爲沒有設置set方法,因此o.a=3的賦值語句會靜默失敗
o.a = 3;
console.log(o.a);//2
複製代碼

  在嚴格模式下,給沒有設置set方法的訪問器屬性賦值會報錯

複製代碼
'use strict';
var o = {
    get a(){
        return 2;
    }
}    
console.log(o.a);//2
//因爲沒有設置set方法,因此o.a=3的賦值語句會報錯
//Uncaught TypeError: Cannot set property a of #<Object> which has only a getter
o.a = 3;
複製代碼
複製代碼
'use strict';
Object.defineProperty(o,'a',{
    get: function(){
        return 2;
    }
})
console.log(o.a);//2
//因爲沒有設置set方法,因此o.a=3的賦值語句會報錯
//Uncaught TypeError: Cannot set property a of #<Object> which has only a getter
o.a = 3;
複製代碼

  【2】只設置set方法,而不設置get方法,則對象屬性值爲undefined

複製代碼
var o = {
    set a(val){
        return 2;
    }
}    
o.a = 1;
console.log(o.a);//undefined
複製代碼
複製代碼
Object.defineProperty(o,'a',{
    set: function(){
        return 2;
    }
})
o.a = 1;
console.log(o.a);//undefined
複製代碼

  【3】通常地,set和get方法是成對出現的

複製代碼
var o ={
    get a(){
        return this._a;
    },
    set a(val){
        this._a = val*2;
    }
}
o.a = 1;
console.log(o.a);//2
複製代碼
複製代碼
Object.defineProperty(o,'a',{
    get: function(){
        return this._a;
    },
    set :function(val){
        this._a = val*2;
    }
})
o.a = 1;
console.log(o.a);//2
複製代碼

 

對象狀態

  屬性描述符只能用來控制對象中一個屬性的狀態。而若是要控制對象的狀態,就要用到下面的6種方法 

Object.preventExtensions()(禁止擴展)

  Object.preventExtensions()方法使一個對象沒法再添加新的屬性,並返回當前對象

Object.isExtensible()(測試擴展)

  Object.isExtensible()方法用來檢測該對象是否能夠擴展

複製代碼
var o = {a:1};
console.log(Object.isExtensible(o));//true
o.b = 2;
console.log(o);//{a: 1, b: 2}
console.log(Object.preventExtensions(o));//{a: 1, b: 2}
//因爲對象o禁止擴展,因此該賦值語句靜默失敗
o.c = 3;
console.log(Object.isExtensible(o));//false
console.log(o);//{a: 1, b: 2}
複製代碼

  在嚴格模式下,給禁止擴展的對象添加屬性會報TypeError錯誤

'use strict';
var o = {a:1};
console.log(Object.preventExtensions(o));//{a:1}
//Uncaught TypeError: Can't add property c, object is not extensible
o.c = 3;

  Object.preventExtensions()方法並不改變對象中屬性的描述符狀態

複製代碼
var o = {a:1};
//{value: 1, writable: true, enumerable: true, configurable: true}
console.log(Object.getOwnPropertyDescriptor(o,'a'));
Object.preventExtensions(o);
//{value: 1, writable: true, enumerable: true, configurable: true}
console.log(Object.getOwnPropertyDescriptor(o,'a'));
複製代碼

 

Object.seal()(對象封印)

  對象封印又叫對象密封,使一個對象不可擴展而且全部屬性不可配置,並返回當前對象

Object.isSealed()(測試封印)

  Object.isSealed()方法用來檢測該方法是否被封印

複製代碼
var o = {a:1,b:2};
console.log(Object.isSealed(o));//false
console.log(Object.seal(o));//{a:1,b:2}
console.log(Object.isSealed(o));//true
console.log(delete o.b);//false
o.c = 3;
console.log(o);//{a:1,b:2}
複製代碼

  在嚴格模式下,刪除舊屬性或添加新屬性都會報錯

'use strict';
var o = {a:1,b:2};
console.log(Object.seal(o));//{a:1,b:2}
//Uncaught TypeError: Cannot delete property 'b' of #<Object>
delete o.b;

  這個方法實際上會在現有對象上調用Object.preventExtensions()方法,並把全部現有屬性的configurable描述符置爲false

複製代碼
var o = {a:1,b:2};
//{value: 1, writable: true, enumerable: true, configurable: true}
console.log(Object.getOwnPropertyDescriptor(o,'a'));
console.log(Object.seal(o));//{a:1,b:2}
//{value: 1, writable: true, enumerable: true, configurable: false}
console.log(Object.getOwnPropertyDescriptor(o,'a'));
複製代碼

 

Object.freeze()(對象凍結)

  Object.freeze()方法使一個對象不可擴展,不可配置,也不可改寫,變成一個僅能夠枚舉的只讀常量,並返回當前對象

Object.isFrozen()(檢測凍結)

  Object.isFrozen()方法用來檢測一個對象是否被凍結

複製代碼
var o = {a:1,b:2};
console.log(Object.isFrozen(o));//false
console.log(Object.freeze(o));//{a:1,b:2}
console.log(Object.isFrozen(o));//true
o.a = 3;
console.log(o);//{a:1,b:2}
複製代碼

  在嚴格模式下,刪除舊屬性、添加新屬性、更改現有屬性都會報錯

'use strict';
var o = {a:1,b:2};
console.log(Object.freeze(o));//{a:1,b:2}
//Uncaught TypeError: Cannot assign to read only property 'a' of object '#<Object>'
o.a = 3;

  這個方法實際上會在現有對象上調用Object.seal()方法,並把全部現有屬性的writable描述符置爲false

複製代碼
var o = {a:1};
//{value: 1, writable: true, enumerable: true, configurable: true}
console.log(Object.getOwnPropertyDescriptor(o,'a'));
console.log(Object.freeze(o));//{a:1}
//{value: 1, writable: false, enumerable: true, configurable: false}
console.log(Object.getOwnPropertyDescriptor(o,'a'));
相關文章
相關標籤/搜索