淺談 JS 對象之擴展、密封及凍結三大特性

  • 擴展特性segmentfault

    • Object.isExtensible 方法函數

    • Object.preventExtensions 方法測試

  • 密封特性spa

    • Object.isSealed 方法prototype

    • Object.seal 方法code

  • 凍結特性對象

    • Object.isFrozen 方法繼承

    • Object.freeze 方法遞歸

      • 淺凍結深凍結原型鏈

擴展特性

若是一個對象能夠添加新的屬性,則這個對象是可擴展的。
讓這個對象變的不可擴展,也就是不能再有新的屬性

咱們都知道,咱們能夠經過屬性描述符建立屬性不可配置對象 如何讓對象屬性不可配置或枚舉,
在這裏咱們能夠建立不可擴展屬性的對象

Object.isExtensible 方法

MDN:

概述
    Object.isExtensible() 方法判斷一個對象是不是可擴展的(是否能夠在它上面添加新的屬性)。
語法
    Object.isExtensible(obj)
參數
    obj 須要檢測的對象

使用:

//新對象默認是可擴展的不管何種方式建立的對象,這裏使用的是字面量方式
var empty = {a:1};
console.log(Object.isExtensible(empty) === true);//true

//等價於 使用屬性描述符
empty = Object.create({},{
    "a":{
        value : 1,
        configurable : true,//可配置
        enumerable : true,//可枚舉
        writable : true//可寫
    }
});
console.log(Object.isExtensible(empty) === true);//true

//對象是否能夠擴展與對象的屬性是否能夠配置無關
empty = Object.create({},{
    "a":{
        value : 1,
        configurable : false,//不可配置
        enumerable : true,//可枚舉
        writable : true//可寫
    }
});
console.log(Object.isExtensible(empty) === true);//true

那麼咱們如何讓一個對象變成不可擴展:

Object.preventExtensions 方法

MDN:

概述
    Object.preventExtensions() 方法讓一個對象變的不可擴展,也就是永遠不能再添加新的屬性。
語法
    Object.preventExtensions(obj)
參數
    obj 將要變得不可擴展的對象
描述
    若是一個對象能夠添加新的屬性,則這個對象是可擴展的。
    preventExtensions 可讓這個對象變的不可擴展,也就是不能再有新的屬性。
    須要注意的是不可擴展的對象的屬性一般仍然能夠被刪除。
    嘗試給一個不可擴展對象添加新屬性的操做將會失敗,不過多是靜默失敗,也可能會拋出 TypeError 異常(嚴格模式)。        
    Object.preventExtensions 只能阻止一個對象不能再添加新的自身屬性,仍然能夠爲該對象的原型添加屬性。

使用:

(function () {
    //Object.preventExtensions 將原對象變得不可擴展,而且返回原對象.
    var obj = {};
    var obj2 = Object.preventExtensions(obj);
    console.log(obj === obj2);//true

    //新建立的對象默認是可擴展的
    var empty = {};
    console.log(Object.isExtensible(empty) === true);//true
    empty.a = 1;//添加成功

    //將其變爲不可擴展對象
    Object.preventExtensions(empty);
    console.log(Object.isExtensible(empty) === false);//true

    //使用傳統方式爲不可擴展對象添加屬性
    empty.b = 2;//靜默失敗,不拋出錯誤
    empty["c"] = 3;//靜默失敗,不拋出錯誤

    //在嚴格模式中,爲不可擴展對象添加屬性將拋出錯誤
    (function fail(){
        "use strict";
        empty.d = "4";//throws a TypeError
    })();

    //使用 Object.defineProperty方法爲不可擴展對象添加新屬性會拋出異常
    Object.defineProperty(empty,"e",{value : 5});//拋出 TypeError 異常

    Object.defineProperty(empty,"a",{value : 2});
    console.log(empty.a);//輸出2

})();

