之前的文章《解析vue中observer數(shù)據(jù)雙向綁定原理(代碼分享)》中,給大家了解了vue中observer數(shù)據(jù)雙向綁定原理。下面本篇文章給大家了解vue中watcher數(shù)據(jù)雙向綁定原理,一定的參考價(jià)值,有需要的朋友可以參考一下。
vue
數(shù)據(jù)雙向綁定原理,和簡單的實(shí)現(xiàn),本文將實(shí)現(xiàn)mvvm
的watcher
1)vue數(shù)據(jù)雙向綁定原理-observer
2)vue數(shù)據(jù)雙向綁定原理-wather
3)vue數(shù)據(jù)雙向綁定原理-解析器Complie
vue
數(shù)據(jù)雙向綁定原理,和簡單的實(shí)現(xiàn),本文將實(shí)現(xiàn)mvvm
的Watcher
上面的步驟已經(jīng)實(shí)現(xiàn)了監(jiān)聽器,和訂閱器,當(dāng)屬性發(fā)生改變,發(fā)出通知,那么這個(gè)通知是通知誰呢,肯定是訂閱者watcher
.Watcher
訂閱者作為Observer
和Compile
之間通信的橋梁,主要做的事情是:
1、在自身實(shí)例化時(shí)往屬性訂閱器(dep
)里面添加自己
2、自身必須有一個(gè)update()
方法
3、待屬性變動dep.notice()
通知時(shí),能調(diào)用自身的update()
方法,并觸發(fā)Compile
中綁定的回調(diào),則釋放自己。
// Watcher function Watcher(vm, exp, cb) { this.cb = cb; this.$vm = vm; this.exp = exp; // 此處為了觸發(fā)屬性的getter,從而在dep添加自己,結(jié)合Observer更易理解 this.value = this.get(); // 將自己添加到訂閱器的操作 } Watcher.prototype = { update: function () { this.run(); // 屬性值變化收到通知 }, run: function () { var value = this.get(); // 取到最新值 var oldVal = this.value; if (value !== oldVal) { this.value = value; this.cb.call(this.$vm, value, oldVal); // 執(zhí)行Compile中綁定的回調(diào),更新視圖 } }, get: function () { Dep.target = this; // 將當(dāng)前訂閱者指向自己, 緩存 var value = this.$vm[this.exp]; // 強(qiáng)制觸發(fā)監(jiān)聽的getter,添加自己到屬性訂閱器中 Dep.target = null; // 添加完畢,重置釋放 return value; }, };
訂閱者要緩存自己,并且告訴監(jiān)聽器,要把我加到訂閱器里面去。所以還要改造下監(jiān)聽器
function defineReactive(data, key, val) { var dep = new Dep() observe(val); // 監(jiān)聽子屬性 Object.defineProperty(data, key, { .... get: function() { // 由于需要在閉包內(nèi)添加watcher,所以可以在Dep定義一個(gè)全局target屬性,暫存watcher, 添加完移除 Dep.target && dep.addDep(Dep.target); return val; }, .... }); }
實(shí)例化Watcher
的時(shí)候,調(diào)用get()
方法,通過Dep.target=watcherInstance
標(biāo)記訂閱者是當(dāng)前watcher
實(shí)例,強(qiáng)行觸發(fā)屬性定義的getter
方法,getter
方法執(zhí)行的時(shí)候,就會在屬性的訂閱器dep
添加當(dāng)前watcher
實(shí)例,從而在屬性值有變化的時(shí)候watcherInstance
就能收到更新通知。
實(shí)現(xiàn)MVVM
到這兒先將監(jiān)聽器Observer
和監(jiān)聽者Watcher
連起來,先模擬一些數(shù)據(jù),實(shí)現(xiàn)簡單的數(shù)據(jù)綁定
<div id="name"></div> <script> function Vue(data, el, exp) { this.data = data; observe(data); el.innerHTML = this.data[exp]; // 初始化模板數(shù)據(jù)的值 new Watcher(this, exp, function (value) { el.innerHTML = value; }); return this; } var ele = document.querySelector("#name"); var vue = new Vue( { name: "hello world", }, ele, "name" ); setInterval(function () { vue.data.name = "chuchur " + new Date() * 1; }, 1000); </script>
這可以看到div
的和內(nèi)容初始為hello world
,每隔一秒之后變換為chuchur
加時(shí)間戳,雖然是實(shí)現(xiàn)了,但是與想象的還差很多。是vue.name
不是vue.data.name
,所以這里需要給Vue
實(shí)例添加一個(gè)屬性代理的方法,使訪問vm
的屬性代理為訪問vm.data
的屬性,改造后的代碼如下:
function Vue(options) { this.$options = options || {}; this.data = this.$options.data; // 屬性代理,實(shí)現(xiàn) vm.xxx -> vm.data.xxx var self = this; Object.keys(this.data).forEach(function(key) { self.proxy(key); // 綁定代理屬性 }); observe(this.data, this); el.innerHTML = this.data[exp]; // 初始化模板數(shù)據(jù)的值 new Watcher(this, exp, function(value) { el.innerHTML = value; }); return this; } Vue.prototype = { proxy: function(key) { var self = this; Object.defineProperty(this, key, { enumerable: false, configurable: true, get: function proxyGetter() { return self.data[key]; }, set: function proxySetter(newVal) { self.data[key] = newVal; } }); } }
然后就可以通過vue.name
,直接改版模板的數(shù)據(jù)了,下一步就要實(shí)現(xiàn)解析器Complie
[完]
推薦學(xué)習(xí):JavaScript視頻教程