亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > JavaScript > 正文

vue 中Virtual Dom被創建的方法

2019-11-19 11:46:51
字體:
來源:轉載
供稿:網友

本文將通過解讀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 = 1const ALWAYS_NORMALIZE = 2// wrapper function for providing a more flexible interface// without getting yelled at by flowexport 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 中使用這些特性??蛇x參數。 { }, // {String | Array} // 子虛擬節點 (VNodes),由 `createElement()` 構建而成, // 也可以使用字符串來生成“文本虛擬節點”??蛇x參數。 [ '先寫一些文字', 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被創建的方法,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對武林網網站的支持!
如果你覺得本文對你有幫助,歡迎轉載,煩請注明出處,謝謝!

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
午夜精品久久久久久99热| 亚洲va久久久噜噜噜久久天堂| 亚洲人成在线免费观看| 亚洲第一免费播放区| 欧美韩日一区二区| 国产97在线视频| 亚洲影视九九影院在线观看| 国产精品视频一区二区三区四| 日韩高清电影免费观看完整版| 国产不卡av在线免费观看| 欧美在线视频一二三| 欧美国产精品日韩| 国产精品影院在线观看| 亚洲视频一区二区三区| 91久久久久久久久久久| 国内精品免费午夜毛片| 国产成人激情视频| 疯狂蹂躏欧美一区二区精品| 狠狠躁夜夜躁久久躁别揉| 91产国在线观看动作片喷水| 亚洲国产精品小视频| 日本精品免费一区二区三区| 欧美一区视频在线| 久久久久久久久久久网站| 国内外成人免费激情在线视频| 成人欧美一区二区三区在线| 久久精品国产欧美亚洲人人爽| 欧美诱惑福利视频| 国产视频在线观看一区二区| 日韩中文字幕在线视频播放| 欧美精品免费在线观看| 中文字幕在线观看日韩| 欧美性受xxxx白人性爽| 久久免费视频在线观看| 国产亚洲一区二区在线| 亚洲视频在线免费看| 久久久久久久久亚洲| 亚洲欧美在线一区| 中文字幕日韩欧美| 国产精品网红直播| 欧美高清理论片| 黄色成人av在线| 日韩中文综合网| 日韩av一区二区在线| 国产免费成人av| 韩国v欧美v日本v亚洲| 成人国产亚洲精品a区天堂华泰| 亚洲精品之草原avav久久| 日韩69视频在线观看| 久久精品99无色码中文字幕| 日韩成人激情在线| 日韩女优人人人人射在线视频| 国产主播喷水一区二区| 国产成人精品电影久久久| 日韩av一区二区在线| 日韩中文第一页| 亚洲va国产va天堂va久久| 色妞一区二区三区| 欧美理论在线观看| 亚洲精品成人免费| 亚洲成人精品久久久| 欧美激情a∨在线视频播放| 国产亚洲欧美另类中文| 人体精品一二三区| 在线观看国产精品日韩av| 亚洲专区国产精品| 国精产品一区一区三区有限在线| 日韩人在线观看| 成人在线激情视频| 久久久久国产一区二区三区| 日韩精品在线视频美女| 久久久久久久久久久免费| 国产一区红桃视频| 日韩电影大片中文字幕| 成人黄色片网站| 国产日本欧美一区二区三区在线| 日韩三级影视基地| 欧美激情日韩图片| 欧美日韩黄色大片| 国产裸体写真av一区二区| 国产精品久久久久久婷婷天堂| 69av在线播放| 国产欧美一区二区三区在线| 91精品综合久久久久久五月天| 综合网日日天干夜夜久久| 久久久久久久久久久久久久久久久久av| 中文字幕亚洲激情| 日韩av手机在线观看| 亚洲欧美日韩综合| 国产精品麻豆va在线播放| 91豆花精品一区| 欧美丰满片xxx777| 在线播放亚洲激情| 国产精品美女免费视频| 欧美老女人xx| 精品国产成人在线| 国产精品av在线播放| 国产国语刺激对白av不卡| 国产成人拍精品视频午夜网站| 日韩av电影中文字幕| 亚洲精品在线看| 欧美又大粗又爽又黄大片视频| 欧美性在线视频| 国产一区二区色| 欧洲亚洲女同hd| 中文字幕一区日韩电影| 国产一区二区三区在线观看网站| 欧美成人精品三级在线观看| 川上优av一区二区线观看| 国产精品扒开腿爽爽爽视频| 日韩免费在线免费观看| 日韩中文第一页| 激情亚洲一区二区三区四区| 国产精品久久久久久影视| 欧美成人精品激情在线观看| 国产女人18毛片水18精品| 91精品免费久久久久久久久| 久久免费精品视频| 精品香蕉一区二区三区| 91在线观看欧美日韩| 国产精品91久久| 中文字幕av一区中文字幕天堂| 亚洲国产精品小视频| 欧美特黄级在线| 国产视频久久久久久久| 国产成+人+综合+亚洲欧洲| 中文字幕国产日韩| 日韩av中文字幕在线播放| www.欧美精品一二三区| 国内精品久久久久久| 日韩av影视在线| 另类视频在线观看| 国产精品福利片| 久久综合88中文色鬼| 日韩av手机在线| 欧美日韩精品在线| 亚洲精品狠狠操| 最近2019中文字幕大全第二页| 国产视频精品va久久久久久| 亚洲精品欧美一区二区三区| 正在播放国产一区| 久久99精品久久久久久噜噜| 久久福利网址导航| 欧美在线观看www| 久久精品国产免费观看| 亚洲欧美三级在线| 欧美色道久久88综合亚洲精品| 九九视频这里只有精品| 91大神在线播放精品| 久久久亚洲福利精品午夜| 国产69精品久久久久久| 欧美激情va永久在线播放| 午夜精品在线观看| 亚洲自拍偷拍区| 国产精品久久久久久久久借妻| 国模精品视频一区二区三区| 岛国视频午夜一区免费在线观看| 欧美色道久久88综合亚洲精品| 欧美亚洲国产成人精品| 欧美大尺度激情区在线播放| 久久精品国产亚洲精品| 九九热99久久久国产盗摄| xxav国产精品美女主播| 日韩大片免费观看视频播放|