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