淺談 JS 對象添加 getter與 setter 的5種方法以及如何讓對象屬性不可配置或枚舉

  • 定義 getter 與 setterchrome

    1. 經過對象初始化器定義segmentfault

    2. 使用 Object.create 方法瀏覽器

    3. 使用 Object.defineProperty 方法函數

    4. 使用 Object.defineProperties 方法學習

    5. 使用 Object.prototype.__defineGetter__ 以及 Object.prototype.__defineSetter__ 方法優化

  • 什麼是屬性描述符this

  • 建立屬性不可配置不可枚舉的對象spa

  • Enumerable 特性firefox

  • Configurable 特新prototype

  • 提升及擴展

定義 getter 與 setter

1.經過對象初始化器在建立對象的時候指明(也能夠稱爲經過字面值建立對象時聲明)

(function () {
    var o = {
        a : 7,
        get b(){return this.a +1;},//經過 get,set的 b,c方法間接性修改 a 屬性
        set c(x){this.a = x/2}
    };
    console.log(o.a);
    console.log(o.b);
    o.c = 50;
    console.log(o.a);
})();

在 chrome 中調試視圖以下:
圖片描述

能夠看到對象下多了 get 屬性以及 set 屬性
輸出結果以下:
圖片描述

固然 get 語句與 set 語句能夠聲明屢次用來對應多個 gettersetter
使用這種方法的好處是能夠在聲明屬性的時候同時聲明對應的 gettersetter
這裏就有人問了,能不能將o 對象的 getset 方法的方法名都改爲 「a」,這樣就能夠直接經過「.」來訪問方法直接操做

(function () {
    var o = {
        a : 7,
        get a(){return this.a +1;},//死循環
        set a(x){this.a = x/2}
    };
    console.log(o.a);
    console.log(o.b);
    o.c = 50;
    console.log(o.a);
})();

打開 chrome 查看建立後的視圖以下:
圖片描述

能夠看到這個時候的 getset 方法已經和上面不一樣,可是是否真的能起做用呢,答案是否認的,當咱們經過 o.a 調用的是 get語句 聲明的 a方法,進入到該方法後遇到 this.a 方法繼續調用該方法造成死循環最終致使死循環報內存溢出錯誤。

新語法(ES6):暫時只有 firefox 支持,其餘瀏覽器會報錯

(function () {
    var b = "bb";
    var c = "cc";
    var o = {
        a : 7,
        get [b](){return this.a +1;},
        set [c](x){this.a = x/2},
    };
    console.log(o.a);
    console.log(o[b]);
    o["cc"] = 50;
    console.log(o.a);
})();

打開 firefox 查看調試:
圖片描述

輸出結果以下:
圖片描述

2.使用 Object.create 方法

引用 MDN:

概述
    Object.create() 方法建立一個擁有指定原型和若干個指定屬性的對象。

語法
    Object.create(proto, [ propertiesObject ])

咱們都知道使用 Object.create 方法傳遞一個參數的時候能夠建立一個以該參數爲原型的對象 淺談 JS 建立對象的 8 種模式
第二個參數是可選項,是一個匿名的參數對象,該參數對象是一組屬性與值,該對象的屬性名稱將是新建立的對象的屬性名稱,值是屬性描述符(包擴數據描述符或存取描述符,具體解釋看後面的內容 什麼是屬性描述符)。
經過屬性描述符咱們能夠實現爲新建立的對象添加 get 方法以及 set 方法

(function () {
    var o = null;
    o = Object.create(Object.prototype,//指定原型爲 Object.prototype
            {
                bar:{
                    get :function(){
                        return 10;
                    },
                    set : function (val) {
                        console.log("Setting `o.bar` to ",val);
                    }
                }
            }//第二個參數
        );
    console.log(o.bar);
    o.bar = 12;
})();

在 chrome 中調試試圖以下:
圖片描述

能夠看到新建立對象通用多了 get 以及 set 屬性
輸出結果以下:
圖片描述

上面這個例子並無用來針對的 get 方法以及 set 方法使用的屬性

(function () {
    var o = null;
    o = Object.create(Object.prototype,//指定原型爲 Object.prototype
            {
                bar:{
                    get :function(){
                        return this.a;
                    },
                    set : function (val) {
                        console.log("Setting `o.bar` to ",val);
                        this.a = val;
                    },
                    configurable :true
                }
            }//第二個參數
        );
    o.a = 10;
    console.log(o.bar);
    o.bar = 12;
    console.log(o.bar);
})();

亦或:

(function () {
    var o = {a:10};
    o = Object.create(o,//指定原型爲 o 這裏實際能夠理解爲繼承
            {
                bar:{
                    get :function(){
                        return this.a;
                    },
                    set : function (val) {
                        console.log("Setting `o.bar` to ",val);
                        this.a = val;
                    },
                    configurable :true
                }
            }//第二個參數
        );
    console.log(o.bar);
    o.bar = 12;
    console.log(o.bar);
})();

輸出結果以下:
圖片描述

使用這種方式的好處是可配置性高,但初學者容易迷糊。

