摘要:會檢測出最大的靜態子樹不需要動態性的子樹并且從渲染函數中萃取出來。成功的水合作用。如果新節點不是克隆的,則表示呈現函數。方法解析在此虛擬的
Vitual DOM是一種虛擬dom技術,本質上是基于javascript實現的,相對于dom對象,javascript對象更簡單,處理速度更快,dom樹的結構,屬性信息都可以很容易的用javascript對象來表示:
let element={ tagName:"ul",//節點標簽名 props:{//dom的屬性,用一個對象存儲鍵值對 id:"list" }, children:[//該節點的子節點 {tagName:"li",props:{class:"item"},children:["aa"]}, {tagName:"li",props:{class:"item"},children:["bb"]}, {tagName:"li",props:{class:"item"},children:["cc"]} ] } 對應的html寫法是:
Virtual DOM并沒有完全實現DOM,Virtual DOM最主要的還是保留了Element之間的層次關系和一些基本屬性. 你給我一個數據,我根據這個數據生成一個全新的Virtual DOM,然后跟我上一次生成的Virtual DOM去 diff,得到一個Patch,然后把這個Patch打到瀏覽器的DOM上去。
我們可以通過javascript對象表示的樹結構來構建一棵真正的dom樹,當數據狀態發生變化時,可以直接修改這個javascript對象,接著對比修改后的javascript對象,記錄下需要對頁面做的dom操作,然后將其應用到真正的dom樹,實現視圖的更新,這個過程就是Virtual DOM的核心思想。
VNode的數據結構圖:
VNode生成最關鍵的點是通過render有2種生成方式,第一種是直接在vue對象的option中添加render字段。第二種是寫一個模板或指定一個el根元素,它會首先轉換成模板,經過html語法解析器生成一個ast抽象語法樹,對語法樹做優化,然后把語法樹轉換成代碼片段,最后通過代碼片段生成function添加到option的render字段中。
ast語法優的過程,主要做了2件事:
會檢測出靜態的class名和attributes,這樣它們在初始化渲染后就永遠不會再被比對了。
會檢測出最大的靜態子樹(不需要動態性的子樹)并且從渲染函數中萃取出來。這樣在每次重渲染時,它就會直接重用完全相同的vnode,同時跳過比對。
src/core/vdom/create-element.js const SIMPLE_NORMALIZE = 1 const ALWAYS_NORMALIZE = 2 function createElement (context, tag, data, children, normalizationType, alwaysNormalize) { // 兼容不傳data的情況 if (Array.isArray(data) || isPrimitive(data)) { normalizationType = children children = data data = undefined } // 如果alwaysNormalize是true // 那么normalizationType應該設置為常量ALWAYS_NORMALIZE的值 if (alwaysNormalize) normalizationType = ALWAYS_NORMALIZE // 調用_createElement創建虛擬節點 return _createElement(context, tag, data, children, normalizationType) } function _createElement (context, tag, data, children, normalizationType) { /** * 如果存在data.__ob__,說明data是被Observer觀察的數據 * 不能用作虛擬節點的data * 需要拋出警告,并返回一個空節點 * 被監控的data不能被用作vnode渲染的數據的原因是: * data在vnode渲染過程中可能會被改變,這樣會觸發監控,導致不符合預期的操作 */ if (data && data.__ob__) { process.env.NODE_ENV !== "production" && warn( `Avoid using observed data object as vnode data: ${JSON.stringify(data)} ` + "Always create fresh vnode data objects in each render!", context ) return createEmptyVNode() } // 當組件的is屬性被設置為一個falsy的值 // Vue將不會知道要把這個組件渲染成什么 // 所以渲染一個空節點 if (!tag) { return createEmptyVNode() } // 作用域插槽 if (Array.isArray(children) && typeof children[0] === "function") { data = data || {} data.scopedSlots = { default: children[0] } children.length = 0 } // 根據normalizationType的值,選擇不同的處理方法 if (normalizationType === ALWAYS_NORMALIZE) { children = normalizeChildren(children) } else if (normalizationType === SIMPLE_NORMALIZE) { children = simpleNormalizeChildren(children) } let vnode, ns // 如果標簽名是字符串類型 if (typeof tag === "string") { let Ctor // 獲取標簽名的命名空間 ns = config.getTagNamespace(tag) // 判斷是否為保留標簽 if (config.isReservedTag(tag)) { // 如果是保留標簽,就創建一個這樣的vnode vnode = new VNode( config.parsePlatformTagName(tag), data, children, undefined, undefined, context ) // 如果不是保留標簽,那么我們將嘗試從vm的components上查找是否有這個標簽的定義 } else if ((Ctor = resolveAsset(context.$options, "components", tag))) { // 如果找到了這個標簽的定義,就以此創建虛擬組件節點 vnode = createComponent(Ctor, data, context, children, tag) } else { // 兜底方案,正常創建一個vnode vnode = new VNode( tag, data, children, undefined, undefined, context ) } // 當tag不是字符串的時候,我們認為tag是組件的構造類 // 所以直接創建 } else { vnode = createComponent(tag, data, context, children) } // 如果有vnode if (vnode) { // 如果有namespace,就應用下namespace,然后返回vnode if (ns) applyNS(vnode, ns) return vnode // 否則,返回一個空節點 } else { return createEmptyVNode() } }
方法的功能是給一個Vnode對象對象添加若干個子Vnode,因為整個Virtual DOM是一種樹狀結構,每個節點都可能會有若干子節點。然后創建一個VNode對象,如果是一個reserved tag(比如html,head等一些合法的html標簽)則會創建普通的DOM VNode,如果是一個component tag(通過vue注冊的自定義component),則會創建Component VNode對象,它的VnodeComponentOptions不為Null.
創建好Vnode,下一步就是要把Virtual DOM渲染成真正的DOM,是通過patch來實現的,源碼如下:
src/core/vdom/patch.js return function patch (oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) { // oldVnoe:dom||當前vnode,vnode:vnoder=對象類型,hydration是否直接用服務端渲染的dom元素 if (isUndef(vnode)) { if (isDef(oldVnode)) invokeDestroyHook(oldVnode) return } let isInitialPatch = false const insertedVnodeQueue = [] if (isUndef(oldVnode)) { // 空掛載(可能是組件),創建新的根元素。 isInitialPatch = true createElm(vnode, insertedVnodeQueue, parentElm, refElm) } else { const isRealElement = isDef(oldVnode.nodeType) if (!isRealElement && sameVnode(oldVnode, vnode)) { // patch 現有的根節點 patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly) } else { if (isRealElement) { // 安裝到一個真實的元素。 // 檢查這是否是服務器渲染的內容,如果我們可以執行。 // 成功的水合作用。 if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) { oldVnode.removeAttribute(SSR_ATTR) hydrating = true } if (isTrue(hydrating)) { if (hydrate(oldVnode, vnode, insertedVnodeQueue)) { invokeInsertHook(vnode, insertedVnodeQueue, true) return oldVnode } else if (process.env.NODE_ENV !== "production") { warn( "The client-side rendered virtual DOM tree is not matching " + "server-rendered content. This is likely caused by incorrect " + "HTML markup, for example nesting block-level elements inside " + ", or missing
. Bailing hydration and performing " + "full client-side render." ) } } // 不是服務器呈現,就是水化失敗。創建一個空節點并替換它。 oldVnode = emptyNodeAt(oldVnode) } // 替換現有的元素 const oldElm = oldVnode.elm const parentElm = nodeOps.parentNode(oldElm) // create new node createElm( vnode, insertedVnodeQueue, // 極為罕見的邊緣情況:如果舊元素在a中,則不要插入。 // 離開過渡。只有結合過渡+時才會發生。 // keep-alive + HOCs. (#4590) oldElm._leaveCb ? null : parentElm, nodeOps.nextSibling(oldElm) ) // 遞歸地更新父占位符節點元素。 if (isDef(vnode.parent)) { let ancestor = vnode.parent const patchable = isPatchable(vnode) while (ancestor) { for (let i = 0; i < cbs.destroy.length; ++i) { cbs.destroy[i](ancestor) } ancestor.elm = vnode.elm if (patchable) { for (let i = 0; i < cbs.create.length; ++i) { cbs.create[i](emptyNode, ancestor) } // #6513 // 調用插入鉤子,這些鉤子可能已經被創建鉤子合并了。 // 例如使用“插入”鉤子的指令。 const insert = ancestor.data.hook.insert if (insert.merged) { // 從索引1開始,以避免重新調用組件掛起的鉤子。 for (let i = 1; i < insert.fns.length; i++) { insert.fns[i]() } } } else { registerRef(ancestor) } ancestor = ancestor.parent } } // destroy old node if (isDef(parentElm)) { removeVnodes(parentElm, [oldVnode], 0, 0) } else if (isDef(oldVnode.tag)) { invokeDestroyHook(oldVnode) } } } invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch) return vnode.elm }patch支持的3個參數,其中oldVnode是一個真實的DOM或者一個VNode對象,它表示當前的VNode,vnode是VNode對象類型,它表示待替換的VNode,hydration是bool類型,它表示是否直接使用服務器端渲染的DOM元素,下面流程圖表示patch的運行邏輯:
patch運行邏輯看上去比較復雜,有2個方法createElm和patchVnode是生成dom的關鍵,源碼如下:
/** * @param vnode根據vnode的數據結構創建真實的dom節點,如果vnode有children則會遍歷這些子節點,遞歸調用createElm方法, * @param insertedVnodeQueue記錄子節點創建順序的隊列,每創建一個dom元素就會往隊列中插入當前的vnode,當整個vnode對象全部轉換成為真實的dom 樹時,會依次調用這個隊列中vnode hook的insert方法 */ let inPre = 0 function createElm (vnode, insertedVnodeQueue, parentElm, refElm, nested) { vnode.isRootInsert = !nested // 過渡進入檢查 if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) { return } const data = vnode.data const children = vnode.children const tag = vnode.tag if (isDef(tag)) { if (process.env.NODE_ENV !== "production") { if (data && data.pre) { inPre++ } if ( !inPre && !vnode.ns && !( config.ignoredElements.length && config.ignoredElements.some(ignore => { return isRegExp(ignore) ? ignore.test(tag) : ignore === tag }) ) && config.isUnknownElement(tag) ) { warn( "Unknown custom element: <" + tag + "> - did you " + "register the component correctly? For recursive components, " + "make sure to provide the "name" option.", vnode.context ) } } vnode.elm = vnode.ns ? nodeOps.createElementNS(vnode.ns, tag) : nodeOps.createElement(tag, vnode) setScope(vnode) /* istanbul ignore if */ if (__WEEX__) { // in Weex, the default insertion order is parent-first. // List items can be optimized to use children-first insertion // with append="tree". const appendAsTree = isDef(data) && isTrue(data.appendAsTree) if (!appendAsTree) { if (isDef(data)) { invokeCreateHooks(vnode, insertedVnodeQueue) } insert(parentElm, vnode.elm, refElm) } createChildren(vnode, children, insertedVnodeQueue) if (appendAsTree) { if (isDef(data)) { invokeCreateHooks(vnode, insertedVnodeQueue) } insert(parentElm, vnode.elm, refElm) } } else { createChildren(vnode, children, insertedVnodeQueue) if (isDef(data)) { invokeCreateHooks(vnode, insertedVnodeQueue) } insert(parentElm, vnode.elm, refElm) } if (process.env.NODE_ENV !== "production" && data && data.pre) { inPre-- } } else if (isTrue(vnode.isComment)) { vnode.elm = nodeOps.createComment(vnode.text) insert(parentElm, vnode.elm, refElm) } else { vnode.elm = nodeOps.createTextNode(vnode.text) insert(parentElm, vnode.elm, refElm) } }方法會根據vnode的數據結構創建真實的DOM節點,如果vnode有children,則會遍歷這些子節點,遞歸調用createElm方法,InsertedVnodeQueue是記錄子節點創建順序的隊列,每創建一個DOM元素就會往這個隊列中插入當前的VNode,當整個VNode對象全部轉換成為真實的DOM樹時,會依次調用這個隊列中的VNode hook的insert方法。
/** * 比較新舊vnode節點,根據不同的狀態對dom做合理的更新操作(添加,移動,刪除)整個過程還會依次調用prepatch,update,postpatch等鉤子函數,在編譯階段生成的一些靜態子樹,在這個過程 * @param oldVnode 中由于不會改變而直接跳過比對,動態子樹在比較過程中比較核心的部分就是當新舊vnode同時存在children,通過updateChildren方法對子節點做更新, * @param vnode * @param insertedVnodeQueue * @param removeOnly */ function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) { if (oldVnode === vnode) { return } const elm = vnode.elm = oldVnode.elm if (isTrue(oldVnode.isAsyncPlaceholder)) { if (isDef(vnode.asyncFactory.resolved)) { hydrate(oldVnode.elm, vnode, insertedVnodeQueue) } else { vnode.isAsyncPlaceholder = true } return } // 用于靜態樹的重用元素。 // 注意,如果vnode是克隆的,我們只做這個。 // 如果新節點不是克隆的,則表示呈現函數。 // 由熱重加載api重新設置,我們需要進行適當的重新渲染。 if (isTrue(vnode.isStatic) && isTrue(oldVnode.isStatic) && vnode.key === oldVnode.key && (isTrue(vnode.isCloned) || isTrue(vnode.isOnce)) ) { vnode.componentInstance = oldVnode.componentInstance return } let i const data = vnode.data if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) { i(oldVnode, vnode) } const oldCh = oldVnode.children const ch = vnode.children if (isDef(data) && isPatchable(vnode)) { for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode) if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode) } if (isUndef(vnode.text)) { if (isDef(oldCh) && isDef(ch)) { if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly) } else if (isDef(ch)) { if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, "") addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue) } else if (isDef(oldCh)) { removeVnodes(elm, oldCh, 0, oldCh.length - 1) } else if (isDef(oldVnode.text)) { nodeOps.setTextContent(elm, "") } } else if (oldVnode.text !== vnode.text) { nodeOps.setTextContent(elm, vnode.text) } if (isDef(data)) { if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode) } }updateChildren方法解析在此:vue:虛擬DOM的patch
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/93054.html
相關文章
Vue.js-Render函數
摘要:函數通過參數來創建虛擬,結構精簡。其中,訪問的用法,使用場景集中在函數。使用代替模板功能在函數中,不再需要內置的指令,比如。方法時快速改變數組結構,返回一個新數組。 學習筆記:Render函數 Render函數 Vue2與Vue1最大的區別就在于Vue2使用了虛擬DOM來更新DOM節點,提升渲染性能。 Vue2與Vue1最大的區別就在于Vue2使用了虛擬DOM來更新DOM節點,提升...
虛擬Dom詳解 - (一)
摘要:為此也做了一些學習簡單的侃一侃虛擬到底是什么虛擬詳解二什么是虛擬虛擬首次產生是框架最先提出和使用的,其卓越的性能很快得到廣大開發者的認可,繼之后也在其核心引入了虛擬的概念。所謂的虛擬到底是什么也就是通過語言來描述一段代碼。 隨著Vue和React的風聲水起,伴隨著諸多框架的成長,虛擬DOM漸漸成了我們經常議論和討論的話題。什么是虛擬DOM,虛擬DOM是如何渲染的,那么Vue的虛擬Dom...
Vue中的虛擬DOM及diff算法
摘要:的算法是基于的實現,并在些基礎上作了很多的調整和改進。此時和之間的是新增的,調用,把這些虛擬全部插進的后邊,可以認為新節點先遍歷完。 虛擬dom 為什么出現:瀏覽器解析一個html大致分為五步:創建DOM tree –> 創建Style Rules -> 構建Render tree -> 布局Layout –> 繪制Painting。每次對真實dom進行操作的時候,瀏覽器都會從構建...
Svelte 前端框架探索
摘要:苗條的框架正是作者的初始目的,苗條包括代碼編寫量打包大小等等。是已經編譯后的組件有什么缺點是一個剛起步不久的前端框架,無論在維護人員還是社區上都是大大不如三大框架,這里列舉一下本人認為的存在的缺點。 Svelte 的作者也是 rollup 的作者 Rich Harris,前端界的輪子哥。sevlte 項目首次提交于 2016 年 11 月 16 日,目前版本是 3.6.1(2019-0...
vue常用知識點總結
摘要:這里借鑒了一下的處理方式,我們把單獨模塊的包裝成一個函數,提供一個全局的回調方法,加載完成時候再調用回調函數。 感謝本文引用鏈接的各位大佬們,小菜鳥我只是個搬運工 1.談一談你理解的vue是什么樣子的? vue是數據、視圖分離的一個框架,讓數據與視圖間不會發生直接聯系。MVVM 組件化:把整體拆分為各個可以復用的個體 數據驅動:通過數據變化直接影響bom展示,避免dom操作。 可以在...
發表評論
0條評論
BLUE
男|高級講師
TA的文章
閱讀更多
tensorflow2.4.0
閱讀 2418·2023-04-26 00:46
tensorflow函數
閱讀 581·2023-04-25 21:36
如何使用OPA實現多云策略和流程可移植性
閱讀 729·2021-11-24 10:19
#私藏項目實操分享# 什么是 SAP enhancement package
閱讀 2266·2021-11-23 09:51
hostxen,雙11,注冊贈送20元代金券,充值多送25%,VPS可選中國香港/洛杉磯/日本/新加
閱讀 1015·2021-10-21 09:39
教你如何在低成本、有限的時間里策劃裂變100社群_怎么做微信防封群
閱讀 830·2021-09-22 10:02
賦值運算符重載函數不加&為什么會出錯?
閱讀 1664·2021-09-03 10:29
前端每日實戰:54# 視頻演示如何用純 CSS 創作一副國際象棋
閱讀 2677·2019-08-30 15:53
閱讀需要支付1元查看