文章概述
此文章主要目的是从一个最简单的demo开始,从new Vue
开始,跟踪Vue源码中的代码行进流程。
对主要的初始化流程有更清晰的理解。为后续的深入理解打好基础,避免迷茫。
搭配
可以搭配这篇文章一起食用 vuejs全局运行机制
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下
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。