3.使用 Object.defineProperty 方法
引用 MDN:

概要
    Object.defineProperty() 方法直接在一個對象上定義一個新屬性,或者修改一個已經存在的屬性, 並返回這個對象。
語法
    Object.defineProperty(obj, prop, descriptor)
參數
    obj
        須要定義屬性的對象。
    prop
        需被定義或修改的屬性名。
    descriptor
        需被定義或修改的屬性的描述符。
(function () {
    var o = { a : 1}//聲明一個對象,包含一個 a 屬性,值爲1
    Object.defineProperty(o,"b",{
        get: function () {
            return this.a;
        },
        set : function (val) {
            this.a = val;
        },
        configurable : true
    });

    console.log(o.b);
    o.b = 2;
    console.log(o.b);
})();

這個方法與前面兩種的區別是:使用前面兩種只能在聲明定義的時候指定 gettersetter,使用該方法能夠隨時的添加或修改。

若是說須要一次性批量添加 getter 與 setter 也是沒問題的,使用以下方法:

4.使用 Object.defineProperties方法

MDN:

概述
    Object.defineProperties() 方法在一個對象上添加或修改一個或者多個自有屬性,並返回該對象。
語法
    Object.defineProperties(obj, props)
參數
    obj
    將要被添加屬性或修改屬性的對象
    props
    該對象的一個或多個鍵值對定義了將要爲對象添加或修改的屬性的具體配置

不難看出用法與 Object.defineProperty 方法相似

(function () {
    var obj = {a:1,b:"string"};
    Object.defineProperties(obj,{
        "A":{
            get:function(){return this.a+1;},
            set:function(val){this.a = val;}
        },
        "B":{
            get:function(){return this.b+2;},
            set:function(val){this.b = val}
        }
    });

    console.log(obj.A);
    console.log(obj.B);
    obj.A = 3;
    obj.B = "hello";
    console.log(obj.A);
    console.log(obj.B);
})();

輸出結果以下:
圖片描述

5.使用 Object.prototype.__defineGetter__ 以及 Object.prototype.__defineSetter__ 方法

(function () {
    var o = {a:1};
    o.__defineGetter__("giveMeA", function () {
        return this.a;
    });
    o.__defineSetter__("setMeNew", function (val) {
        this.a  = val;
    })
    console.log(o.giveMeA);
    o.setMeNew = 2;
    console.log(o.giveMeA);
})();
輸出結果爲1和2

查看 MDN 有以下說明:
圖片描述

什麼是屬性描述符

MDN:

對象裏目前存在的屬性描述符有兩種主要形式:數據描述符和存取描述符。
  1. 數據描述符是一個擁有可寫或不可寫值的屬性。

  2. 存取描述符是由一對 getter-setter 函數功能來描述的屬性。

  3. 描述符必須是兩種形式之一;不能同時是二者。

數據描述符和存取描述符均具備如下可選鍵值:

configurable
    當且僅當這個屬性描述符值爲 true 時,該屬性可能會改變,也可能會被從相應的對象刪除。默認爲 false。
enumerable  
    true 當且僅當該屬性出如今相應的對象枚舉屬性中。默認爲 false。

數據描述符同時具備如下可選鍵值:

value 
    與屬性相關的值。能夠是任何有效的 JavaScript 值(數值,對象,函數等)。默認爲 undefined。
writable 
    true 當且僅當可能用 賦值運算符 改變與屬性相關的值。默認爲 false。

存取描述符同時具備如下可選鍵值:

get 
    一個給屬性提供 getter 的方法,若是沒有 getter 則爲 undefined。方法將返回用做屬性的值。默認爲 undefined。
set
    一個給屬性提供 setter 的方法,若是沒有 setter 則爲 undefined。該方法將收到做爲惟一參數的新值分配給屬性。默認爲 undefined。

以上是摘自MDN的解釋,看起來是很晦澀的,具體什麼意思呢:
首先咱們從以上解釋知道該匿名參數對象有個很好聽的名字叫屬性描述符,屬性描述符又分紅兩大塊:數據描述符以及存取描述符(其實只是一個外號,給指定的屬性集合起個外號)。

數據描述符包括兩個屬性 : value 屬性以及 writable 屬性,第一個屬性用來聲明當前欲修飾的屬性的值,第二個屬性用來聲明當前對象是否可寫便是否能夠修改

存取描述符就包括 getset 屬性用來聲明欲修飾的象屬性的 gettersetter

屬性描述符內部,數據描述符與存取描述符只能存在其中之一,可是不論使用哪一個描述符均可以同時設置 configurable 屬性以及enumerable 屬性。
configurable屬性用來聲明欲修飾的屬性是否可以配置,僅有當其值爲 true 時,被修飾的屬性纔有可能可以被刪除,或者從新配置。
enumerable 屬性用來聲明欲修飾屬性是否能夠被枚舉

知道了什麼是屬性描述符,咱們就能夠開始着手建立一些對象並開始配置其屬性

建立屬性不可配置不可枚舉的對象

