<span id="mktg5"></span>

<i id="mktg5"><meter id="mktg5"></meter></i>

        <label id="mktg5"><meter id="mktg5"></meter></label>
        最新文章專題視頻專題問答1問答10問答100問答1000問答2000關(guān)鍵字專題1關(guān)鍵字專題50關(guān)鍵字專題500關(guān)鍵字專題1500TAG最新視頻文章推薦1 推薦3 推薦5 推薦7 推薦9 推薦11 推薦13 推薦15 推薦17 推薦19 推薦21 推薦23 推薦25 推薦27 推薦29 推薦31 推薦33 推薦35 推薦37視頻文章20視頻文章30視頻文章40視頻文章50視頻文章60 視頻文章70視頻文章80視頻文章90視頻文章100視頻文章120視頻文章140 視頻2關(guān)鍵字專題關(guān)鍵字專題tag2tag3文章專題文章專題2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章專題3
        問答文章1 問答文章501 問答文章1001 問答文章1501 問答文章2001 問答文章2501 問答文章3001 問答文章3501 問答文章4001 問答文章4501 問答文章5001 問答文章5501 問答文章6001 問答文章6501 問答文章7001 問答文章7501 問答文章8001 問答文章8501 問答文章9001 問答文章9501
        當(dāng)前位置: 首頁(yè) - 科技 - 知識(shí)百科 - 正文

        vue 中Virtual Dom被創(chuàng)建的方法

        來源:懂視網(wǎng) 責(zé)編:小采 時(shí)間:2020-11-27 21:58:42
        文檔

        vue 中Virtual Dom被創(chuàng)建的方法

        vue 中Virtual Dom被創(chuàng)建的方法:本文將通過解讀render函數(shù)的源碼,來分析vue中的vNode是如何創(chuàng)建的。在vue2.x的版本中,無論是直接書寫render函數(shù),還是使用template或el屬性,或是使用.vue單文件的形式,最終都需要編譯成render函數(shù)進(jìn)行vnode的創(chuàng)建,最終再渲染成真實(shí)的DOM。 如果對(duì)
        推薦度:
        導(dǎo)讀vue 中Virtual Dom被創(chuàng)建的方法:本文將通過解讀render函數(shù)的源碼,來分析vue中的vNode是如何創(chuàng)建的。在vue2.x的版本中,無論是直接書寫render函數(shù),還是使用template或el屬性,或是使用.vue單文件的形式,最終都需要編譯成render函數(shù)進(jìn)行vnode的創(chuàng)建,最終再渲染成真實(shí)的DOM。 如果對(duì)

        本文將通過解讀render函數(shù)的源碼,來分析vue中的vNode是如何創(chuàng)建的。在vue2.x的版本中,無論是直接書寫render函數(shù),還是使用template或el屬性,或是使用.vue單文件的形式,最終都需要編譯成render函數(shù)進(jìn)行vnode的創(chuàng)建,最終再渲染成真實(shí)的DOM。 如果對(duì)vue源碼的目錄還不是很了解,推薦先閱讀下 深入vue -- 源碼目錄和編譯過程。

        01  render函數(shù)

        render方法定義在文件 src/core/instance/render.js 中

        Vue.prototype._render = function (): VNode {
         const vm: Component = this
         const { render, _parentVnode } = vm.$options
         // ... 
         // 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 {
         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
         }
         }
         // 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
         }

        _render定義在vue的原型上,會(huì)返回vnode,vnode通過代碼render.call(vm._renderProxy, vm.$createElement)進(jìn)行創(chuàng)建。

        在創(chuàng)建vnode過程中,如果出現(xiàn)錯(cuò)誤,就會(huì)執(zhí)行catch中代碼做降級(jí)處理。

        _render中最核心的代碼就是:

        vnode = render.call(vm._renderProxy, vm.$createElement)

        接下來,分析下這里的render,vm._renderProxy,vm.$createElement分別是什么。

        render函數(shù)

        const { render, _parentVnode } = vm.$options

        render方法是從$options中提取的。render方法有兩種途徑得來:

        在組件中開發(fā)者直接手寫的render函數(shù)

        通過編譯template屬性生成

        參數(shù) vm._renderProxy

        vm._renderProxy定義在 src/core/instance/init.js 中,是call的第一個(gè)參數(shù),指定render函數(shù)執(zhí)行的上下文。

        /* istanbul ignore else */
        if (process.env.NODE_ENV !== 'production') {
         initProxy(vm)
        } else {
         vm._renderProxy = vm
        }

        生產(chǎn)環(huán)境:

        vm._renderProxy = vm,也就是說,在生產(chǎn)環(huán)境,render函數(shù)執(zhí)行的上下文就是當(dāng)前vue實(shí)例,即當(dāng)前組件的this。

        開發(fā)環(huán)境:

        開發(fā)環(huán)境會(huì)執(zhí)行initProxy(vm),initProxy定義在文件 src/core/instance/proxy.js 中。

        let 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
         }
        }

        hasProxy的定義如下

        const hasProxy =
         typeof Proxy !== 'undefined' && isNative(Proxy)

        用來判斷瀏覽器是否支持es6的Proxy。

        Proxy作用是在訪問一個(gè)對(duì)象時(shí),對(duì)其進(jìn)行攔截,new Proxy的第一個(gè)參數(shù)表示所要攔截的對(duì)象,第二個(gè)參數(shù)是用來定制攔截行為的對(duì)象。

        開發(fā)環(huán)境,如果支持Proxy就會(huì)對(duì)vm實(shí)例進(jìn)行攔截,否則和生產(chǎn)環(huán)境相同,直接將vm賦值給vm._renderProxy。具體的攔截行為通過handlers對(duì)象指定。

        當(dāng)手寫render函數(shù)時(shí),handlers = hasHandler,通過template生成的render函數(shù),handlers = getHandler。 hasHandler代碼:

        const hasHandler = {
         has (target, key) {
         const has = key in target
         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
         }
        }
        
        

        getHandler代碼

        const getHandler = {
         get (target, key) {
         if (typeof key === 'string' && !(key in target)) {
         if (key in target.$data) warnReservedPrefix(target, key)
         else warnNonPresent(target, key)
         }
         return target[key]
         }
        }

        hasHandler,getHandler分別是對(duì)vm對(duì)象的屬性的讀取和propKey in proxy的操作進(jìn)行攔截,并對(duì)vm的參數(shù)進(jìn)行校驗(yàn),再調(diào)用 warnNonPresent 和 warnReservedPrefix 進(jìn)行Warn警告。

        可見,initProxy方法的主要作用就是在開發(fā)時(shí),對(duì)vm實(shí)例進(jìn)行攔截發(fā)現(xiàn)問題并拋出錯(cuò)誤,方便開發(fā)者及時(shí)修改問題。
        參數(shù) vm.$createElement

        vm.$createElement就是手寫render函數(shù)時(shí)傳入的createElement函數(shù),它定義在initRender方法中,initRender在new Vue初始化時(shí)執(zhí)行,參數(shù)是實(shí)例vm。

        export function initRender (vm: Component) {
         // ...
         // 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)
         // ...
        }

        從代碼的注釋可以看出: vm.$createElement是為開發(fā)者手寫render函數(shù)提供的方法,vm._c是為通過編譯template生成的render函數(shù)使用的方法。它們都會(huì)調(diào)用createElement方法。

        02  createElement方法

        createElement方法定義在 src/core/vdom/create-element.js 文件中

        const SIMPLE_NORMALIZE = 1
        const ALWAYS_NORMALIZE = 2
        // wrapper function for providing a more flexible interface
        // without getting yelled at by flow
        export function createElement (
         context: Component,
         tag: any,
         data: any,
         children: any,
         normalizationType: any,
         alwaysNormalize: boolean
        ): VNode | Array<VNode> {
         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)
        }

        createElement方法主要是對(duì)參數(shù)做一些處理,再調(diào)用_createElement方法創(chuàng)建vnode。

        下面看一下vue文檔中createElement能接收的參數(shù)。

        // @returns {VNode}
        createElement(
         // {String | Object | Function}
         // 一個(gè) HTML 標(biāo)簽字符串,組件選項(xiàng)對(duì)象,或者
         // 解析上述任何一種的一個(gè) async 異步函數(shù)。必需參數(shù)。
         'div',
        
         // {Object}
         // 一個(gè)包含模板相關(guān)屬性的數(shù)據(jù)對(duì)象
         // 你可以在 template 中使用這些特性。可選參數(shù)。
         {
         },
        
         // {String | Array}
         // 子虛擬節(jié)點(diǎn) (VNodes),由 `createElement()` 構(gòu)建而成,
         // 也可以使用字符串來生成“文本虛擬節(jié)點(diǎn)”。可選參數(shù)。
         [
         '先寫一些文字',
         createElement('h1', '一則頭條'),
         createElement(MyComponent, {
         props: {
         someProp: 'foobar'
         }
         })
         ]
        )

        文檔中除了第一個(gè)參數(shù)是必選參數(shù),其他都是可選參數(shù)。也就是說使用createElement方法的時(shí)候,可以不傳第二個(gè)參數(shù),只傳第一個(gè)參數(shù)和第三個(gè)參數(shù)。剛剛說的參數(shù)處理就是對(duì)這種情況做處理。

        if (Array.isArray(data) || isPrimitive(data)) {
         normalizationType = children
         children = data
         data = undefined
        }

        通過判斷data是否是數(shù)組或者是基礎(chǔ)類型,如果滿足這個(gè)條件,說明這個(gè)位置傳的參數(shù)是children,然后對(duì)參數(shù)依次重新賦值。這種方式被稱為重載。

        重載:函數(shù)名相同,函數(shù)的參數(shù)列表不同(包括參數(shù)個(gè)數(shù)和參數(shù)類型),至于返回類型可同可不同。

        處理好參數(shù)后調(diào)用_createElement方法創(chuàng)建vnode。下面是_createElement方法的核心代碼。

        export function _createElement (
         context: Component,
         tag?: string | Class<Component> | Function | Object,
         data?: VNodeData,
         children?: any,
         normalizationType?: number
        ): VNode | Array<VNode> {
         // ...
         if (normalizationType === ALWAYS_NORMALIZE) {
         children = normalizeChildren(children)
         } else if (normalizationType === SIMPLE_NORMALIZE) {
         children = simpleNormalizeChildren(children)
         }
         let vnode, ns
         if (typeof tag === 'string') {
         let Ctor
         // ...
         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 = new VNode(
         tag, data, children,
         undefined, undefined, context
         )
         }
         } else {
         // direct component options / constructor
         vnode = createComponent(tag, data, context, children)
         }
         if (Array.isArray(vnode)) {
         return vnode
         } else if (isDef(vnode)) {
         if (isDef(ns)) applyNS(vnode, ns)
         if (isDef(data)) registerDeepBindings(data)
         return vnode
         } else {
         return createEmptyVNode()
         }
        }

        方法開始會(huì)做判斷,如果data是響應(yīng)式的數(shù)據(jù),component的is屬性不是真值的時(shí)候,都會(huì)去調(diào)用createEmptyVNode方法,創(chuàng)建一個(gè)空的vnode。 接下來,根據(jù)normalizationType的值,調(diào)用normalizeChildren或simpleNormalizeChildren方法對(duì)參數(shù)children進(jìn)行處理。這兩個(gè)方法定義在 src/core/vdom/helpers/normalize-children.js 文件下。

        // 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
        }
        
        

        normalizeChildren和simpleNormalizeChildren的目的都是將children數(shù)組扁平化處理,最終返回一個(gè)vnode的一維數(shù)組。
        simpleNormalizeChildren是針對(duì)函數(shù)式組件做處理,所以只需要考慮children是二維數(shù)組的情況。 normalizeChildren方法會(huì)考慮children是多層嵌套的數(shù)組的情況。normalizeChildren開始會(huì)判斷children的類型,如果children是基礎(chǔ)類型,直接創(chuàng)建文本vnode,如果是數(shù)組,調(diào)用normalizeArrayChildren方法,并在normalizeArrayChildren方法里面進(jìn)行遞歸調(diào)用,最終將children轉(zhuǎn)成一維數(shù)組。

        接下來,繼續(xù)看_createElement方法,如果tag參數(shù)的類型不是String類型,是組件的話,調(diào)用createComponent創(chuàng)建vnode。如果tag是String類型,再去判斷tag是否是html的保留標(biāo)簽,是否是不認(rèn)識(shí)的節(jié)點(diǎn),通過調(diào)用new VNode(),傳入不同的參數(shù)來創(chuàng)建vnode實(shí)例。

        無論是哪種情況,最終都是通過VNode這個(gè)class來創(chuàng)建vnode,下面是類VNode的源碼,在文件 src/core/vdom/vnode.js 中定義

        export default class VNode {
         tag: string | void;
         data: VNodeData | void;
         children: ?Array<VNode>;
         text: string | void;
         elm: Node | void;
         ns: string | void;
         context: Component | void; // rendered in this component's scope
         key: string | number | void;
         componentOptions: VNodeComponentOptions | void;
         componentInstance: Component | void; // component instance
         parent: VNode | void; // component placeholder node
        
         // strictly internal
         raw: boolean; // contains raw HTML? (server only)
         isStatic: boolean; // hoisted static node
         isRootInsert: boolean; // necessary for enter transition check
         isComment: boolean; // empty comment placeholder?
         isCloned: boolean; // is a cloned node?
         isOnce: boolean; // is a v-once node?
         asyncFactory: Function | void; // async component factory function
         asyncMeta: Object | void;
         isAsyncPlaceholder: boolean;
         ssrContext: Object | void;
         fnContext: Component | void; // real context vm for functional nodes
         fnOptions: ?ComponentOptions; // for SSR caching
         devtoolsMeta: ?Object; // used to store functional render context for devtools
         fnScopeId: ?string; // functional scope id support
        
         constructor (
         tag?: string,
         data?: VNodeData,
         children?: ?Array<VNode>,
         text?: string,
         elm?: Node,
         context?: Component,
         componentOptions?: VNodeComponentOptions,
         asyncFactory?: Function
        ) {
         this.tag = tag // 標(biāo)簽名
         this.data = data // 當(dāng)前節(jié)點(diǎn)數(shù)據(jù)
         this.children = children // 子節(jié)點(diǎn)
         this.text = text // 文本
         this.elm = elm // 對(duì)應(yīng)的真實(shí)DOM節(jié)點(diǎn)
         this.ns = undefined // 命名空間
         this.context = context // 當(dāng)前節(jié)點(diǎn)上下文
         this.fnContext = undefined // 函數(shù)化組件上下文
         this.fnOptions = undefined // 函數(shù)化組件配置參數(shù)
         this.fnScopeId = undefined // 函數(shù)化組件ScopeId
         this.key = data && data.key // 子節(jié)點(diǎn)key屬性
         this.componentOptions = componentOptions // 組件配置項(xiàng) 
         this.componentInstance = undefined // 組件實(shí)例
         this.parent = undefined // 父節(jié)點(diǎn)
         this.raw = false // 是否是原生的HTML片段或只是普通文本
         this.isStatic = false // 靜態(tài)節(jié)點(diǎn)標(biāo)記
         this.isRootInsert = true // 是否作為根節(jié)點(diǎn)插入
         this.isComment = false // 是否為注釋節(jié)點(diǎn)
         this.isCloned = false // 是否為克隆節(jié)點(diǎn)
         this.isOnce = false // 是否有v-once指令
         this.asyncFactory = asyncFactory // 異步工廠方法 
         this.asyncMeta = undefined // 異步Meta
         this.isAsyncPlaceholder = false // 是否異步占位
         }
        
         // DEPRECATED: alias for componentInstance for backwards compat.
         /* istanbul ignore next */
         get child (): Component | void {
         return this.componentInstance
         }
        }

        VNode類定義的數(shù)據(jù),都是用來描述VNode的。

        至此,render函數(shù)創(chuàng)建vdom的源碼就分析完了,我們簡(jiǎn)單的總結(jié)梳理一下。

        _render 定義在 Vue.prototype 上,_render函數(shù)執(zhí)行會(huì)調(diào)用方法render,在開發(fā)環(huán)境下,會(huì)對(duì)vm實(shí)例進(jìn)行代理,校驗(yàn)vm實(shí)例數(shù)據(jù)正確性。render函數(shù)內(nèi),會(huì)執(zhí)行render的參數(shù)createElement方法,createElement會(huì)對(duì)參數(shù)進(jìn)行處理,處理參數(shù)后調(diào)用_createElement, _createElement方法內(nèi)部最終會(huì)直接或間接調(diào)用new VNode(), 創(chuàng)建vnode實(shí)例。

        03   vnode && vdom

        createElement 返回的vnode并不是真正的dom元素,VNode的全稱叫做“虛擬節(jié)點(diǎn) (Virtual Node)”,它所包含的信息會(huì)告訴 Vue 頁(yè)面上需要渲染什么樣的節(jié)點(diǎn),及其子節(jié)點(diǎn)。我們常說的“虛擬 DOM(Virtual Dom)”是對(duì)由 Vue 組件樹建立起來的整個(gè) VNode 樹的稱呼。

        04  心得

        讀源碼切忌只看源碼,一定要結(jié)合具體的使用一起分析,這樣才能更清楚的了解某段代碼的意圖。就像本文render函數(shù),如果從來沒有使用過render函數(shù),直接就閱讀這塊源碼可能會(huì)比較吃力,不妨先看看文檔,寫個(gè)demo,看看具體的使用,再對(duì)照使用來分析源碼,這樣很多比較困惑的問題就迎刃而解了。

        總結(jié)

        以上所述是小編給大家介紹的vue 中Virtual Dom被創(chuàng)建的方法,希望對(duì)大家有所幫助,如果大家有任何疑問請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
        如果你覺得本文對(duì)你有幫助,歡迎轉(zhuǎn)載,煩請(qǐng)注明出處,謝謝!

        聲明:本網(wǎng)頁(yè)內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com

        文檔

        vue 中Virtual Dom被創(chuàng)建的方法

        vue 中Virtual Dom被創(chuàng)建的方法:本文將通過解讀render函數(shù)的源碼,來分析vue中的vNode是如何創(chuàng)建的。在vue2.x的版本中,無論是直接書寫render函數(shù),還是使用template或el屬性,或是使用.vue單文件的形式,最終都需要編譯成render函數(shù)進(jìn)行vnode的創(chuàng)建,最終再渲染成真實(shí)的DOM。 如果對(duì)
        推薦度:
        標(biāo)簽: 創(chuàng)建 中的 VUE
        • 熱門焦點(diǎn)

        最新推薦

        猜你喜歡

        熱門推薦

        專題
        Top
        主站蜘蛛池模板: 国产免费毛不卡片| 午夜精品免费在线观看| 在线观看视频免费国语| 亚洲一卡2卡3卡4卡5卡6卡| 青青青国产在线观看免费网站| 久久精品亚洲中文字幕无码麻豆| 国产精品网站在线观看免费传媒 | 久久亚洲AV无码精品色午夜麻豆 | 久久精品7亚洲午夜a| 免费v片在线观看品善网| 亚洲中文字幕久久无码| 男人的好看免费观看在线视频 | 在线播放免费播放av片 | 中国一级毛片视频免费看| 曰韩亚洲av人人夜夜澡人人爽| 久久国产精品免费一区| 亚洲高清在线视频| 国产香蕉免费精品视频| 亚洲一本到无码av中文字幕| 免费在线精品视频| 国产午夜无码精品免费看| 亚洲综合免费视频| 免费看香港一级毛片| 一级黄色片免费观看| 久久精品国产精品亚洲精品 | 亚洲AV成人一区二区三区AV| 黄页网站免费在线观看| 粉色视频免费入口| 亚洲精品无码专区久久久 | 国产在线不卡免费播放| 野花香高清在线观看视频播放免费 | 亚洲1234区乱码| 亚洲成人一区二区| 伊人久久免费视频| 亚洲A∨精品一区二区三区下载| 亚洲色一色噜一噜噜噜| 久久永久免费人妻精品下载| 亚洲s码欧洲m码吹潮| 久久夜色精品国产亚洲AV动态图 | 久久亚洲高清综合| 国产人成免费视频网站|