此文章主要目的是從一個最簡單的demo開始,從new Vue
開始,跟蹤Vue源碼中的代碼行進流程。
對主要的初始化流程有更清晰的理解。爲後續的深刻理解打好基礎,避免迷茫。html
能夠搭配這篇文章一塊兒食用 vuejs全局運行機制vue
<div id="app"> {{ message }} </div>
var app = new Vue({ el: '#app', data: { message: 'Hello Vue!' } })
src/core/index.js
這裏主要是導出真正的Vue函數,初始化全局APInode
import Vue from './instance/index' initGlobalAPI(Vue) export default Vue
src/core/instance/index.js
實例化的時候調用this._init方法
git
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) }
src/core/instance/init.js
這一塊主要是初始化一系列的屬性和方法。而後調用$mount
方法掛載el元素。github
Vue.prototype._init = function (options?: Object) { ... vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ) ... // 調用一系列初始化函數 initLifecycle(vm) initEvents(vm) initRender(vm) callHook(vm, 'beforeCreate') initInjections(vm) // resolve injections before data/props initState(vm) initProvide(vm) // resolve provide after data/props callHook(vm, 'created') ... if (vm.$options.el) { // 掛載el屬性 vm.$mount(vm.$options.el) } }
src/platforms/web/entry-runtime-with-compiler.js
兩個點,調用compileToFunctions
函數生成render函數備用, 調用mount
函數開始執行真正的掛載流程。web
// 這個mount指向下面這個文件的Vue.prototype.$mount const mount = Vue.prototype.$mount // 在這裏主要是使用compileToFunctions來編譯模板,生成render函數,以供後用。 // 主體的mount函數仍是使用原來的$mount方法 Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && query(el) ... if (!options.render) { ... template = getOuterHTML(el) // 調用compileToFunctions函數,獲得render, staticRenderFns const { render, staticRenderFns } = compileToFunctions(template, { outputSourceRange: process.env.NODE_ENV !== 'production', shouldDecodeNewlines, shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments }, this) // 更新options屬性 options.render = render options.staticRenderFns = staticRenderFns } // 調用本來的mount方法 return mount.call(this, el, hydrating) } // 對外導出的 Vue(這個vue指向下面這個文件) export default Vue
src/platforms/web/runtime/index.js
一個點,執行mountComponent
開始掛載組件segmentfault
import { mountComponent } from 'core/instance/lifecycle' // install platform patch function Vue.prototype.__patch__ = inBrowser ? patch : noop // public mount method Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && inBrowser ? query(el) : undefined return mountComponent(this, el, hydrating) } export default Vue
src/core/instance/lifecycle.js
而後會執行到mountComponent函數。在實例化Watcher時,會執行vm._render(), vm._update()
方法,來看這兩個重要方法app
export function mountComponent ( vm: Component, el: ?Element, hydrating?: boolean ): Component { ... let updateComponent ... updateComponent = () => { vm._update(vm._render(), hydrating) } new Watcher(vm, updateComponent, noop, { before () { if (vm._isMounted && !vm._isDestroyed) { callHook(vm, 'beforeUpdate') } } }, true /* isRenderWatcher */) }
src/core/instance/render.js
這裏的$options.render就是第一步調用compileToFunctions的模板編譯後的render函數。 因此這一步理解爲返回模板對應的vnodedom
Vue.prototype._render = function (): VNode { ... const { render, _parentVnode } = vm.$options ... vnode = render.call(vm._renderProxy, vm.$createElement) ... return vnode }
src/core/instance/lifecycle.js
_update
方法(這一步的做用是把VNode渲染成真實的DOM)。這一步主要調用 vm.__patch__
方法ide
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) { ... if (!prevVnode) { // initial render vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */) } else { // updates vm.$el = vm.__patch__(prevVnode, vnode) } ... }
src/platforms/web/runtime/index.js
// install platform patch function Vue.prototype.__patch__ = inBrowser ? patch : noop
src/platforms/web/runtime/patch.js
export const patch: Function = createPatchFunction({ nodeOps, modules })
src/core/vdom/patch.js
這個函數超級複雜。做用是依賴vnode遞歸建立了一個完整的DOM樹並插到Body上。
export function createPatchFunction (backend) { ... return function patch (oldVnode, vnode, hydrating, removeOnly) } }
template => vnode tree => DOM tree => 將DOM tree插到body下
_render負責將template轉爲vnode tree, _update負責將vnode tree轉爲DOM tree, 而且插到body下