JS對象添加getter與setter的5種方法

 

定義 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 語句可以聲明多次用來對應多個 getter 和 setter
使用這種方法的好處是可以在聲明屬性的時候同時聲明對應的 getter 和 setter
這裏就有人問了,能不能將o 對象的 get 及 set 方法的方法名都改成 “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 查看創建後的視圖如下: 

可以看到這個時候的 get 與 set 方法已經和上面不同,但是是否真的能起作用呢,答案是否定的,當我們通過 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 方法傳遞一個參數的時候可以創建一個以該參數爲原型的對象。

第二個參數是可選項,是一個匿名的參數對象,該參數對象是一組屬性與值,該對象的屬性名稱將是新創建的對象的屬性名稱,值是屬性描述符(包擴數據描述符或存取描述符,具體解釋看後面的內容 什麼是屬性描述符)。

通過屬性描述符我們可以實現爲新創建的對象添加 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);
})();

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

如果說需要一次性批量添加 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:

對象裏目前存在的屬性描述符有兩種主要形式:數據描述符和存取描述符。

  • 數據描述符是一個擁有可寫或不可寫值的屬性。

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

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

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

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

存取描述符同時具有以下可選鍵值:

get

一個給屬性提供 getter 的方法,如果沒有 getter 則爲 undefined。方法將返回用作屬性的值。默認爲 undefined。 set 一個給屬性提供 setter 的方法,如果沒有 setter 則爲 undefined。該方法將收到作爲唯一參數的新值分配給屬性。默認爲 undefined。

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

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

存取描述符就包括 get 與 set 屬性用來聲明欲修飾的象屬性的 getter 及 setter

屬性描述符內部,數據描述符與存取描述符只能存在其中之一,但是不論使用哪個描述符都可以同時設置 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.屬性描述符中容易被誤導的地方之 writable 與 configurable

(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"))
})();

小編是一個有着5年工作經驗的架構師,關於web前端,自己有做材料的整合,一個完整學習web前端的路線,學習材料和工具。能夠進我的羣537631707收取,免費送給大家。希望你也能憑自己的努力,成爲下一個優秀的程序員!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章