前端數據雙向綁定原理:Object.defineProperty()

Object.definedProperty方法能夠在一個對象上直接定義一個新的屬性、或修改一個對象已經存在的屬性,最終返回這個對象。html

Object.defineProperty(obj, prop, descriptor)數組

參數:

obj:被定義或修改屬性的對象;

prop :要定義或修改的屬性名稱;

descriptor :對屬性的描述;

返回值: obj

描述符(descriptor)說明

 

該方法容許開發者精確的對對象屬性的定義和修改。經過正常賦值進行屬性添加而構建的屬性會被枚舉器方法(如for..in循環或Object.keys方法)獲取,從而致使屬性值被外部方法改變或刪除。而Object.defineProperty()能夠避免以上描述的狀況,經過Object.defineProperty()添加的屬性是默認不可改變的。 
屬性描述參數(descriptor)主要由兩部分構成:數據描述符(data descriptor)和訪問器描述符(accessor descriptor)。數據描述符就是一個包含屬性的值,並說明這個值可讀或不可讀的對象;訪問器描述符就是包含該屬性的一對getter-setter方法的對象。一個完整的屬性描述(descriptor)必須是這二者之一,而且不能夠二者都有。app

數據描述符和訪問器描述符各自都是對象,他們必須包含如下鍵值對:

configurable 
僅當設置的屬性的描述符須要被修改或須要經過delete來刪除該屬性時,configurable屬性設置爲true。默認爲false。this

enumerable 
僅當設置的屬性須要被枚舉器(如for..in)訪問時設置爲true。默認爲false。spa

數據描述符能夠包含如下可選鍵值對:

value 
設置屬性的值,能夠是任何JavaScript值類型(number,object,function等類型)。默認爲undefined。prototype

writable 
僅當屬性的值能夠被賦值操做修改時設置爲true。默認爲false。雙向綁定

訪問器描述符能夠包含如下可選鍵值對:

get 
屬性的getter方法,若屬性沒有getter方法則爲undefined。該方法的返回爲屬性的值。默認爲undefined。日誌

set 
屬性的setter方法,若屬性沒有setter方法則爲undefined。該方法接收惟一的參數,做爲屬性的新值。默認爲undefined。code

請牢記,這些描述符的屬性並非必須的,從原型鏈繼承而來的屬性也可填充。爲了保證這些描述符屬性被填充爲默認值,你可能會使用形如預先凍結Object.prototype、明確設置每一個描述符屬性的值、使用Object.create(null)來獲取空對象等方式。htm

一個簡單的例子:

 1 var obj = {};
 2 var descriptor = Object.create(null); // no inherited properties
 3 
 4 //全部描述符的屬性被設置爲默認值
 5 descriptor.value = 'static';
 6 Object.defineProperty(obj, 'key', descriptor);
 7 
 8 //明確設置每一個描述符的屬性
 9 Object.defineProperty(obj, 'key', {
10   enumerable: false,
11   configurable: false,
12   writable: false,
13   value: 'static'
14 });
15 
16 //重用同一個對象做爲描述符
17 function withValue(value) {
18   var d = withValue.d || (
19     withValue.d = {
20       enumerable: false,
21       writable: false,
22       configurable: false,
23       value: null
24     }
25   );
26   d.value = value;
27   return d;
28 }
29 Object.defineProperty(obj, 'key', withValue('static'));
30 
31 //若是Object.freeze方法可用,則使用它來防止對對象屬性的修改 
32 (Object.freeze || Object)(Object.prototype);

 

使用示例

建立一個屬性

若是當前對象不存在咱們要設置的屬性,Object.defineProperty()會根據方法設置爲對象建立一個新的屬性。若是描述符參數缺失,則會被設置爲默認值。全部布爾型描述符屬性會被默認設置爲false。而value,get,set會被默認設置爲undefined。一個未設置get/set/value/writable的屬性被稱爲一個「原生屬性(generic)」,而且他的描述符(descriptor)會被「歸類」爲一個數據描述符(data descriptor)

var o = {}; //建立一個對象

//使用數據描述符來爲對象添加屬性
Object.defineProperty(o, 'a', {
  value: 37,
  writable: true,
  enumerable: true,
  configurable: true
});
//屬性」a」被設置到對象o上,而且值爲37

//使用訪問器描述符來爲對象添加屬性
var bValue = 38;
Object.defineProperty(o, 'b', {
  get: function() { return bValue; },
  set: function(newValue) { bValue = newValue; },
  enumerable: true,
  configurable: true
});
o.b; // 38
//屬性」b」被設置到對象o上,而且值爲38。
//如今o.b的值指向bValue變量,除非o.b被從新定義

//你不能嘗試混合數據、訪問器兩種描述符
Object.defineProperty(o, 'conflict', {
  value: 0x9f91102,
  get: function() { return 0xdeadbeef; }
});
//拋出一個類型錯誤: value appears only in data descriptors, get appears only in accessor descriptors(value只出如今數據描述符中,get只出如今訪問器描述符中)

 

修改一個屬性

