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.$mountconst 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 functionVue.prototype.__patch__ = inBrowser ? patch : noop// public mount methodVue.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 functionVue.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...