demo
<div id="app">
{{message}}
</div>
var app = new Vue({
el: '#app',
data: {message: 'Hello Vue!'}
})
简要流程图
源码详细流程
src/core/index.js
这里主要是导出真正的 Vue 函数,初始化全局 API
import Vue from './instance/index'
initGlobalAPI(Vue)
export default Vue
src/core/instance/index.js
实例化的时候调用this._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)
}
src/core/instance/init.js
这一块主要是初始化一系列的属性和方法。然后调用 $mount
方法挂载 el 元素。
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
函数开始执行真正的挂载流程。
// 这个 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
开始挂载组件
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()
方法,来看这两个重要方法
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 函数。所以这一步理解为返回模板对应的 vnode
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__
方法
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 下
参考链接
https://ustbhuangyi.github.io…