原文鏈接我的blog,歡迎STAR。
首先安利一波福利,有沒有用vscode的小伙伴?推薦一個神奇的字體,自從用了這個字體,敲代碼效率簡直上天了。先上圖看看效果:
還有其他許多,就不一一列舉出來了。
有沒有看上了的?
沒有我等下再來問。
這次推薦的一篇文章來自這,閱讀文章有利于加深對Vue程序結(jié)構(gòu)的了解,雖然是 1.0版本,不過好在 2.0 版本保留了絕大部分 1.0 的API。
在這篇文章里我將是這幾個月來對 Vue 學(xué)習(xí)的一個小結(jié)。
思路
Vue 和其他的 MVVM 思路是類似的:
主要是為了實現(xiàn)三個過程:
Observer: 通過Observer對data進(jìn)行監(jiān)聽,并且提供訂閱某個數(shù)據(jù)項的變化的能力。利用
Object.defineProperty
, 將data里的每個屬性全部轉(zhuǎn)化為getter/setter,已遍攔截對象賦值與取值操作;Compiler: 將template 解析為 render()方法;
watcher: Compiler 的解析結(jié)果與 Observer 結(jié)合起來,在 Observer 監(jiān)聽到數(shù)據(jù)發(fā)生改變時,接受通知,進(jìn)而觸發(fā) re-render, 重新渲染DOM。
new Vue
我們從 new Vue
開始,
上圖即是官網(wǎng)給出的一張生命周期圖,主要是四個過程:
- create: 在 new Vue() 時會運(yùn)行,創(chuàng)建出 Vue 對象。
- mount: 會根據(jù) el, template, render 等,生成 Vnode, 完成 diff 算法后掛載到 DOM 上。
- updata: 當(dāng)數(shù)據(jù)發(fā)生改變時,會重新渲染 DOM。
- destory: Vue 銷毀時會運(yùn)行。
現(xiàn)在,我們進(jìn)入源碼,分析具體的實現(xiàn):
- Create: 首先運(yùn)行
new vue()
的時候,會進(jìn)入_init,
其中關(guān)鍵部分的代碼如下:
可以看出在 beforeCreate
與 created
只有initState
, initState
是用于實現(xiàn)data observer
和 event/watcher
。
- Mount: 在
_init
最后,會運(yùn)行vm.$mount
方法:
具體 vm.$mount
的分析,請看上篇。最后進(jìn)行了 render()
, 從而會有Vnode
, 經(jīng)過 DIFF 算法后會有真實 DOM ;
-
Update: DOM 之后,會進(jìn)行update方法:
vm._watcher = new Watcher(vm, () => { vm._update(vm._render(), hydrating) }, noop)
將以上用一張序列圖表示也就是:
深入響應(yīng)式原理 (Observer, watcher)
MVVM 框架有一個很重要的特征:就是當(dāng)數(shù)據(jù)放生變化后,會自動更新對應(yīng)的DOM節(jié)點(diǎn)。 Vue 是怎么實現(xiàn)的?
以上是來自官網(wǎng)的一張圖。
前面提到在 beforeCreate
與 created
兩個生命周期鉤子函數(shù)之間會運(yùn)行 initState()
方法。
initState() 源碼里:
在這個方法里,會對props, data, computed
等屬性利用 Object.defineProperty
將這些屬性全部轉(zhuǎn)化為 getter/setter
。
我們以 initData()
為例子進(jìn)行分析:
這里有一個值得注意的地方,
proxy(vm, "_data", keys[i])
, 設(shè)置vm._data
為代理,具體作用是實現(xiàn)vm.a === vm._data.a
。
在 initData()
最后 會進(jìn)行 observe(data, this)
。
在observe()里,既是轉(zhuǎn)化為 getter/setter
。
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
// 只有在有Dep.target時才說明是Vue內(nèi)部依賴收集過程觸發(fā)的getter
// 那么這個時候就需要執(zhí)行dep.depend(),將watcher(Dep.target的實際值)添加到dep的subs數(shù)組中
// 對于其他時候,比如dom事件回調(diào)函數(shù)中訪問這個變量導(dǎo)致觸發(fā)的getter并不需要執(zhí)行依賴收集,直接返回value即可
if (Dep.target) {
dep.depend()
if (childOb) {
// 如果value在observe過程中生成了ob實例,那么就讓ob的dep也收集依賴
childOb.dep.depend()
}
if (isArray(value)) {
//如果數(shù)組元素也是對象,那么他們observe過程也生成了ob實例
dependArray(value)
}
}
return value
},
set: function reactiveSetter (newVal) {
var value = getter ? getter.call(obj) : val
if (newVal === value) {
return
}
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
// observe 這個新數(shù)據(jù)
childOb = observe(newVal)
// 通知到dep 進(jìn)行數(shù)據(jù)更新。
dep.notify()
}
})
到這個時候,Observer 監(jiān)聽已經(jīng)完成,如果數(shù)據(jù)更新,我們會進(jìn)行 dep.notify()
方法:
dep.notify() , 方法里會執(zhí)行update()
:
update() , 中會進(jìn)行
queueWatcher()
方法:
queueWatcher()
, 目的是通過 nextTicker
來執(zhí)行 run()
:
在 run()
里,其實就是執(zhí)行 this.get()
方法:
在 get()
方法里,會運(yùn)行 this.getter()
, 方法來更新 DOM 。
this.getter()
方法是啥?
再就是上文中所提到的new Watcher(), 的第二個參數(shù)。
vm._watcher = new Watcher(vm, () => {
vm._update(vm._render(), hydrating)
}, noop)
而在 new Watcher
構(gòu)造完成后,會調(diào)用 this.get()
,觸發(fā) this.getter()
,方法觸發(fā) DOM 更新。
具體可以看watcher.js:
截一個關(guān)鍵代碼部分的圖:
以上,用一張序列圖來表示,既就是:
對以上總結(jié):
- 首先
_init
,對屬性利用Object.defineProperty
,將屬性轉(zhuǎn)為getter/setter
,在setter
方法里,會調(diào)用dep.notify()
。 - 對
vm
設(shè)置new Watcher
。 -
data
變化時,進(jìn)行數(shù)據(jù)跟新。
完。