• <fieldset id="8imwq"><menu id="8imwq"></menu></fieldset>
  • <bdo id="8imwq"><input id="8imwq"></input></bdo>
    最新文章專題視頻專題問答1問答10問答100問答1000問答2000關鍵字專題1關鍵字專題50關鍵字專題500關鍵字專題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關鍵字專題關鍵字專題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
    當前位置: 首頁 - 科技 - 知識百科 - 正文

    vue 中Virtual Dom被創建的方法

    來源:懂視網 責編:小采 時間:2020-11-27 21:58:42
    文檔

    vue 中Virtual Dom被創建的方法

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

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

    01  render函數

    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的原型上,會返回vnode,vnode通過代碼render.call(vm._renderProxy, vm.$createElement)進行創建。

    在創建vnode過程中,如果出現錯誤,就會執行catch中代碼做降級處理。

    _render中最核心的代碼就是:

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

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

    render函數

    const { render, _parentVnode } = vm.$options

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

    在組件中開發者直接手寫的render函數

    通過編譯template屬性生成

    參數 vm._renderProxy

    vm._renderProxy定義在 src/core/instance/init.js 中,是call的第一個參數,指定render函數執行的上下文。

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

    生產環境:

    vm._renderProxy = vm,也就是說,在生產環境,render函數執行的上下文就是當前vue實例,即當前組件的this。

    開發環境:

    開發環境會執行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作用是在訪問一個對象時,對其進行攔截,new Proxy的第一個參數表示所要攔截的對象,第二個參數是用來定制攔截行為的對象。

    開發環境,如果支持Proxy就會對vm實例進行攔截,否則和生產環境相同,直接將vm賦值給vm._renderProxy。具體的攔截行為通過handlers對象指定。

    當手寫render函數時,handlers = hasHandler,通過template生成的render函數,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分別是對vm對象的屬性的讀取和propKey in proxy的操作進行攔截,并對vm的參數進行校驗,再調用 warnNonPresent 和 warnReservedPrefix 進行Warn警告。

    可見,initProxy方法的主要作用就是在開發時,對vm實例進行攔截發現問題并拋出錯誤,方便開發者及時修改問題。
    參數 vm.$createElement

    vm.$createElement就是手寫render函數時傳入的createElement函數,它定義在initRender方法中,initRender在new Vue初始化時執行,參數是實例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是為開發者手寫render函數提供的方法,vm._c是為通過編譯template生成的render函數使用的方法。它們都會調用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方法主要是對參數做一些處理,再調用_createElement方法創建vnode。

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

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

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

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

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

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

    處理好參數后調用_createElement方法創建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()
     }
    }

    方法開始會做判斷,如果data是響應式的數據,component的is屬性不是真值的時候,都會去調用createEmptyVNode方法,創建一個空的vnode。 接下來,根據normalizationType的值,調用normalizeChildren或simpleNormalizeChildren方法對參數children進行處理。這兩個方法定義在 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數組扁平化處理,最終返回一個vnode的一維數組。
    simpleNormalizeChildren是針對函數式組件做處理,所以只需要考慮children是二維數組的情況。 normalizeChildren方法會考慮children是多層嵌套的數組的情況。normalizeChildren開始會判斷children的類型,如果children是基礎類型,直接創建文本vnode,如果是數組,調用normalizeArrayChildren方法,并在normalizeArrayChildren方法里面進行遞歸調用,最終將children轉成一維數組。

    接下來,繼續看_createElement方法,如果tag參數的類型不是String類型,是組件的話,調用createComponent創建vnode。如果tag是String類型,再去判斷tag是否是html的保留標簽,是否是不認識的節點,通過調用new VNode(),傳入不同的參數來創建vnode實例。

    無論是哪種情況,最終都是通過VNode這個class來創建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 // 標簽名
     this.data = data // 當前節點數據
     this.children = children // 子節點
     this.text = text // 文本
     this.elm = elm // 對應的真實DOM節點
     this.ns = undefined // 命名空間
     this.context = context // 當前節點上下文
     this.fnContext = undefined // 函數化組件上下文
     this.fnOptions = undefined // 函數化組件配置參數
     this.fnScopeId = undefined // 函數化組件ScopeId
     this.key = data && data.key // 子節點key屬性
     this.componentOptions = componentOptions // 組件配置項 
     this.componentInstance = undefined // 組件實例
     this.parent = undefined // 父節點
     this.raw = false // 是否是原生的HTML片段或只是普通文本
     this.isStatic = false // 靜態節點標記
     this.isRootInsert = true // 是否作為根節點插入
     this.isComment = false // 是否為注釋節點
     this.isCloned = false // 是否為克隆節點
     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類定義的數據,都是用來描述VNode的。

    至此,render函數創建vdom的源碼就分析完了,我們簡單的總結梳理一下。

    _render 定義在 Vue.prototype 上,_render函數執行會調用方法render,在開發環境下,會對vm實例進行代理,校驗vm實例數據正確性。render函數內,會執行render的參數createElement方法,createElement會對參數進行處理,處理參數后調用_createElement, _createElement方法內部最終會直接或間接調用new VNode(), 創建vnode實例。

    03   vnode && vdom

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

    04  心得

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

    總結

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

    聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com

    文檔

    vue 中Virtual Dom被創建的方法

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

    最新推薦

    猜你喜歡

    熱門推薦

    專題
    Top
    主站蜘蛛池模板: 奇米精品一区二区三区在线观看| 国产精品无码久久久久| 欧美大片日韩精品| 亚洲AV永久无码精品网站在线观看| 国产精品爽爽ⅴa在线观看| 2022国产精品福利在线观看| 亚洲中文字幕无码久久精品1| 精品国产高清在线拍| 9re热国产这里只有精品| 九九热这里只有在线精品视| 国产欧美亚洲精品A| 亚洲2022国产成人精品无码区| 麻豆精品视频在线观看91| 国产精品色视频ⅹxxx | 91精品啪在线观看国产| 无码日韩精品一区二区免费暖暖| 欧美激情精品久久久久久久| 国产精品美女久久久网AV| 日本精品久久久久中文字幕8| 99热门精品一区二区三区无码| 亚洲国产精品成人精品无码区| 一区二区国产精品 | 亚洲乱码国产乱码精品精| 亚洲国产成人乱码精品女人久久久不卡| 国产精品一香蕉国产线看观看 | 国内精品在线视频| 国产香蕉精品视频在| 国产cosplay精品视频| 99re6在线视频精品免费| 国产精品亚洲片在线| 精品国产一区二区三区色欲 | 国产观看精品一区二区三区| 1区1区3区4区产品芒果精品| 91精品国产91久久久久久青草| 热re99久久精品国产99热| 青青草国产精品久久| 98香蕉草草视频在线精品看| 91精品国产麻豆国产自产在线 | 亚洲AV永久无码精品一区二区 | 国产精品免费高清在线观看| 国产成人A人亚洲精品无码|