此文章主要目的是从一个最简单的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下