成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Vue2 源碼分析

alin / 1687人閱讀

摘要:應用啟動一般是通過,所以,先從該構(gòu)造函數(shù)著手。構(gòu)造函數(shù)文件該文件只是構(gòu)造函數(shù),原型對象的聲明分散在當前目錄的多個文件中構(gòu)造函數(shù)接收參數(shù),然后調(diào)用。

源碼版本:v2.1.10

分析目標

通過閱讀源碼,對 Vue2 的基礎(chǔ)運行機制有所了解,主要是:

Vue2 中數(shù)據(jù)綁定的實現(xiàn)方式

Vue2 中對 Virtual DOM 機制的使用方式

源碼初見

項目構(gòu)建配置文件為 build/config.js,定位 vue.js 對應的入口文件為 src/entries/web-runtime-with-compiler.js,基于 rollup 進行模塊打包。

代碼中使用 flow 進行接口類型標記和檢查,在打包過程中移除這些標記。為了閱讀代碼方便,在 VS Code 中安裝了插件 Flow Language Support,然后關(guān)閉工作區(qū) JS 代碼檢查,這樣界面就清爽很多了。

Vue 應用啟動一般是通過 new Vue({...}),所以,先從該構(gòu)造函數(shù)著手。

注:本文只關(guān)注 Vue 在瀏覽器端的應用,不涉及服務(wù)器端代碼。

Vue 構(gòu)造函數(shù)

文件:src/core/instance/index.js

該文件只是構(gòu)造函數(shù),Vue 原型對象的聲明分散在當前目錄的多個文件中:

init.js:._init()

state.js:.$data .$set() .$delete() .$watch()

render.js:._render() ...

events.js:.$on() .$once() .$off() .$emit()

lifecycle.js:._mount() ._update() .$forceUpdate() .$destroy()

構(gòu)造函數(shù)接收參數(shù) options ,然后調(diào)用 this._init(options)。

._init() 中進行初始化,其中會依次調(diào)用 lifecycle、events、render、state 模塊中的初始化函數(shù)。

Vue2 中應該是為了代碼更易管理,Vue 類的定義分散到了上面的多個文件中。

其中,對于 Vue.prototype 對象的定義,通過 mixin 的方式在入口文件 core/index.js 中依次調(diào)用。對于實例對象(代碼中通常稱為 vm)則通過 init 函數(shù)在 vm._init() 中依次調(diào)用。

Vue 公共接口

文件:src/core/index.js

這里調(diào)用了 initGlobalAPI() 來初始化 Vue 的公共接口,包括:

Vue.util

Vue.set

Vue.delete

Vue.nextTick

Vue.options

Vue.use

Vue.mixin

Vue.extend

asset相關(guān)接口:配置在 src/core/config.js

Vue 啟動過程

調(diào)用 new Vue({...}) 后,在內(nèi)部的 ._init() 的最后,是調(diào)用 .$mount() 方法來“啟動”。

web-runtime-with-compiler.jsweb-runtime.js 中,定義了 Vue.prototype.$mount()。不過兩個文件中的 $mount() 最終調(diào)用的是 ._mount() 內(nèi)部方法,定義在文件 src/core/instance/lifecycle.js 中。

Vue.prototype._mount(el, hydrating)

簡化邏輯后的偽代碼:

vm = this
vm._watcher = new Watcher(vm, updateComponent)

接下來看 Watcher。

Watcher

文件:src/core/observer/watcher.js

先看構(gòu)造函數(shù)的簡化邏輯:

// 參數(shù):vm, expOrFn, cb, options
this.vm = vm
vm._watchers.push(this)
// 解析 options,略....
// 屬性初始化,略....
this.getter = expOrFn // if `function`
this.value = this.lazy ? undefined : this.get()

由于缺省的 lazy 屬性值為 false,接著看 .get() 的邏輯:

