摘要:調(diào)用了方法,參數(shù)是拿到后,判斷類型是否為,如果有多個(gè),則是模板上有多個(gè)根節(jié)點(diǎn),觸發(fā)告警。
vm._render 生成虛擬dom
我們知道在掛載過(guò)程中, $mount 會(huì)調(diào)用 vm._update和vm._render 方法,vm._updata是負(fù)責(zé)把VNode渲染成真正的DOM,vm._render方法是用來(lái)把實(shí)例渲染成VNode,這里的_render是實(shí)例的私有方法,和前面我們說(shuō)的vm.render不是同一個(gè),先來(lái)看下vm._render定義,vm._render是通過(guò)renderMixin(Vue)掛載的,定義在src/core/instance/render.js :
// 簡(jiǎn)化版本 Vue.prototype._render = function (): VNode { const vm: Component = this const { render, _parentVnode } = vm.$options ... // render self let vnode try { // _renderProxy生產(chǎn)環(huán)境下是vm // 開(kāi)發(fā)環(huán)境可能是proxy對(duì)象 vnode = render.call(vm._renderProxy, vm.$createElement) // 近似vm.render(createElement) } catch (e) {...} // return empty vnode in case the render function errored out if (!(vnode instanceof VNode)) { if (process.env.NODE_ENV !== "production" && Array.isArray(vnode)) {...} vnode = createEmptyVNode() } // set parent vnode.parent = _parentVnode return vnode }
先緩存vm.$options.render和vm.$options._parentVnode,vm.$options.render是在上節(jié)的$mount中通過(guò)comileToFunctions方法將template/el編譯來(lái)的。
vnode = render.call(vm._renderProxy, vm.$createElement)調(diào)用了render方法,參數(shù)是vm._renderProxy,vm.$createElement
拿到vnode后,判斷類型是否為VNode,如果有多個(gè)vnode,則是模板上有多個(gè)根節(jié)點(diǎn),觸發(fā)告警。
掛載vnode父節(jié)點(diǎn),最后返回vnode
簡(jiǎn)要概括,vm._render函數(shù)最后是通過(guò)render執(zhí)行了createElement方法并返回vnode;下面就來(lái)具體看下vm._renderProxy,vm.$createElement,vnode
vm._renderProxy首先來(lái)看下vm._renderProxy,vm._renderProxy是在_init()中掛載的:
Vue.prototype._init = function (options?: Object) { ... if (process.env.NODE_ENV !== "production") { // 對(duì)vm對(duì)一層攔截處理,當(dāng)使用vm上沒(méi)有的屬性時(shí)將告警 initProxy(vm) } else { vm._renderProxy = vm } ... }
如果是生產(chǎn)環(huán)境,vm._renderProxy直接就是vm;開(kāi)發(fā)環(huán)境下,執(zhí)行initProxy(vm),找到定義:
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 // 對(duì)vm對(duì)一層攔截處理 vm._renderProxy = new Proxy(vm, handlers) } else { vm._renderProxy = vm } }
先判斷當(dāng)前是否支持Proxy(ES6新語(yǔ)法),支持的話會(huì)實(shí)例化一個(gè)Proxy, 當(dāng)前例子用的是hasHandler(只要判斷是否vm上有無(wú)屬性即可),這樣每次通過(guò)vm._renderProxy訪問(wèn)vm時(shí),都必須經(jīng)過(guò)這層代理:
// 判斷對(duì)象是否有某個(gè)屬性 const hasHandler = { has (target, key) { // vm中是否有key屬性 const has = key in target // 當(dāng)key是全局變量或者key是私有屬性且key沒(méi)有在$data中,允許訪問(wèn)該key const isAllowed = allowedGlobals(key) || (typeof key === "string" && key.charAt(0) === "_" && !(key in target.$data)) // 沒(méi)有該屬性且不允許訪問(wèn)該屬性時(shí)發(fā)起警告 if (!has && !isAllowed) { if (key in target.$data) warnReservedPrefix(target, key) else warnNonPresent(target, key) } return has || !isAllowed } }
所以,_render中的vnode = render.call(vm._renderProxy, vm.$createElement),實(shí)際上是執(zhí)行vm._renderProxy.render(vm.$createElement)
Virtual DOM 虛擬domvue.2.0中引入了virtual dom ,大大提升了代碼的性能。所謂virtual dom ,就是用js對(duì)象去描述一個(gè)dom節(jié)點(diǎn),這比真實(shí)創(chuàng)建dom快很多。在vue中,Virtual dom是用類vnode來(lái)表示,vnode在src/core/vdom/vnode.js中定義,有真實(shí)dom上也有的屬性,像tag/text/key/data/children等,還有些是vue的特色屬性,在渲染過(guò)程也會(huì)用到.
vm.$createElementvue文檔中介紹了render函數(shù),第一個(gè)參數(shù)就是createElement,之前的例子轉(zhuǎn)換成render函數(shù)就是:
{{ message }}// 轉(zhuǎn)換成render: render: function (createElement) { return createElement("div", { attrs: { id: "app" }, }, this.message) }
可以看出,createElement就是vm.$createElement
找到vm.$createElement定義,在initRender方法中,
// 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)
看到這里定義了2個(gè)實(shí)例方法都是調(diào)用的createElement,一個(gè)是用于編譯生成的render方法,一個(gè)是用于手寫render方法,createElement最后會(huì)返回Vnode,來(lái)看下createElement的定義:
export function createElement ( context: Component, //vm實(shí)例 tag: any, data: any, //可以不傳 children: any,// 子節(jié)點(diǎn) normalizationType: any, alwaysNormalize: boolean ) { // 參數(shù)判斷,不傳data時(shí),要把children,normalizationType參數(shù)往前移 if (Array.isArray(data) || isPrimitive(data)) { normalizationType = children children = data data = undefined } if (isTrue(alwaysNormalize)) { normalizationType = ALWAYS_NORMALIZE } return _createElement(context, tag, data, children, normalizationType) }
先經(jīng)過(guò)參數(shù)重載,根據(jù)alwaysNormalize傳不同的normalizationType,調(diào)用_createElement(),實(shí)際上createElement是提前對(duì)參數(shù)做了一層處理
這里的參數(shù)重載有個(gè)小點(diǎn)值得注意,normalizationType是關(guān)系到后面children的扁平處理,沒(méi)有children則不需要對(duì)normalizationType賦值,children和normalizationType就都是空值
首先校驗(yàn)data,data是響應(yīng)式的,調(diào)用createEmptyVNode直接返回注釋節(jié)點(diǎn):
export const createEmptyVNode = (text: string = "") => { const node = new VNode() node.text = text node.isComment = true//注釋vnode return node }
處理tag,沒(méi)有tag時(shí)也返回注釋節(jié)點(diǎn)
key做基礎(chǔ)類型校驗(yàn)
當(dāng)children中有function類型作slot處理,此處先不作分析
對(duì)children做normalize 變成vnode一維數(shù)組,有2種不同的方式:normalizeChildren和simpleNormalizeChildren
創(chuàng)建vnode
simpleNormalizeChildrennormalizeChildren和simpleNormalizeChildren是2種對(duì)children扁平化處理的方法,先來(lái)看下simpleNormalizeChildren定義:
export function simpleNormalizeChildren (children: any) { for (let i = 0; i < children.length; i++) { // 把嵌套數(shù)組拍平成一維數(shù)組 if (Array.isArray(children[i])) { return Array.prototype.concat.apply([], children) } } return children }
如果chilren中有一個(gè)是數(shù)組則將整個(gè)children作為參數(shù)組用concat連接,可以得到每個(gè)子元素都是vnode的children,這適用于只有一級(jí)嵌套數(shù)組的情況
normalizeChildrenexport function normalizeChildren (children: any): ?Array{ // 判斷是否基礎(chǔ)類型,是:創(chuàng)建文本節(jié)點(diǎn),否:判斷是否數(shù)組,是:作normalizeArrayChildren處理 return isPrimitive(children) ? [createTextVNode(children)] : Array.isArray(children) ? normalizeArrayChildren(children) : undefined }
普通的children處理:最后也是返回一組一維vnode的數(shù)組,當(dāng)children是Array時(shí),執(zhí)行normalizeArrayChildren
normalizeArrayChildren代碼較長(zhǎng),此處就不貼了,可以自己對(duì)照源碼來(lái)分析:
定義res
遍歷children,當(dāng)children[i]是空或者是布爾值,跳過(guò)該次循環(huán)
如果children[i]還是個(gè)數(shù)組,再對(duì)children[i]作normalizeArrayChildren處理
if (Array.isArray(c)) { if (c.length > 0) { c = normalizeArrayChildren(c, `${nestedIndex || ""}_${i}`)// 返回vnode數(shù)組 // merge adjacent text nodes // 優(yōu)化:如果c的第一個(gè)vnode和children上一次處理的vnode都是文本節(jié)點(diǎn)可以合并成一個(gè)vnode if (isTextNode(c[0]) && isTextNode(last)) { res[lastIndex] = createTextVNode(last.text + (c[0]: any).text) c.shift() } res.push.apply(res, c) } } else if (){...}
children[i]是基礎(chǔ)類型時(shí)
} else if (isPrimitive(c)) { // 當(dāng)c是基礎(chǔ)類型時(shí) // children上一次處理的vnode是文本節(jié)點(diǎn),則合并成一個(gè)文本節(jié)點(diǎn) if (isTextNode(last)) { // merge adjacent text nodes // this is necessary for SSR hydration because text nodes are // essentially merged when rendered to HTML strings // 這是SSR hydration所必需的,因?yàn)槲谋竟?jié)點(diǎn)渲染成html時(shí)基本上都是合并的 res[lastIndex] = createTextVNode(last.text + c) } else if (c !== "") { // convert primitive to vnode res.push(createTextVNode(c))// c不為空直接創(chuàng)建文本節(jié)點(diǎn) } } else {
其它情況,children[i]是vnode時(shí),
} else {// 當(dāng)c是vnode時(shí) if (isTextNode(c) && isTextNode(last)) { // merge adjacent text nodes res[lastIndex] = createTextVNode(last.text + c.text) } else { // default key for nested array children (likely generated by v-for) // 特殊處理,先略過(guò) if (isTrue(children._isVList) && isDef(c.tag) && isUndef(c.key) && isDef(nestedIndex)) { c.key = `__vlist${nestedIndex}_${i}__` } // push到res上 res.push(c) } }
最后返回一組vnode
主要有2個(gè)點(diǎn),一是normalizeArrayChildren的遞歸調(diào)用,二是文本節(jié)點(diǎn)的合并
創(chuàng)建vnode創(chuàng)建vnode,并返回
判斷tag類型,為字符串時(shí):
let Ctor ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag) // 判斷tag是否是原生標(biāo)簽 if (config.isReservedTag(tag)) { // platform built-in elements vnode = new VNode( config.parsePlatformTagName(tag), data, children, undefined, undefined, context ) } else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, "components", tag))) { // component組件部分先略過(guò) vnode = createComponent(Ctor, data, context, children, tag) } else { // unknown or unlisted namespaced elements // check at runtime because it may get assigned a namespace when its // parent normalizes children // 未知標(biāo)簽,創(chuàng)建vnode vnode = new VNode( tag, data, children, undefined, undefined, context ) }
tag不是字符串類型時(shí),vnode = createComponent(tag, data, context, children),先略過(guò)
最后再對(duì)生成的vnode作校驗(yàn),返回vnode
小結(jié)到此為止,我們分析了vm._render方法和_createElement方法,知道了創(chuàng)建vnode的整個(gè)過(guò)程,在$mount中的 vm._update(vm._render(), hydrating),vm._render返回了vnode,再傳入vm._update中,由vm._update渲染成真實(shí)dom。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/101023.html
摘要:模版語(yǔ)法中的模版是基于的模版語(yǔ)法,所有的模版都是合法的,所以能被遵循規(guī)范的瀏覽器和解析器解析。表達(dá)式模版中不僅僅可以進(jìn)行簡(jiǎn)單的數(shù)據(jù)綁定操作,我們還可以在模版中進(jìn)行簡(jiǎn)單的表達(dá)式。我們也簡(jiǎn)單的敘述了模版編譯的整個(gè)流程。 我們?cè)谏弦黄f(shuō)到如何把 Vue 實(shí)例中的數(shù)據(jù)顯示到視圖中,就會(huì)需要用到我們的模版,我們只是簡(jiǎn)單的使用了一些,模版其實(shí)還有很多其他的特性。今天我們就來(lái)看看模版的其他特性。 模...
摘要:五六月份推薦集合查看最新的請(qǐng)點(diǎn)擊集前端最近很火的框架資源定時(shí)更新,歡迎一下。蘇幕遮燎沈香宋周邦彥燎沈香,消溽暑。鳥(niǎo)雀呼晴,侵曉窺檐語(yǔ)。葉上初陽(yáng)乾宿雨,水面清圓,一一風(fēng)荷舉。家住吳門,久作長(zhǎng)安旅。五月漁郎相憶否。小楫輕舟,夢(mèng)入芙蓉浦。 五、六月份推薦集合 查看github最新的Vue weekly;請(qǐng)::點(diǎn)擊::集web前端最近很火的vue2框架資源;定時(shí)更新,歡迎 Star 一下。 蘇...
摘要:五六月份推薦集合查看最新的請(qǐng)點(diǎn)擊集前端最近很火的框架資源定時(shí)更新,歡迎一下。蘇幕遮燎沈香宋周邦彥燎沈香,消溽暑。鳥(niǎo)雀呼晴,侵曉窺檐語(yǔ)。葉上初陽(yáng)乾宿雨,水面清圓,一一風(fēng)荷舉。家住吳門,久作長(zhǎng)安旅。五月漁郎相憶否。小楫輕舟,夢(mèng)入芙蓉浦。 五、六月份推薦集合 查看github最新的Vue weekly;請(qǐng)::點(diǎn)擊::集web前端最近很火的vue2框架資源;定時(shí)更新,歡迎 Star 一下。 蘇...
摘要:前端日?qǐng)?bào)精選未來(lái)布局之星更快地構(gòu)建使用預(yù)解析以及深入的虛擬原理原來(lái)與是這樣阻塞解析和渲染的怎樣把網(wǎng)站升級(jí)到中文視頻從談函數(shù)式與響應(yīng)式編程葉俊星系列三之煙花效果實(shí)現(xiàn)掘金的故事解剖表情動(dòng)圖的構(gòu)成設(shè)計(jì)系列傳統(tǒng)遞歸和尾調(diào)用的實(shí)現(xiàn)前端架構(gòu)經(jīng) 2017-09-24 前端日?qǐng)?bào) 精選 未來(lái)布局之星Grid更快地構(gòu)建DOM: 使用預(yù)解析, async, defer 以及 preload_JavaScri...
閱讀 2926·2019-08-30 15:55
閱讀 2919·2019-08-30 15:53
閱讀 2366·2019-08-26 13:47
閱讀 2646·2019-08-26 13:43
閱讀 3234·2019-08-26 13:33
閱讀 2875·2019-08-26 11:53
閱讀 1852·2019-08-23 18:35
閱讀 878·2019-08-23 17:16