//使用默認值配置
(function () {
    var obj = {};//聲明一個空對象
    Object.defineProperty(obj,"key",{
        value:"static"
                        //沒有設置 enumerable 使用默認值 false
                        //沒有 configurable 使用默認值 false
                        //沒有 writable 使用默認值 false
    });

    console.log(obj.key);           //輸出 「static」
    obj.key = "new"                 //嘗試修改其值,修改將失敗,由於 writable 爲 false
    console.log(obj.key);           //輸出 「static」
    obj.a = 1;//動態添加一個屬性
    for(var item in obj){ //遍歷全部 obj 的可枚舉屬性
         console.log(item);
    }//只輸出一個 「a」 由於 「key」的 enumerable爲 false
})();
//顯示配置 等價於上面
(function () {
    var obj = {};
    Object.defineProperty(obj,"key",{
        enumerable : false,
        configurable : false,
        writable : false,
        value : "static"
    })
})();
//等價配置
(function () {
    var o = {};
    o.a = 1;
    //等價於
    Object.defineProperty(o,"a",{value : 1,
                                writable : true,
                                configurable : true,
                                enumerable : true});
    
    Object.defineProperty(o,"a",{value :1});
    //等價於
    Object.defineProperty(o,"a",{value : 1,
                                writable : false,
                                configurable : false,
                                enumerable : false});
})();

Enumerable 特性
屬性特性 enumerable 決定屬性是否能被 for...in 循環或 Object.keys 方法遍歷獲得

(function () {
    var o = {};
    Object.defineProperty(o,"a",{value :1,enumerable :true});
    Object.defineProperty(o,"b",{value :2,enumerable :false});
    Object.defineProperty(o,"c",{value :2});//enumerable default to false
    o.d = 4;//若是直接賦值的方式建立對象的屬性,則這個屬性的 enumerable 爲 true

    for(var item in o){ //遍歷全部可枚舉屬性包括繼承的屬性
        console.log(item);
    }

    console.log(Object.keys(o));//獲取 o 對象的全部可遍歷屬性不包括繼承的屬性

    console.log(o.propertyIsEnumerable('a'));//true
    console.log(o.propertyIsEnumerable('b'));//false
    console.log(o.propertyIsEnumerable('c'));//false
})();

輸出結果以下:
圖片描述

Configurable 特性

(function () {
    var o = {};
    Object.defineProperty(o,"a",{get: function () {return 1;},
                                configurable : false} );
                                //enumerable 默認爲 false,
                                //value 默認爲 undefined,
                                //writable 默認爲 false,
                                //set 默認爲 undefined
                                  
    //拋出異常,由於最開始定義了 configurable 爲 false,故後期沒法對其進行再配置
    Object.defineProperty(o,"a",{configurable : true} );
    //拋出異常,由於最開始定義了 configurable 爲 false,故後期沒法對其進行再配置,enumerable 的原值爲 false
    Object.defineProperty(o,"a",{enumerable : true} );
    //拋出異常,由於最開始定義了 configurable 爲 false,set的原值爲 undefined
    Object.defineProperty(o,"a",{set : function(val){}} );
    //拋出異常,由於最開始定義了 configurable 爲 false,故沒法進行覆蓋,儘管想用同樣的來覆蓋
    Object.defineProperty(o,"a",{get : function(){return 1}});
    //拋出異常,由於最開始定義了 configurable 爲 false,故沒法將其進行從新配置把屬性描述符從存取描述符改成數據描述符
    Object.defineProperty(o,"a",{value : 12});

    console.log(o.a);//輸出1
    delete o.a;      //想要刪除屬性,將失敗
    console.log(o.a);//輸出1
    
})();

提升及擴展
1.屬性描述符中容易被誤導的地方之 writableconfigurable

(function () {
    var o = {};
    Object.defineProperties(o,{
        "a": {
            value:1,
            writable:true,//可寫
            configurable:false//不可配置
            //enumerable 默認爲 false 不可枚舉
        },
        "b":{
            get :function(){
                return this.a;
            },
            configurable:false
        }
    });
    console.log(o.a);   //1
    o.a = 2;            //修改值成功,writable 爲 true
    console.log(o.a);   //2
    Object.defineProperty(o,"a",{value:3});//一樣爲修改值成功
    console.log(o.a);   //3

    //將其屬性 b 的屬性描述符從存取描述符從新配置爲數據描述符
    Object.defineProperty(o,"b",{value:3});//拋出異常,由於 configurable 爲 false
})();

2.經過上面的學習,咱們都知道傳遞屬性描述符參數時,是定義一個匿名的對象,裏面包含屬性描述符內容,若每定義一次便要建立一個匿名對象傳入,將會形成內存浪費。故優化以下:

(function () {
    var obj = {};

    //回收同一對象,即減小內存浪費
    function withValue(value){
        var d = withValue.d ||(
            withValue.d = {
                enumerable : false,
                configurable : false,
                writable : false,
                value :null
            }
            );
        d.value = value;
        return d;
    }
    Object.defineProperty(obj,"key",withValue("static"))
})();
相關文章
相關標籤/搜索