pushTarget(this) // !
value = this.getter.call(this.vm, this.vm)
popTarget()
this.cleanupDeps()
return value

先看這里對 getter 的調(diào)用,返回到 ._mount() 中,可以看到,是調(diào)用了 vm._update(vm._render(), hydrating),涉及兩個方法:

vm._render():返回虛擬節(jié)點(VNode)

vm._update()

來看 _update() 的邏輯,這里應該是進行 Virtual DOM 的更新:

// 參數(shù):vnode, hydrating
vm = this
prevEl = vm.$el
prevVnode = vm._vnode
prevActiveInstance = activeInstance
activeInstance = vm
vm._vnode = vnode
if (!prevVnode) {
  // 初次加載
  vm.$el = vm.__patch__(vm.$el, vnode, ...)
} else {
  // 更新
  vm.$el = vm.__patch__(prevVnode, vnode)
}
activeInstance = prevActiveInstance
// 后續(xù)屬性配置,略....

參考 Virtual DOM 的一般邏輯,這里是差不多的處理過程,不再贅述。

綜上,這里的 watcher 主要作用應該是在數(shù)據(jù)發(fā)生變更時,觸發(fā)重新渲染和更新視圖的處理:vm._update(vm._render())

接下來,我們看下 watcher 是如何發(fā)揮作用的,參考 Vue 1.0 的經(jīng)驗,下面應該是關(guān)于依賴收集、數(shù)據(jù)綁定方面的細節(jié)了,而這一部分,和 Vue 1.0 差別不大。

數(shù)據(jù)綁定

watcher.get() 中調(diào)用的 pushTarget()popTarget() 來自文件:src/core/observer/dep.js

pushTarget()popTarget() 兩個方法,用于處理 Dep.target,顯然 Dep.targetwather.getter 的調(diào)用過程中會用到,調(diào)用時會涉及到依賴收集,從而建立起數(shù)據(jù)綁定的關(guān)系。

Dep 類的 .dep() 方法中用到了 Dep.target,調(diào)用方式為:

Dep.target.addDep(this)

可以想見,在使用數(shù)據(jù)進行渲染的過程中,會對數(shù)據(jù)屬性進行“讀”操作,從而觸發(fā) dep.depend(),進而收集到這個依賴關(guān)系。下面來找一下這樣的調(diào)用的位置。

state.js 中找到一處,makeComputedGetter() 函數(shù)中通過 watcher.depend() 間接調(diào)用了 dep.depend()。不過 computedGetter 應該不是最主要的地方,根據(jù) Vue 1.0 的經(jīng)驗,還是要找對數(shù)據(jù)進行“數(shù)據(jù)劫持”的地方,應該是defineReactive()。

defineReactive() 定義在文件 src/core/observer/index.js。

// 參數(shù):obj, key, val, customSetter?
dep = new Dep()
childOb = observe(val)
Object.defineProperty(obj, key, {
  enumerable: true,
  configurable: true,
  get: function () {
    // 略,調(diào)用了 dep.depend()
  },
  set: function () {
    // 略,調(diào)用 dep.notify()
  }
})

結(jié)合 Vue 1.0 經(jīng)驗,這里應該就是數(shù)據(jù)劫持的關(guān)鍵了。數(shù)據(jù)原有的屬性被重新定義,屬性的 get() 被調(diào)用時,會通過 dep.depend() 收集依賴關(guān)系,記錄到 vm 中;而在 set() 被調(diào)用時,則會判斷屬性值是否發(fā)生變更,如果發(fā)生變更,則通過 dep.notify() 來通知 vm,從而觸發(fā) vm 的更新操作,實現(xiàn) UI 與數(shù)據(jù)的同步,這也就是數(shù)據(jù)綁定后的效果了。

回過頭來看 state.js,是在 initProps() 中調(diào)用了 defineReactive()。而 initProps()initState() 中調(diào)用,后者則是在 Vue.prototype._init() 中被調(diào)用。

