render函数能够做为一道分割线,render函数的左边能够称之为编译期,将Vue的模板转换为渲染函数。render函数的右边是Vue的运行时,主要是基于渲染函数生成Virtual DOM树,Diff和Patch。vue
第1个问题?:node
with一般被当作重复引用同一个对象中的多个属性的快捷方式,能够不须要重复引用对象自己。引自《你不知道的JavaScript》上2.2.2节with
with(this)会造成块级做用域this,render函数里面的变量都会指向Vue实例(this)数组
第2个问题?:缓存
通过parse生成AST和optimize对AST树的优化,会生成render函数的字符串。在下图框出的createCompilerCreator(在src\compiler\create-compiler.js),调用createCompileToFunctionFn将字符串转成函数。bash
createCompileToFunctionFn(在src\compiler\to-function.js)中,会优先读缓存信息,若没有才会执行编译方法,同时将render字符串经过createFunction(在src\compiler\to-function.js中)调用New Function()的方法,创造render函数,而且缓存信息。
app
function createFunction (code, errors) {
try {
return new Function(code)
} catch (err) {
errors.push({ err, code })
return noop
}
}复制代码
export function renderSlot (
name: string,
fallback: ?Array<VNode>,
props: ?Object,
bindObject: ?Object): ?Array<VNode> {
const scopedSlotFn = this.$scopedSlots[name]
let nodes
if (scopedSlotFn) {
props = props || {}
if (bindObject) {
props = extend(extend({}, bindObject), props)
}
nodes = scopedSlotFn(props) || fallback
} else {
nodes = this.$slots[name] || fallback
}
const target = props && props.slot
if (target) {
//调用createElemnet return this.$createElement('template', { slot: target }, nodes)
} else {
return nodes
}
}
//有做用域插槽,会合并props和须要绑定的对象,否则直接去$solt数组里取,最后会调用creatElement()复制代码
//核心代码
vnode = render.call(vm._renderProxy, vm.$createElement)复制代码
以上是render函数执行的核心代码,render归根结底,是调用createElement建立vnode节点。下面会详细分析createElement到底作了哪些事情,首先咱们先经过一个例子,来看下,createElement方法的入参,主要为3个入参,可经过一个例子呈现:dom
<div id="app">
<render-element></render-element>
</div>
Vue.component('render-element', {
render: function (createElement) {
var self = this
return createElement(
'div', // 第一个参数是HTML标签字符 “必选”
{
class: {
title: true
},
style: {
border: '1px solid',
padding: '10px'
}
}, // 第二个参数是包含模板相关属性的数据对象 “可选”
[
createElement('h1', 'Hello Vue!'),
createElement('p', '开始学习Vue!')
] // 第三个参数是传涵盖子元素的一个数组 “可选”
)
}
})
let app = new Vue({
el: '#app'
})复制代码
const SIMPLE_NORMALIZE = 1
const ALWAYS_NORMALIZE = 2
function createElement (context, tag, data, children, normalizationType, alwaysNormalize) {
// 兼容不传data的状况
if (Array.isArray(data) || isPrimitive(data)) {
normalizationType = children
children = data
data = undefined
}
if (alwaysNormalize) normalizationType = ALWAYS_NORMALIZE
// 调用_createElement建立虚拟节点
return _createElement(context, tag, data, children, normalizationType)
}
function _createElement (context, tag, data, children, normalizationType) {
// 若是存在data.__ob__,说明data是被Observer观察的数据,不能用做虚拟节点的data,返回一个空节点
if (data && data.__ob__) {
process.env.NODE_ENV !== 'production' && warn(
`Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` +
'Always create fresh vnode data objects in each render!',
context
)
return createEmptyVNode()
}
// 当组件的tag未空,渲染一个空节点
if (!tag) {
return createEmptyVNode()
}
// 做用域插槽
if (Array.isArray(children) && typeof children[0] === 'function') {
data = data || {}
data.scopedSlots = { default: children[0] }
children.length = 0
}
// 根据normalizationType的值,选择不一样的处理方法
if (normalizationType === ALWAYS_NORMALIZE) {
children = normalizeChildren(children)
} else if (normalizationType === SIMPLE_NORMALIZE) {
children = simpleNormalizeChildren(children)
}
let vnode, ns
// 若是标签名是字符串类型
if (typeof tag === 'string') {
let Ctor
// 获取标签命名空间
ns = config.getTagNamespace(tag)
// 判断是否为保留标签
if (config.isReservedTag(tag)) {
// 若是是保留标签,就建立一个这样的vnode
vnode = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined, undefined, context
)
// vm的components上查找是否有这个标签的定义
} else if ((Ctor = resolveAsset(context.$options, 'components', tag))) {
// 若是找到了这个标签的定义,就以此建立虚拟组件节点
vnode = createComponent(Ctor, data, context, children, tag)
} else {
// 兜底方案,正常建立一个vnode
vnode = new VNode(
tag, data, children,
undefined, undefined, context
)
}
// 当tag不是字符串的时候,咱们认为tag是组件的构造类,直接建立
} else {
vnode = createComponent(tag, data, context, children)
}
// 若是有vnode
if (vnode) {
// 应用namespace,绑定data,而后返回vnode
if (ns) applyNS(vnode, ns)
if (isDef(data)) registerDeepBindings(data)
return vnode
} else {
return createEmptyVNode()
}
}
}复制代码
梳理成流程图以下:函数
特别注意当经过tag判断为组件时,会执行createcompontent()oop
最后总结下render函数的编译的主要几个步骤:
学习
另外本文的重点是createElement如何生成一个vnode,接下来vnode如何映射到正式的dom上,是经过数据变化,通知vm.watcher,最终调用vm.update,最后调用patch方法映射到真实的dom节点中,这里涉及到数据双向绑定相关,请详见[vue]双向数据绑定。