每一次實例化一個組件,都會調用 initData 而後調用 observe 方法,observe 方法調用了 new Observer(value), 而且返回 __ob__
。javascript
在 new Observer 中作了兩件事:vue
__ob__
屬性上,這個實例在後面有用處。若是是對象:java
橫向遍歷對象屬性,調用 defineReactive;react
遞歸調用 observe 方法, 當屬性值不是數組或者對象中止遞歸es6
下面對 defineReactive 方法作了詳細的註釋:vue-router
export function defineReactive( obj: Object, key: string, val: any, customSetter ? : ? Function, shallow ? : boolean ) { const dep = new Dep(); // 閉包建立依賴對象; 每一個對象的屬性都有本身的dep // 下面是針對已經經過Object.defineProperty 或者Object.seal Object.freeze 處理過的數據 const property = Object.getOwnPropertyDescriptor(obj, key); // 若是configurable爲false ,再次Object.defineProperty(obj, key)會報錯,而且不會成功;因此直接返回 // 因此能夠針對性的使用Object.freeze/seal優化性能。 if (property && property.configurable === false) { return; } const getter = property && property.get; const setter = property && property.set; // 正常狀況下 咱們使用的數據getter、setter都是不存在的,而且在new Observer()中調用defineReactive的參數只有兩個 if ((!getter || setter) && arguments.length === 2) { val = obj[key]; // 也就是說 這行代碼通常狀況下會執行 } // 通常狀況下 shallow是false ;childOb就是返回的Observer實例,這個實例是存儲在數據的__ob__屬性上的 // let childOb = !shallow && observe(val); Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter() { const value = getter ? getter.call(obj) : val; // getter 不存在 ,直接用val if (Dep.target) { // Dep.target是一個全局數據,保存的是watcher棧(targetStack)棧頂的watcher, dep.depend(); // 閉包dep把當前watcher收集起來; 收集依賴真正發生在render方法執行的時候(也就是虛擬dom生成的時候) if (childOb) { // val不是對象(非Array 或者object) observe方法纔會返回一個Observer實例,不然返回undefined // 此處爲何要執行childOb.dep.depend()呢? // 這麼作的效果是:在對象上掛載的__ob__的dep對象把點前watcher添加到了依賴裏,這個dep和閉包dep不是一個。 // 目的在於: // 1.針對對象:要想this.$set/$del時候可以觸發組件從新渲染,須要把渲染watcher保存下來,而後在$set中調用 ob.dep.notify();這裏就用到了__ob__屬性 // 2.針對數組:數組的攔截中(調用splice push 等法法)要想觸發從新渲染,調用 ob.dep.notify() 這裏就用到了__ob__屬性 childOb.dep.depend(); if (Array.isArray(value)) { // 若是value是一個數組,在observe方法中走的是數組那套程序,這些元素沒有被Object.defineProperty這一系列的處理(元素當作val處理),即使元素是object/array ,沒有childOb.dep.depend()這樣的一個過程,致使上面this.$set/$del、數組沒法觸發從新渲染; // 因此調用dependArray 針對數組作處理 這裏就用到了__ob__屬性 dependArray(value); } } } return value; }, set: function reactiveSetter(newVal) { // 通常沒有getter const value = getter ? getter.call(obj) : val; // 值未變化, newVal !== newVal && value !== value 應該針對的是NaN if (newVal === value || (newVal !== newVal && value !== value)) { return; } if (process.env.NODE_ENV !== "production" && customSetter) { customSetter(); } // getter 和setter 要成對才行 if (getter && !setter) return; if (setter) { setter.call(obj, newVal); } else { val = newVal; } // 從新設置值以後,須要從新observe ,而且更新閉包變量 childOB childOb = !shallow && observe(newVal); // 更新 dep.notify(); }, }); }
若是是數組:vuex
修改數組的
__proto__
屬性值,指向一個新的對象;express
function protoAugment (target, src: Object) { target.__proto__ = src }
這個新對象中從新定義以下方法:數組
'push','pop','shift','unshift','splice','sort','reverse'
同時這個對象的 __proto__
指向 Array.prototype。閉包
const arrayProto = Array.prototype; export const arrayMethods = Object.create(arrayProto);
最後項目的代碼在控制檯打印出下面的截圖
data() { return { data1: [{ name: 1 }] } },
同時對數組中的每一個元素作 observe 遞歸處理。
watch 的使用方法通常以下:
watch: { a: function(newVal, oldVal) { console.log(newVal, oldVal); }, b: 'someMethod', c: { handler: function(val, oldVal) { /* ... */ }, deep: true }, d: { handler: 'someMethod', immediate: true }, e: [ 'handle1', function handle2(val, oldVal) {}, { handler: function handle3(val, oldVal) {}, } ], 'e.f': function(val, oldVal) { /* ... */ } }
watch 的處理按照以下流程, 把其中的關鍵代碼羅列出來了:
-- > initData() // 初始化組件的時候調用 若是組件中有watch選項,調用initWatch -- > initWatch() if (Array.isArray(handler)) { // 這裏處理數組的狀況,也就是上面e的狀況 for (let i = 0; i < handler.length; i++) { createWatcher(vm, key, handler[i]) } } else { createWatcher(vm, key, handler) } -- > createWatcher() if (isPlainObject(handler)) { // 兼容c(對象) options = handler handler = handler.handler } // 若是是b 字符串的狀況,須要在vm上有對應的數據 if (typeof handler === 'string') { handler = vm[handler] } // 默認是 a(函數) vm.$watch(expOrFn, handler, options) -- > vm.$watch() options.user = true // 添加參數 options.user = true ,處理immediate:true的狀況 const watcher = new Watcher(vm, expOrFn, cb, options) -- > new Watcher() // 建立watcher this.getter = parsePath(expOrFn) // 這個getter方法主要是get一下watch的變量,在get的過程當中觸發依賴收集,把當前watcher添加到依賴 this.value = this.lazy // 選項lazy是false ? undefined : this.get() // 在constructor中直接調用get方法 -- > watcher.get() pushTarget(this) // 把當前watcher推入棧頂 value = this.getter.call(vm, vm) // 這時候這個watch的變量的依賴裏就有了當前watcher -- > watcher.getter() // 依賴收集的地方
當 watch 的變量變化的時候,會執行 watcher 的 run 方法:
run() { if (this.active) { const value = this.get() // 渲染watcher狀況下 value是undefined // 在自定義watcher的狀況下 value就是監聽的值 if ( value !== this.value || // 當watch的值有變化的時候 isObject(value) || this.deep ) { // set new value const oldValue = this.value this.value = value // 自定義watcher的user是true ,cb就是那個handler if (this.user) { try { this.cb.call(this.vm, value, oldValue) } catch (e) { handleError(e, this.vm, `callback for watcher "${this.expression}"`) } } else { this.cb.call(this.vm, value, oldValue) } } } }
上面的的代碼中 value !== this.value 和 deep 比較好理解,數值變化觸發 handler;
可是 isObject(value)對應的什麼狀況呢?看一下下面的例子就知道了:
data() { return { data1: [{ name: 1 }], } }, computed: { data2() { let value = this.data1[0].name // return this.data1 // 返回的是一個數組,因此data2一致是不變的 } }, watch: { data2: function() { // 雖然data2的值一直是data1,沒有變化;可是由於data2知足isObject,因此仍然能觸發handler // 由此能夠想到,能夠在computed中主動去獲取某個數據屬性來觸發watch,而且避免在watch中使用deep // 可是這樣也不太合適,由於能夠直接使用'e.f'這種例子來代替; // 因此根據要實際狀況肯定 console.log('data2'); } } created() { setInterval(() => { this.data1[0].name++ }, 2000) }
computed 首先是建立 watcher,與渲染 watcher、自定義 watcher 不一樣之處:初始化的時候不會執行 get 方法,也就是不會作依賴收集。
另外使用 Object.defineProperty 定義 get 方法:
function createComputedGetter(key) { return function computedGetter() { const watcher = this._computedWatchers && this._computedWatchers[key]; if (watcher) { // lazy=true 而後 dirty 也是true if (watcher.dirty) { watcher.evaluate(); // 把computed watcher添加到涉及到的全部的變量的依賴中; } if (Dep.target) { watcher.depend(); // 主動調用depend方法;假如這個computed是用在頁面渲染上,就會把渲染watcher添加到變量的依賴中 } return watcher.value; } }; }
當 computed 數據在初次渲染中:
-- > render // 渲染 -- > computedGetter // computed Object.defineProperty 定義get方法: -- > watcher.evaluate() // 計算獲得watcher.value -- > watcher.get() -- > pushTarget(this) // 把當前computed watcher 推入watcher棧頂 -- > watcher.getter() // getter方法就是組件中computed定義的方法,執行的時候會作依賴收集 -- > dep.depend() // 把當前computed watcher加入變量的依賴中 -- > popTarget() // 把當前 computed watcher 移除棧,通常來講渲染watcher會被推出到棧頂 -- > cleanupDeps() // 清除多餘的watcher 和 dep -- > watcher.depend() // 這是computed比較特殊的地方。假如computed中依賴變量data中的數據,這個步驟把當前watcher添加到變量的依賴中;爲何要這麼作呢?我的猜想意圖是computed的目的是作一個處理數據的橋樑,真正的響應式仍是須要落實到data中的數據。
當 computed 中的依賴數據變化的時候會走以下流程:
-- > watcher.update() // 這是個 computed watcher,其中lazy爲true,因此不會往下走 if (this.lazy) { this.dirty = true } -- > watcher.update() // 渲染watcher render 以後的過程就如同初次渲染同樣
渲染 watcher 相對好理解一些
new Watcher(渲染 watcher) ->watcher.get-> pushTarget(this) ->watcher.getter()-> render -> Object.defineProperty(get) -dep.depend()-> popTarget()->watcher.cleanupDeps()
watcher.getter 是下面方法:
updateComponent = () => { vm._update(vm._render(), hydrating) }
源碼在 vue/src/core/observer/watcher.js 中;
須要注意到 vue 中有一套清除 watcher 和 dep 的方案;vue 中的依賴收集並非一次性的,從新 render 會觸發新一次的依賴收集,這時候會把無效的 watcher 和 dep 去除掉,這樣可以避免無效的更新。
以下 computed ,只要有一次 temp<=0.5
, 改變 b 都再也不會在打印 temp
;緣由在於當 temp<0.5
以後, this.b
不會把當前 a
放進本身的 dep 中,也就不會再觸發這個 computed watcher 了
data() { return { b: 1 } }, computed: { a() { var temp = Math.random() console.log(temp); // 只要有一次a<=0.5 接下來就不會打印temp了 if (temp > 0.5) { return this.b } else { return 1 } } }, created() { setTimeout(() => { this.b++ }, 5000) },
這裏面主要是 watcher.js 中的 cleanupDeps 方法在處理;
cleanupDeps() { let i = this.deps.length // 遍歷上次保存的deps while (i--) { // i-- const dep = this.deps[i] // newDepIds 是在本次依賴收集中加入的新depId集合 // 把不在newDepIds中的dep清除 if (!this.newDepIds.has(dep.id)) { dep.removeSub(this) } } // depIds是一個es6 set集合 ,是引用類數據 // newDepIds相似於一個臨時保存的地方,最終須要把數據保存到depIds。左手到右手的把戲 // newDeps 和 newDepIds 是同樣的 let tmp = this.depIds this.depIds = this.newDepIds this.newDepIds = tmp this.newDepIds.clear() tmp = this.deps this.deps = this.newDeps this.newDeps = tmp this.newDeps.length = 0 }
依賴於 vue 自身的響應式原理,經過構建一個 Vue 實例,在 render 過程當中完成依賴的收集。
store._vm = new Vue({ data: { $$state: state, // 自定義的state數據 }, computed, });
Vue.mixin({ beforeCreate() { if (isDef(this.$options.router)) { this._routerRoot = this; this._router = this.$options.router; this._router.init(this); // 關鍵是這行代碼,把_route屬性進行響應式處理 Vue.util.defineReactive( this, "_route", this._router.history.current ); } else { this._routerRoot = (this.$parent && this.$parent._routerRoot) || this; } registerInstance(this, this); }, destroyed() { registerInstance(this); }, }); Object.defineProperty(Vue.prototype, "$route", { get() { return this._routerRoot._route; }, });
渲染 router-view 的時候會觸發上面的
// 該組件渲染的時候render方法 render() { ... // 當調用$route的時候會觸發依賴收集 var route = parent.$route; }