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]雙向數據綁定。