不過最常用的其實是在 initData() 中,對初始傳入的 data 進行劫持,不過里面的過程稍微繞一些,是將這里的 data 賦值到 vm._data 并且代理到了 vm 上,進一步的處理還涉及 observe()Observer 類。這里不展開了。

綜上,數(shù)據(jù)綁定的實現(xiàn)過程為:

初始化:new Vue() -> vm._init()

數(shù)據(jù)劫持:initState(vm) -> initProps(), initData() -> dep.depend()

依賴收集:vm.$mount() -> vm._mount() -> new Watcher() -> vm._render()

渲染

首先來看 initRender(),這里在 vm 上初始化了兩個與創(chuàng)建虛擬元素相關(guān)的方法:

vm._c()

vm.$createElement()

其內(nèi)部實現(xiàn)都是調(diào)用 createElement(),來自文件:src/core/vdom/create-element.js。

而在 renderMixin() 中初始化了 Vue.prototype._render() 方法,其中創(chuàng)建 vnode 的邏輯為:

render = vm.$options.render
try {
  vnode = render.call(vm._renderProxy, vm.$createElement)
} catch (e) {
  // ...
}

這里傳入 render() 是一個會返回 vnode 的函數(shù)。

接下來看 vm._update() 的邏輯,這部分在前面有介紹,初次渲染時是通過調(diào)用 vm.__patch__() 來實現(xiàn)。那么 vm.__patch__() 是在哪里實現(xiàn)的呢?在 _update() 代碼中有句注釋,提到:

    // Vue.prototype.__patch__ is injected in entry points
    // based on the rendering backend used.

在文件 web-runtime.js 中,找到了:

Vue.prototype.__patch__ = inBrowser ? patch : noop

顯然示在瀏覽器環(huán)境下使用 patch(),來自:src/platforms/web/runtime/patch.js,其實現(xiàn)是通過 createPatchFunction(),來自文件 src/core/vdom/patch。

OK,以上線索都指向了 vdom 相關(guān)的模塊,也就是說,顯然是 vdom 也就是 Virtual DOM 參與了渲染和更新。

不過還有個問題沒有解決,那就是原始的字符串模塊,是如何轉(zhuǎn)成用于 Virtual DOM 創(chuàng)建的函數(shù)調(diào)用的呢?這里會有一個解析的過程。

回到入口文件 web-runtime-with-compiler.js,在 Vue.prototype.$mount() 中,有一個關(guān)鍵的調(diào)用:compileToFunctions(template, ...),template 變量值為傳入的參數(shù)解析得到的模板內(nèi)容。

模板解析

文件:src/platforms/web/compiler/index.js

函數(shù) compileToFunctions() 的基本邏輯:

// 參數(shù):template, options?, vm?
res = {}
compiled = compile(template, options)
res.render = makeFunction(compiled.render)
// 拷貝數(shù)組元素:
// res.staticRenderFns <= compiled.staticRenderFns
return res

這里對模板進行了編譯(compile()),最終返回了根據(jù)編譯結(jié)果得到的 render()、staticRenderFns。再看 web-runtime-with-compiler.jsVue.prototype.$mount() 的邏輯,則是將這里得到的結(jié)果寫入了 vm.$options 中,也就是說,后面 vm._render() 中會使用這里的 render()。

再來看 compile() 函數(shù),這里是實現(xiàn)模板解析的核心,來做文件 src/compiler/index.js,基本邏輯為:

// 參數(shù):template, options
ast = parse(template.trim(), options)
optimize(ast, options)
code = generate(ast, options)
return {
  ast,
  render: code.render,
  staticRenderFns: code.staticRenderFns
}

邏輯很清晰,首先從模板進行解析得到抽象語法樹(ast),進行優(yōu)化,最后生成結(jié)果代碼。整個過程中肯定會涉及到 Vue 的語法,包括指令、組件嵌套等等,不僅僅是得到構(gòu)建 Virtual DOM 的代碼。

