看過vue官方文檔的同窗,對這張圖應該已然至關熟悉了。javascript
vue的響應式是如何實現的?vue
聽過太多回答,經過Object.defineProperty
,但是再詳細的問時,對方渾然不知。java
const Observer = function(data) { for (let key in data) { defineReactive(data, key); } } const defineReactive = function(obj, key) { const dep = new Dep(); let val = obj[key]; Object.defineProperty(obj, key, { enumerable: true, configurable: true, get() { console.log('in get'); dep.depend(); return val; }, set(newVal) { if (newVal === val) { return; } val = newVal; dep.notify(); } }); } const observe = function(data) { return new Observer(data); } const Vue = function(options) { const self = this; if (options && typeof options.data === 'function') { this._data = options.data.apply(this); } this.mount = function() { new Watcher(self, self.render); } this.render = function() { with(self) { _data.text; } } observe(this._data); } const Watcher = function(vm, fn) { const self = this; this.vm = vm; Dep.target = this; this.addDep = function(dep) { dep.addSub(self); } this.update = function() { console.log('in watcher update'); fn(); } this.value = fn(); Dep.target = null; } const Dep = function() { const self = this; this.target = null; this.subs = []; this.depend = function() { if (Dep.target) { Dep.target.addDep(self); } } this.addSub = function(watcher) { self.subs.push(watcher); } this.notify = function() { for (let i = 0; i < self.subs.length; i += 1) { self.subs[i].update(); } } } const vue = new Vue({ data() { return { text: 'hello world' }; } }) vue.mount(); // in get vue._data.text = '123'; // in watcher update /n in get
這裏咱們用不到100行的代碼,實現了一個簡易的vue響應式。固然,這裏若是不考慮期間的過程,我相信,40行代碼以內能夠搞定。可是我這裏不想省略,爲何呢?我怕你把其中的過程自動忽略掉,怕別人問你相關東西的時候,明明本身看過了,卻被懟的啞口無言。總之,我是爲了你好,多喝熱水。app
依賴收集器,這不是官方的名字蛤,我本身起的,爲了好記。
用兩個例子來看看依賴收集器的做用吧。函數
例子1,毫無心義的渲染是否是不必?測試
const vm = new Vue({ data() { return { text: 'hello world', text2: 'hey', } } })
當vm.text2
的值發生變化時,會再次調用render
,而template
中卻沒有使用text2
,因此這裏處理render
是否是毫無心義?this
針對這個例子還記得咱們上面模擬實現的沒,在Vue
的render
函數中,咱們調用了本次渲染相關的值,因此,與渲染無關的值,並不會觸發get
,也就不會在依賴收集器中添加到監聽(addSub
方法不會觸發),即便調用set
賦值,notify
中的subs
也是空的。OK,繼續迴歸demo,來一小波測試去印證下我說的吧。spa
const vue = new Vue({ data() { return { text: 'hello world', text2: 'hey' }; } }) vue.mount(); // in get vue._data.text = '456'; // nothing vue._data.text2 = '123'; // in watcher update /n in get
例子2,多個Vue實例引用同一個data時,通知誰?是否是應該倆都通知?code
let commonData = { text: 'hello world' }; const vm1 = new Vue({ data() { return commonData; } }) const vm2 = new Vue({ data() { return commonData; } }) vm1.mount(); // in get vm2.mount(); // in get commonData.text = 'hey' // 輸出了兩次 in watcher update /n in get
老規矩,本身代入進去試試。server
但願經過這兩個例子,你已經大概清楚了Dep
的做用,有沒有原來就那麼回事的感受?有就對了。總結一下吧(如下依賴收集器實爲Dep
):
vue
將data
初始化爲一個Observer
並對對象中的每一個值,重寫了其中的get
、set
,data
中的每一個key
,都有一個獨立的依賴收集器。get
中,向依賴收集器添加了監聽Wathcer
,將收集器的目標指向了當前Watcher
data
值發生變動時,觸發set
,觸發了依賴收集器中的全部監聽的更新,來觸發Watcher.update