當某個屬性已經存在了,Object.defineProperty()會根據對象的屬性配置(configuration)和新設置的值來嘗試修改該屬性。若是該屬性的configurable被設置爲false,則該屬性沒法被修改(這種狀況下有個特殊狀況:若是以前的writable設置爲true,則咱們仍能夠將writable設置爲false,一旦這麼作以後,任何描述符屬性將變得不可設置)。若是屬性的configurable設置爲false,則咱們沒法將屬性的描述符在數據描述符和訪問器描述符之間轉換。 
若是新設置的屬性和該屬性不一樣,而且該屬性的configurable被設置爲false,則一個類型錯誤(TypeError)會被拋出(除了上一段文字中說的特殊狀況)。若新舊屬性徹底相同,則什麼都不會發生。

可寫特性-writable

當一個屬性的writable被設置爲false,這個屬性就成爲「不可寫的(non-writable)」。該屬性不可被從新賦值。

var o = {}; //建立一個對象

Object.defineProperty(o, 'a', {
  value: 37,
  writable: false
});

console.log(o.a); // 37
o.a = 25; //沒有錯誤拋出
//在嚴格模式下會拋出錯誤
console.log(o.a); //仍然是37,賦值操做無效

正如上述代碼所述,嘗試重寫一個「不可寫(non-writable)」屬性不會發生任何改變,也不會拋出錯誤。

可枚舉特性-enumerable

屬性的enumerable值定義對象的屬性是否會出如今枚舉器(for..in循環和Object.keys())中。

var o = {};
Object.defineProperty(o, 'a', {
  value: 1,
  enumerable: true
});
Object.defineProperty(o, 'b', {
 value: 2,
 enumerable: false
});
Object.defineProperty(o, 'c', {
  value: 3
}); //enumerable默認設置爲false
o.d = 4; //經過直接設置屬性的方式,enumerable將被設置爲true

for (var i in o) {
  console.log(i);
}
//打印出’a’和’d’

Object.keys(o); // ['a', 'd']

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

可配置特性-configurable

屬性的configurable值控制一個對象的屬性能否被delete刪除,同時也控制該屬性描述符的配置能否改變(除了前文所述在configurable爲false時,若writable爲true,則仍能夠進行一次修改將writable改變爲false)。

var o = {};
Object.defineProperty(o, 'a', {
  get: function() { return 1; },
  configurable: false
});

Object.defineProperty(o, 'a', {
  configurable: true
}); //拋出錯誤
Object.defineProperty(o, 'a', {
  enumerable: true
}); //拋出錯誤
Object.defineProperty(o, 'a', {
  set: function() {}
}); //拋出錯誤(set以前被設置爲undefined)
Object.defineProperty(o, 'a', {
  get: function() { return 1; }
}); //拋出錯誤(即便新的get作的是相同的事,但方法的先後引用不相同)
Object.defineProperty(o, 'a', {
  value: 12
}); //拋出錯誤

console.log(o.a); // 1
delete o.a; //什麼都不發生
console.log(o.a); // 1

若是o.a屬性的configurable爲true,就不會有任何錯誤拋出,而且o.a在最後的delete操做中會被刪除。

添加屬性時的默認值

考慮描述符特性的默認值如何被應用是很是重要的。正以下面示例所示,簡單的使用」.」符號來設置一個屬性和使用Object.defineProperty()是有很大區別的。

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

定製的Setters和Getters

下面的示例展現瞭如何實現一個「自存檔(self-archiving)」的對象。當temperature屬性被設置時,archive數組就會添加一個日誌記錄。

function Archiver() {
  var temperature = null;
  var archive = [];

  Object.defineProperty(this, 'temperature', {
    get: function() {
      console.log('get!');
      return temperature;
    },
    set: function(value) {
      temperature = value;
      archive.push({ val: temperature });
    }
  });

  this.getArchive = function() { return archive; };
}

var arc = new Archiver();
arc.temperature; // 'get!'
arc.temperature = 11;
arc.temperature = 13;
arc.getArchive(); // [{ val: 11 }, { val: 13 }]

或者下面這樣寫也是一樣的效果:

var pattern = {
    get: function () {
        return 'I always return this string, whatever you have assigned';
    },
    set: function () {
        this.myname = 'this is my name string';
    }
};


function TestDefineSetAndGet() {
    Object.defineProperty(this, 'myproperty', pattern);
}


var instance = new TestDefineSetAndGet();
instance.myproperty = 'test';
console.log(instance.myproperty);
// I always return this string, whatever you have assigned

console.log(instance.myname); // this is my name string

最後放上一個利用Object.defineProperty()實現的簡單的雙向綁定的例子

<!doctype html>
<html lang="en">
 <head>
  <meta charset="UTF-8">
  <meta name="Generator" content="EditPlus®">
  <meta name="Author" content="">
  <meta name="Keywords" content="">
  <meta name="Description" content="">
  <title>Document</title>
 </head>
 <body>
    <input type="text" id="aa"/>
    <span id="bb">{{hello}}</span>
    <script>
        var obj = {};
        Object.defineProperty(obj,'hello',{
            set:function(val){
                document.querySelector('#bb').innerHTML = val;
                document.querySelector('#aa').value = val;
            }
        });
        document.querySelector('#aa').oninput = function(e){
            obj.hello = e.target.value;
        };
        obj.hello = "";
        obj.hello = "abc";
    </script>
 </body>
</html>
相關文章
相關標籤/搜索