需要注意的是,編譯得到 render 其實是代碼文本,通過 new Function(code) 的方式轉(zhuǎn)為函數(shù)。

總結(jié)

Vue2 相比 Vue1 一個主要的區(qū)別在于引入了 Virtual DOM,但其 MVVM 的特性還在,也就是說仍有一套數(shù)據(jù)綁定的機制。

此外,Virtual DOM 的存在,使得原有的視圖模板需要轉(zhuǎn)變?yōu)楹瘮?shù)調(diào)用的模式,從而在每次有更新時可以重新調(diào)用得到新的 vnode,從而應用 Virtual DOM 的更新機制。為此,Vue2 實現(xiàn)了編譯器(compiler),這也意味著 Vue2 的模板可以是純文本,而不必是 DOM 元素。

Vue2 基本運行機制總結(jié)為:

文本模板,編譯得到生成 vnode 的函數(shù)(render),該過程中會識別并記錄 Vue 的指令和其他語法

new Vue() 得到 vm 對象,其中傳入的數(shù)據(jù)會進行數(shù)據(jù)劫持處理,從而可以收集依賴,實現(xiàn)數(shù)據(jù)綁定

渲染過程是將所有數(shù)據(jù)交由渲染函數(shù)(render)進行調(diào)用得到 vnode,應該 Virtual DOM 的機制實現(xiàn)初始渲染和更新

寫在最后

對 Vue2 的源碼分析,是基于我之前對 Vue1 的分析和對 Virtual DOM 的了解,見【鏈接】中之前的文章。

水平有限,錯漏難免,歡迎指正。

感謝閱讀!

鏈接

Vue 雙向數(shù)據(jù)綁定原理分析 - luobotang

一起理解 Virtual DOM - luobotang

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/81768.html

相關(guān)文章

  • vue2.0源碼分析之理解響應式架構(gòu)

    摘要:分享前啰嗦我之前介紹過如何實現(xiàn)和。我們采用用最精簡的代碼,還原響應式架構(gòu)實現(xiàn)以前寫的那篇源碼分析之如何實現(xiàn)和可以作為本次分享的參考。到現(xiàn)在為止,我們再看那張圖是不是就清楚很多了總結(jié)我非常喜歡,以上代碼為了好展示,都采用最簡單的方式呈現(xiàn)。 分享前啰嗦 我之前介紹過vue1.0如何實現(xiàn)observer和watcher。本想繼續(xù)寫下去,可是vue2.0橫空出世..所以 直接看vue2.0吧...

    chenatu 評論0 收藏0
  • vue2源碼框架和流程分析

    摘要:流程圖盜用一下官網(wǎng)關(guān)于生命周期的圖,對照之前的內(nèi)容梳理一下對照上面的分析基本上可以找到各個鉤子函數(shù)的位置,下面那個銷毀的我就沒用做分析了。。。 vue整體框架和主要流程分析 之前對看過比較多關(guān)于vue源碼的文章,但是對于整體框架和流程還是有些模糊,最后用chrome debug對vue的源碼進行查看整理出這篇文章。。。。 本文對vue的整體框架和整體流程進行簡要的分析,不對某些具體的細...

    tain335 評論0 收藏0
  • Vue2 transition源碼分析

    摘要:至此算是找到了源碼位置。至此進入過渡的部分完畢。在動畫結(jié)束后,調(diào)用了由組件生命周期傳入的方法,把這個元素的副本移出了文檔流。這篇并沒有去分析相關(guān)的內(nèi)容,推薦一篇講非常不錯的文章,對構(gòu)造函數(shù)如何來的感興趣的同學可以看這里 Vue transition源碼分析 本來打算自己造一個transition的輪子,所以決定先看看源碼,理清思路。Vue的transition組件提供了一系列鉤子函數(shù),...

    Genng 評論0 收藏0

發(fā)表評論

0條評論

alin

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<