目前vue3.0打的很火熱搁进,都已經出了很多vue3.0源碼解析系列的博客灼舍, 但是vue2.0的源碼我覺得還是有必要細品一下萧福, 掌握了原有通用的源碼原理崇摄,才能知道新版本的vue3.0到底做了哪些更改擎值。如果已經很熟悉了,可跳過~
首先整體看一下整個頁面渲染的流程圖逐抑, 順著這張圖我們再帶著問題深入研究幅恋, 相信很快就能攻克閱讀Vue源碼的困難。
初始化及掛載:new Vue() -> $mount
從文件夾core/index.js入口泵肄,看到 import Vue from './instance/index'
這句話捆交;
接著我們定位到instance/index.js文件,看到
import { initMixin } from './init'
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
initMixin(Vue); // 這個Vue參數在當前文件定義了
可以看到: 上面Vue構造函數中腐巢,執(zhí)行了this._init(options)
this是指當前的Vue實例品追,是從initMixin()
函數中定義的。
我們定位到:instance/init.js可以看到:
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
... // 省略中間的處理
vm._self = vm
initLifecycle(vm) // 初始化生命周期
initEvents(vm) // 初始化事件
initRender(vm) // 初始化render
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm) // 初始化props冯丙、 methods肉瓦、 data、 computed 與 watch 等
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
... // 省略一部分處理
if (vm.$options.el) {
vm.$mount(vm.$options.el) // 初始化之后調用 $mount 掛載組件
}
}
}
- 在 new Vue() 之后胃惜。 Vue 會調用 _init 函數進行初始化泞莉,也就是這里的 init 過程,它會:
- 初始化生命周期:
initLifecycle(vm)
- 事件:
initEvents(vm)
- props船殉、 methods鲫趁、 data、 computed 與 watch 等選項:
initState(vm)
- 初始化生命周期:
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props) // props
if (opts.methods) initMethods(vm, opts.methods) // methods
if (opts.data) {
initData(vm) // data
} else {
observe(vm._data = {}, true)
}
if (opts.computed) initComputed(vm, opts.computed) // computed
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch) // watch
}
}
- 其中最重要的是通過 Object.defineProperty 設置 setter 與 getter 函數利虫,用來實現「響應式」以及「依賴收集」挨厚,后面會詳細講到,這里只要有一個印象即可糠惫;
(Q1: Vue的響應式以及依賴收集是如何實現的疫剃?)
初始化之后調用 $mount 會掛載組件。
如果是運行時編譯硼讽,即不存在 render function 但是采用 template進行渲染 的情況巢价,需要進行「編譯」步驟。
(Q2: Vue的模板編譯過程固阁?)
編譯
compile編譯可以分成 parse壤躲、optimize 與 generate 三個階段,最終需要得到 render function您炉。
查看compiler/index.js文件:
import { parse } from './parser/index'
import { optimize } from './optimizer'
import { generate } from './codegen/index'
import { createCompilerCreator } from './create-compiler'
// `createCompilerCreator` allows creating compilers that use alternative
// parser/optimizer/codegen, e.g the SSR optimizing compiler.
// Here we just export a default compiler using the default parts.
export const createCompiler = createCompilerCreator(function baseCompile (
template: string,
options: CompilerOptions
): CompiledResult {
const ast = parse(template.trim(), options)
if (options.optimize !== false) {
optimize(ast, options)
}
const code = generate(ast, options)
return {
ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
})
可以看到柒爵,先創(chuàng)建了一個編譯器, 創(chuàng)建成功后:
- 調用
parse
函數生成AST抽象語法樹 - 如果選項中的optimize為true赚爵, 則需要進行優(yōu)化棉胀, 調用
optimize
函數 - 接著, 根據生成的AST,通過調用
generate
函數生成代碼段對象 - 最后將
AST
冀膝、code對象中的render字符串
(VNode渲染所需要的)
唁奢、code中的staticRenderFns字符串
包裹成一個對象返回。
parse
parse 會用正則等方式解析 template 模板中的指令窝剖、class麻掸、style等數據,形成AST赐纱。(如何解析脊奋?
)
optimize
optimize 的主要作用是標記 static 靜態(tài)節(jié)點熬北,這是 Vue 在編譯過程中的一處優(yōu)化,后面當 update 更新界面時诚隙,會有一個 patch
的過程讶隐,diff 算法
會直接跳過靜態(tài)節(jié)點,從而減少了比較的過程久又,優(yōu)化了 patch 的性能巫延。
(Q3: Vue是如何區(qū)分靜態(tài)節(jié)點的?Vue的patch過程地消?diff算法做了哪些事情炉峰?)
generate
generate 是將 AST 轉化成 render function 字符串的過程(如何轉換?
)脉执,得到結果是 render 的字符串以及 staticRenderFns 字符串疼阔。
在經歷過 parse、optimize 與 generate 這三個階段以后适瓦,組件中就會存在渲染 VNode 所需的 render function 了竿开。
(Q4: VNode是什么?)
響應式
當render function被渲染的時候玻熙,會讀取對象中的值否彩, 從而觸發(fā)getter函數進行依賴收集
。依賴收集的目的是將觀察者Watcher對象放到訂閱者Dep中的subs中嗦随。
當修改對象中的值時列荔,會觸發(fā)setter函數通知之前收集的Dep中的每一個Watcher重新渲染視圖
,Watcher收到通知后枚尼, 調用update函數來更新視圖贴浙。當然這中間還有一個 patch 的過程以及使用隊列來異步更新的策略,這個我們后面再講署恍。
(Q4: Vue2.0的響應式原理崎溃?)
Virtual DOM
虛擬DOM其實是render function執(zhí)行后的產物,是一棵以 JavaScript 對象( VNode 節(jié)點)作為基礎的樹盯质,用對象屬性來描述節(jié)點袁串,實際上它只是一層對真實 DOM 的抽象。最終可以通過一系列操作使這棵樹映射到真實環(huán)境上呼巷。由于 Virtual DOM 是以 JavaScript 對象為基礎而不依賴真實平臺環(huán)境囱修,所以使它具有了跨平臺的能力,比如說瀏覽器平臺王悍、Weex破镰、Node 等。
更新視圖
前面說到:在修改一個對象值的時候,會通過 setter -> Watcher -> update 的流程來修改對象對應的值鲜漩。
當數據變化后源譬,執(zhí)行 render function 就可以得到一個新的 VNode 節(jié)點,我們如果想要得到新的視圖孕似,最簡單粗暴的方法就是直接解析這個新的 VNode 節(jié)點瓶佳,然后用 innerHTML 直接全部渲染到真實 DOM 中。但是其實我們只對其中的一小塊內容進行了修改鳞青,這樣做似乎有些浪費。
因此我們可以只修改有修改的部分为朋,這個時候就會通過patch去比較了臂拓。將新的 VNode 與舊的 VNode 一起傳入 patch 進行比較,經過 diff 算法得出它們的「差異」习寸。最后我們只需要將這些「差異」的對應 DOM 進行修改即可胶惰。
總結
回過頭來,我們再來看第一張圖
對于vue整體上的執(zhí)行機制是否有了一些概念霞溪?
- 頁面渲染:
new Vue() -> init() -> $mount()
- 數據更新:
用戶操作導致數據需要更新孵滞,視圖需要更新 -> getter收集依賴 -> dep.depend() -> dep.subs.push(watcher) -> setter通知watcher更新視圖 -> dep.notify() -> dep.subs[i].update() -> render fucntion() -> VNode -> patch -> DOM
- 模板更新:
模板編譯:parse -> optimize -> generator
具體的有些機制細節(jié),本系列會一一更新鸯匹, 共同學習坊饶, 共同進步!
具體有些細節(jié)殴蓬, 可能筆者在理解上存在誤解匿级,如果有問題,歡迎在評論或者留言區(qū)進行反饋交流~
參考資料
- 染陌掘金小冊《剖析Vue.js內部運行機制》
- vue2.0源碼