一、render
在上一节末尾我们提到update方法中第一个参数是vm._render。那么这个函数的定义实际上是在src\core\instance\render.js里。
我们先把render的代码放上来,然后进行拆解
export function renderMixin (Vue: Class<Component>) { //在最初initMixin里的rendermixin执行
// install runtime convenience helpers
installRenderHelpers(Vue.prototype)
Vue.prototype.$nextTick = function (fn: Function) { //挂载vue.$nextTick
return nextTick(fn, this)
}
Vue.prototype._render = function (): VNode { //初始化vm._render
const vm: Component = this
const { render, _parentVnode } = vm.$options //拿到render
if (_parentVnode) {
vm.$scopedSlots = normalizeScopedSlots(
_parentVnode.data.scopedSlots,
vm.$slots,
vm.$scopedSlots
)
}
// set parent vnode. this allows render functions to have access
// to the data on the placeholder node.
vm.$vnode = _parentVnode
// render self
let vnode
try {
// There's no need to maintain a stack because all render fns are called
// separately from one another. Nested component's render fns are called
// when parent component is patched.
currentRenderingInstance = vm
vnode = render.call(vm._renderProxy, vm.$createElement)
} catch (e) {
handleError(e, vm, `render`)
// return error render result,
// or previous vnode to prevent render error causing blank component
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production' && vm.$options.renderError) {
try {
vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)
} catch (e) {
handleError(e, vm, `renderError`)
vnode = vm._vnode
}
} else {
vnode = vm._vnode
}
} finally {
currentRenderingInstance = null
}
// if the returned array contains only a single node, allow it
if (Array.isArray(vnode) && vnode.length === 1) {
vnode = vnode[0]
}
// return empty vnode in case the render function errored out
if (!(vnode instanceof VNode)) {
if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
warn(
'Multiple root nodes returned from render function. Render function ' +
'should return a single root node.',
vm
)
}
vnode = createEmptyVNode()
}
// set parent
vnode.parent = _parentVnode
return vnode
}
}
首先在最初我们从vm.$options里拿到render函数,全局搜索可知最初该render函数的定义在之前src\core\instance\lifecycle.js的 mountComponent 里

export const createEmptyVNode = (text: string = '') => {
const node = new VNode()
node.text = text
node.isComment = true
return node
}
所以我们实际上知道如果用户不自己定义render函数,那么Vue会自动给你增加一个空的VNode作为render函数
vnode = render.call(vm._renderProxy, vm.$createElement)
同时我们注意到这行代码,实际上他就是调用了render的call方法把自己的上下文传进去,我们首先看一下第二个参数 vm.$createElement ,首先看看她的定义
// bind the createElement fn to this instance
// so that we get proper render context inside it.
// args order: tag, data, children, normalizationType, alwaysNormalize
// internal version is used by render functions compiled from templates
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
// normalization is always applied for the public version, used in
// user-written render functions.
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
后续我们继续查看createElement可以看到其实他就是定义一些tag,attrs还有有些children。我们在new Vue的时候其实可以利用这个函数自己写一个render,如下
new Vue({
el: '#app',
router,
render(createElement){
return createElement('div',{
attrs:{
id:'app'
}
},this.me1)
},
data(){
return{
me1:"hello!",
}
}
})
接着我们看到第一个参数 vm._renderProxy ,全局搜索可知当Vue在initMixin的时候实际上就赋值这个renderProxy了。
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
可以看到在生产环境下renderProxy就是vm,也就是this,在开发环境中执行了initProxy,继续看
initProxy = function initProxy (vm) {
if (hasProxy) {
// determine which proxy handler to use
const options = vm.$options
const handlers = options.render && options.render._withStripped
? getHandler
: hasHandler
vm._renderProxy = new Proxy(vm, handlers)
} else {
vm._renderProxy = vm
}
}
实际上做的就是一件事情,在开发环境中用ES6的Proxy去劫持vm并进行一些Vue预定义的报错。(handlers),举一个最简单的例子,当模板中使用了methods或者data中未定义的数据时的报错就是从这里定义的。
二、 createElement
这里我们重点分析一下这个 createElement ,上述代码可知createElement 实际上是由两个版本的,唯一的区别就是最后一个参数的不同。

查看src\core\vdom\create-element.js可知这个参数实际上叫做alwaysNormalize,他是一个布尔值,当他为true时,normalizationType就为 ALWAYS_NORMALIZE ,否则为 SIMPLE_NORMALIZE ,如果我们是手写render函数,那么调用的实际上就是true这个分支。

// The template compiler attempts to minimize the need for normalization by
// statically analyzing the template at compile time.
//
// For plain HTML markup, normalization can be completely skipped because the
// generated render function is guaranteed to return Array<VNode>. There are
// two cases where extra normalization is needed:
// 1. When the children contains components - because a functional component
// may return an Array instead of a single root. In this case, just a simple
// normalization is needed - if any child is an Array, we flatten the whole
// thing with Array.prototype.concat. It is guaranteed to be only 1-level deep
// because functional components already normalize their own children.
export function simpleNormalizeChildren (children: any) {
for (let i = 0; i < children.length; i++) {
if (Array.isArray(children[i])) {
return Array.prototype.concat.apply([], children)
}
}
return children
}
// 2. When the children contains constructs that always generated nested Arrays,
// e.g. <template>, <slot>, v-for, or when the children is provided by user
// with hand-written render functions / JSX. In such cases a full normalization
// is needed to cater to all possible types of children values.
export function normalizeChildren (children: any): ?Array<VNode> {
return isPrimitive(children)
? [createTextVNode(children)]
: Array.isArray(children)
? normalizeArrayChildren(children)
: undefined
}
上述这段代码的英文注释讲的很清楚,但是涉及到后续的函数式组件,暂时先放一放,总之这两个函数是有一个flat的功能,一个是一层的拍平,一个是递归的拍平。
三、update
上面有一句核心代码是vm._update(vm._render(), hydrating) ,我们来讲讲这个vm._update方法。全局搜索Vue.prototype._update可知在 lifecycleMixin 方法里有定义。
export function lifecycleMixin (Vue: Class<Component>) {
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) { //入参包含一个VNode
const vm: Component = this //缓存vm
const prevEl = vm.$el //缓存当前el
const prevVnode = vm._vnode //缓存当前vnode
const restoreActiveInstance = setActiveInstance(vm)
vm._vnode = vnode //替换成入参中的vnode
// Vue.prototype.__patch__ is injected in entry points
// based on the rendering backend used.
if (!prevVnode) { //第一次渲染
// initial render
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
} else {
// updates
vm.$el = vm.__patch__(prevVnode, vnode)
}
restoreActiveInstance()
// update __vue__ reference
if (prevEl) {
prevEl.__vue__ = null
}
if (vm.$el) {
vm.$el.__vue__ = vm
}
// if parent is an HOC, update its $el as well
if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
vm.$parent.$el = vm.$el
}
// updated hook is called by the scheduler to ensure that children are
// updated in a parent's updated hook.
}
我们看一下Vue.prototype.__patch__,他是在src\core\vdom\patch.js定义的
阅读源码可知这个文件中蕴含着大量的patch算法。