在上述代碼的最後兩行能夠看到若是爲當前不可擴展對象 empty 修改屬性是成功的,這是由於一個對象的屬性是否能夠被修改與該對象是否能夠擴展無關,而是與該對象在建立的時候是否聲明爲不可重寫有關(Writable

若是咱們想讓一個對象的全部屬性都不可配置同時也不容許爲該對象進行擴展怎麼作:

(function () {
    //建立一個對象,同時聲明其全部屬性均爲不可配置且不可寫
    var obj = {a :1,b:2,c:3};
    Object.defineProperties(obj,{
       "a":{configurable:false},
       "b":{configurable:false},
       "c":{configurable:false}
    });

    //等價於
    var obj = Object.create({},{
        "a":{value :1,congigurable :false,enumerable :true,writable:true},
        "b":{value :2,congigurable :false,enumerable :true,writable:true},
        "c":{value :3,congigurable :false,enumerable :true,writable:true}
    });

    //將其轉化爲不可擴展對象
    Object.preventExtensions(obj);

    //測試該對象是否即不可擴展同時其全部屬性均不可配置
    console.log(Object.isExtensible(obj) === true);//false
    for(var name of Object.keys(obj)){//遍歷該對象的全部可枚舉屬性名,不包括繼承而來的屬性
        Object.defineProperty(obj,name,{enumerable:false});//將該屬性的 enumerable 特性從新配置爲 true
    }//拋出異常
})();

雖說上面的程序實現了需求,但未免太麻煩,這裏咱們可使用 JS 對象的另外一特性 密封

密封特性

密封對象是指那些不可 擴展 的,且全部自身屬性都不可配置的(non-configurable)對象。

或則說 密封對象是指那些不能添加新的屬性,不能刪除已有屬性,以及不能修改已有屬性的可枚舉性、可配置性、可寫性,但可能能夠修改已有屬性的值的對象。

Object.isSealed 方法

MDN:

概述 
    Object.isSealed() 方法判斷一個對象是不是密封的(sealed)。
語法 
    Object.isSealed(obj)
參數
    obj 將要檢測的對象
描述
    若是這個對象是密封的,則返回 true,不然返回 false。

使用:

(function () {
    //新建的對象默認不是密封的
    var empty = {};
    console.log(Object.isSealed(empty) === false);//true

    //若是把一個空對象變得不可擴展,則它同時也會變成個密封對象.
    Object.preventExtensions(empty);
    console.log(Object.isSealed(empty) === true);//true

    //但若是這個對象不是空對象,則它不會變成密封對象,由於密封對象的全部自身屬性必須是不可配置的.
    var hasProp = {fee : "fie foe fum"};
    Object.preventExtensions(hasProp);
    console.log(Object.isSealed(hasProp) === false);//true

    //若是把這個屬性變得不可配置,則這個對象也就成了密封對象.
    Object.defineProperty(hasProp,"fee",{configurable : false});
    console.log(Object.isSealed(hasProp) === true);//true
})();

Object.seal 方法

MDN:

概述
    Object.seal() 方法可讓一個對象密封,並返回被密封后的對象。
    密封對象是指那些不能添加新的屬性,不能刪除已有屬性,以及不能修改已有屬性的可枚舉性、可配置性、可寫性,但可能能夠修改已有屬性的值的對象。
語法
    Object.seal(obj)
參數
    obj 將要被密封的對象
描述
    一般狀況下,一個對象是可擴展的(能夠添加新的屬性)。
    密封一個對象會讓這個對象變的不能添加新屬性,且全部已有屬性會變的不可配置。
    屬性不可配置的效果就是屬性變的不可刪除,以及一個數據屬性不能被從新定義成爲訪問器屬性,或者反之。
    但屬性的值仍然能夠修改。
    嘗試刪除一個密封對象的屬性或者將某個密封對象的屬性從數據屬性轉換成訪問器屬性,結果會靜默失敗或拋出TypeError 異常(嚴格模式)。
    不會影響從原型鏈上繼承的屬性。但 __proto__ (  ) 屬性的值也會不能修改。

使用:

(function () {
    var obj = {             //聲明一個對象
        prop:function(){},
        foo:"bar"
    };
    //能夠添加新的屬性,已有屬性的值能夠修改,能夠刪除
    obj.foo = "baz";
    obj.lumpy = "woof";
    delete obj.prop;

    var o = Object.seal(obj);//將 obj 密封,且返回原對象
    console.log(o === obj);//true
    console.log(Object.isSealed(obj) === true);//true

    //仍然能夠修改密封對象上的屬性的值
    obj.foo = "quux";//修改爲功

    //但不能把密封對象的屬性進行從新配置,譬如講數據屬性重定義成訪問器屬性.
    //Object.defineProperty(obj,"foo",{get : function(){return "g";}});//拋出 TypeError

    //任何除修改屬性值之外的操做都會失敗
    obj.quaxxor = "the friendly duck";//靜默失敗,屬性沒有成功添加
    delete obj.foo;//靜默失敗,屬性沒有刪除成功

    //在嚴格模式中,會拋出 TypeError 異常
    (function fail(){
        "use strict";
        //delete obj.foo;//拋出 TypeError 異常
        //obj.sparky = "arf";//拋出 TYpeError 異常
    })();

    Object.defineProperty(obj,"ohai",{value :17});//添加屬性失敗
    Object.defineProperty(obj,"foo",{value : "eit"});//修改爲功
    console.log(obj.foo);//「eit」
})();

如上面程序所示,將一個對象密封後僅能保證該對象不被擴展且屬性不可重配置,可是原屬性值倒是有可能被修改的,若要達到即密封不可修改原屬性值能夠這樣:

//建立不可修改值的密封對象
(function () {
    //方式一
    var o = {a:1};
    Object.defineProperty(o,"a",{configurable:false,writable:false});
    Object.preventExtensions(o);
    o.a = 2;
    console.log(o.a);//1
    console.log(Object.isExtensible(o) ===false);//true
    console.log(Object.isSealed(o) === true);//true

    //方式二
    o = Object.create(Object.prototype,{"a":{value :1,writable:false}});
    Object.seal(o);
    o.a = 2;
    console.log(o.a);//1
    console.log(Object.isExtensible(o) ===false);//true
    console.log(Object.isSealed(o) === true);//true
    
    //方式...
})();

一樣的,雖然實現了需求,依舊可使用另外一特性 凍結

凍結特性

一個對象是凍結的(frozen)是指它不可擴展,全部屬性都是不可配置的(non-configurable),且全部數據屬性(data properties)都是不可寫的(non-writable)。

數據屬性是值那些沒有取值器(getter)或賦值器(setter)的屬性。

或則說 凍結對象是指那些不能添加新的屬性,不能修改已有屬性的值,不能刪除已有屬性,以及不能修改已有屬性的可枚舉性、可配置性、可寫性的對象。也就是說,這個對象永遠是不可變的。

Object.isFrozen 方法

MDN:

概述
    Object.isFrozen() 方法判斷一個對象是否被凍結(frozen)。
語法
    Object.isFrozen(obj)
參數
obj 被檢測的對象
描述
    一個對象是凍結的(frozen)是指它不可擴展,全部屬性都是不可配置的(non-configurable),且全部數據屬性(data properties)都是不可寫的(non-writable)。數據屬性是值那些沒有取值器(getter)或賦值器(setter)的屬性。

使用:

(function () {
    //一個對象默認是可擴展的,因此他也是非凍結的.
    console.log(Object.isFrozen({}) === false);//true

    //一個不可擴展的空對象同時也是一個凍結對象.一個不可擴展的空對象也是密封對象
    var vacuouslyFrozen = Object.preventExtensions({});
    console.log(Object.isFrozen(vacuouslyFrozen) === true);//true
    console.log(Object.isSealed(vacuouslyFrozen) === true);//true

    //一個非空對象默認也是非凍結的.
    var oneProp = { p:42 };
    console.log(Object.isFrozen(oneProp) === false);//true

    //讓這個對象變的不可擴展,並不意味着這個對象變成了凍結對象,由於 p 屬性仍然是能夠配置的(並且可寫的).
    Object.preventExtensions( oneProp );
    console.log(Object.isFrozen(oneProp) === false);//true

    //若是刪除了這個屬性,則它成爲空對象,會成爲一個凍結對象.
    delete oneProp.p;
    console.log(Object.isFrozen(oneProp) === true);

    //一個不可擴展的對象,擁有一個不可寫但可配置的屬性,則它仍然是非凍結的.
    var nonWritable = { e : "plep" };
    Object.preventExtensions(nonWritable);
    Object.defineProperty(nonWritable,"e",{writable : false});//不可寫
    console.log(Object.isFrozen(nonWritable) === false);//true

    //把這個屬性改成不可配置,會讓這個對象成爲凍結對象
    Object.defineProperty(nonWritable,"e",{configurable : false});//不可配置
    console.log(Object.isFrozen(nonWritable) === true);//true

    //一個不可擴展的對象,擁有一個不可配置但可寫的屬性,則它仍然是非凍結的.
    var nonConfigurable = { release : "the kraken!" };
    Object.preventExtensions(nonConfigurable);
    Object.defineProperty(nonConfigurable,"release",{configurable : false});
    console.log(Object.isFrozen(nonConfigurable) === false);//true

    //把這個屬性改成不可寫,會讓這個對象成爲凍結對象.
    Object.defineProperty(nonConfigurable,"release",{writable : false});
    console.log(Object.isFrozen(nonConfigurable) === true);//true

    //一個不可擴展的對象,值擁有一個訪問器,則它仍然是非凍結的.
    var accessor = {get food(){return "yum";}};//這裏使用的是字面值法建立對象,默承認配置
    Object.preventExtensions(accessor);
    console.log(Object.isFrozen(accessor) === false);//true

    //把這個屬性改成不可配置,會讓這個對象成爲凍結對象.
    Object.defineProperty(accessor,"food",{configurable:false});
    console.log(Object.isFrozen(accessor) === true);//true


    //使用 Object.freeze 是凍結一個對象的最方便的方法.
    var frozen = {1:81};
    console.log(Object.isFrozen(frozen) === false);//true
    Object.freeze(frozen);
    console.log(Object.isFrozen(frozen) === true);//true

    //一個凍結對象也是一個密封對象
    console.log(Object.isSealed(frozen) === true);//true

    //一個凍結對象也是一個不可擴展對象
    console.log(Object.isExtensible(frozen) === false);//true

})();

Object.freeze 方法

MDN:

概述
    Object.freeze() 方法能夠凍結一個對象。
    凍結對象是指那些不能添加新的屬性,不能修改已有屬性的值,不能刪除已有屬性,以及不能修改已有屬性的可枚舉性、可配置性、可寫性的對象。
    也就是說,這個對象永遠是不可變的。該方法返回被凍結的對象。
語法
    Object.freeze(obj)
參數
    obj 將要被凍結的對象
描述
    凍結對象的全部自身屬性都不可能以任何方式被修改。
    任未嘗試修改該對象的操做都會失敗,多是靜默失敗,也可能會拋出異常(嚴格模式中)。
    數據屬性的值不可更改,訪問器屬性(有getter和setter)也一樣(但因爲是函數調用,給人的錯覺是仍是能夠修改這個屬性)。
    若是一個屬性的值是個對象,則這個對象中的屬性是能夠修改的,除非它也是個凍結對象。

使用:

(function () {
    var obj = {
        prop:function(){},
        foo:"bar"
    };

    //能夠添加新的屬性,已有的屬性能夠被修改或刪除
    obj.foo = "baz";
    obj.lumpy = "woof";
    delete obj.prop;

    Object.freeze(obj);//凍結

    console.log(Object.isFrozen(obj) === true);//true

    //對凍結對象的任何操做都會失敗
    obj.foo = "quux";//靜默失敗;
    obj.quaxxor = "the friendly duck";//靜默失敗

    //在嚴格模式中會拋出 TypeError 異常
    (function () {
        "use strict";
        obj.foo = "sparky";//拋出 TypeError 異常
        delete obj.quaxxor;//拋出 TypeError 異常
        obj.sparky = "arf";//拋出 TypeError 異常
    })();

    //使用 Object.defineProperty方法一樣會拋出 TypeError 異常
    Object.defineProperty(obj,"ohai",{value:17});//拋出 TypeError 異常
    Object.defineProperty(obj,"foo",{value:"eit"});//拋出 TypeError 異常
})();

如該方法 MDN 的描述所述,假若一個對象的屬性是一個對象,那麼對這個外部對象進行凍結,內部對象的屬性是依舊能夠改變的,這就叫淺凍結,若把外部對象凍結的同時把其全部內部對象甚至是內部的內部無限延伸的對象屬性也凍結了,這就叫深凍結。

淺凍結與深凍結

(function () {
    obj = {
        internal :{}
    };
    Object.freeze(obj);//淺凍結
    obj.internal.a = "aValue";
    console.log(obj.internal.a);//"aValue"

    //想讓一個對象變得徹底凍結,凍結全部對象中的對象,可使用下面的函數.
    function deepFreeze(o){
        var prop,propKey;
        Object.freeze(o);//首先凍結第一層對象
        for(propKey in o){
            prop = o[propKey];
            if(!o.hasOwnProperty(propKey) || !(typeof prop === "object") || Object.isFrozen(prop)){
                continue;
            }
            deepFreeze(prop);//遞歸
        }
    }

    deepFreeze(obj);
    obj.internal.b = "bValue";//靜默失敗
    console.log(obj.internal.b);//undefined
})();
相關文章
相關標籤/搜索