在"模擬Vue之數據驅動2"中,我們實現了個Observer構造函數,通過它可以達到監聽已有數據data中的所有屬性。 但,倘若我們想在某個對象中,新增某個屬性呢? 如下: 那麼豈不是,新增的infor屬性,以及它的對象屬性,沒有得到監聽。 此時,應該怎麼處理呢? 通過走讀Vue源碼,發現他是採用另 ...
一、前言 |
在"模擬Vue之數據驅動2"中,我們實現了個Observer構造函數,通過它可以達到監聽已有數據data中的所有屬性。
但,倘若我們想在某個對象中,新增某個屬性呢?
如下:
那麼豈不是,新增的infor屬性,以及它的對象屬性,沒有得到監聽。
此時,應該怎麼處理呢?
通過走讀Vue源碼,發現他是採用另增屬性方法$set實現的。
就是說,如果我們採用常規方法為對象增加屬性(如上),我們沒法得知並監控它,所以,我們為每個對象擴展一個$set方法,用於另增屬性使用,即可,如下:
data.user.$set('infor', {msg: 'happy'});
好了,下麵,我們就一同實現這個$set方法吧。
二、$set方法實現 |
首先,我們得創建一個恆定extendObj對象,用於將$set方法綁定在其中。
你可能會想,為什麼我們需要一個extendObj對象呢?直接將$set函數賦值給每個需要監聽的對象不就完了麽?
是的,這樣也可以,但是隨著需求增長,倘若我們又想為每個監聽對象擴展其他方法呢?難道又要去Observer裡面為對象,一一賦值?
so,創建恆定extendObj對象,如下:
const extendObj = {};
因為,我們將$set綁定到extendObj中,且讓$set為不可枚舉型,所以會用到Object.defineProperty,固將其提取出來,作為一個方法如下:
function proxyObject(obj, key, val, enume){ Object.defineProperty(obj, key, { value: val, enumerable: !!enume, writable: true, configurable: true }); };
接下來,就是實現$set方法了,整體結構如下:
proxyObject(extendObj, '$set', function(key, val){ //this指向extendObj if(this.hasOwnProperty(key)){ return; }else{ /* TODO:在extendObj中監聽key屬性, 且,若key屬性值為對象,再次監聽key屬性值 */ } });
看到上面的TODO註釋,是否似曾相識,不就是是在“模擬Vue之數據驅動2”遇見過的嘛,通過Observer.prototype.convert監聽key屬性,通過new Observer再次監聽key屬性值不就完啦。
的確,但是一旦這樣做了,不就和上面我們提到的“直接將$set賦予監聽對象”問題一樣嘛,耦合性太大,且隨著需求上漲,不易維護。
固而,在此需要一點小技巧:在observer模塊中為每個監聽對象賦予一個$Observer屬性,其值指向Observer自身實例,如下:
//observer.js p.walk = function(data){ let keys = Object.keys(data); keys.forEach( key => { let val = data[key]; if(typeof val === 'object'){ new Observer(val); } this.convert(key, val); }); //$Observer屬性指向Observer自身實例 data.$Observer = this; } //新增一個observe方法 p.observe = function(data){ if(typeof data === 'object'){ new Observer(data); } }
好了,這樣之後,得$set整體實現如下:
proxyObject(extendObj, '$set', function(key, val){ if(this.hasOwnProperty(key)){ return; }else{ proxyObject(this, key, val, true); let ob = this.$Observer; ob.observe(val); ob.convert(key, val); } });
到此,一個簡單的$set方法構建完畢。
在上面我們提到,之所以需要一個恆定extendObj對象,是為了更好的代碼管理。且,到目前為止,需要監聽的對象上並沒有擴展$set方法呢,所以,下麵的事情就是為了達到以上效果,如下:
//observer.js function Observer(data){ if(!(this instanceof Observer)){ return new Observer(data); } //將監聽對象的隱指針指向我們的extendObj對象 data.__proto__ = extendObj; this.data = data; this.walk(data); }
好了,一切完畢,接下來就測試下吧:
<script src="./extendObj.js"></script> <script src="./observer.js"></script> <script> let data = { user: { name: 'Monkey', age: 24 }, lover: { name: 'Dorie', age: 23 } }; Observer(data); </script>
效果如下:
Perfect,完整代碼見github。