摘要:直到內部的全部循環結束為止,才進入下一個元素,當循環結束時,內部的節點都已經生成好了。
自己實現虛擬 DOM 從 HTML 中提煉數據結構
先來看下我們的 HTML
傅雷家書
讀家書,想付雷
從 HTML 中我們可以抽離出它的數據結構:
首先頁面中只需要一個根節點root,定義為:nodesDate數組
root內有兩個子元素h1和span,數組有兩項,每項為內容為tag和children
接下來內部所有元素都是如此定義,直到遇到文本元素,將他定義為text
nodesDate = { tag:"div", children:[{ tag:"h1", children:[{ tag:"span", children:[{ tag:"#text", text:"傅雷家書" }] }] },{ tag:"span", children:[{ tag:"#text", text:"讀家書,想傅雷" }] }] }
用這種視野在看 HTML 的話,就不是單純的 HTML 了,而是一堆hash
從上面數據結構中我們可以提煉出3個有用的屬性,分別是tag、children、text,那我們是不是可以定義一個方法,傳遞這三個參數,就能滿足我們的需求呢?
構造 HTML 方法想一下我們拿到這三個參數后,要干什么呢?
當然是在頁面中生成 DOM 元素啊!
對,這三個參數是我們各自私有屬性,通過這三個屬性能生成各自的 DOM,生成 DOM 的方法是不是公用的呢?
所以可以用用構造函數模式創建我們要的方法,(PS:之前講過new操作符的背后的邏輯,不理解的可移步:使用 new 操作符內部到底在做什么)
function vNode(tag,children,text){ this.tag = tag this.children = children this.text = text } vNode.prototype.render = function(){ //如 tag 為文本的話,創建一個文本節點 if(this.tag === "#text"){ return document.createTextNode(this.text) // 返回文本 } //tag 不是文本的話,創建一個 DOM 節點,并且遍歷 children,每次遍歷都是調用自身的 render 方法 let element = document.createElement(this.tag) this.children.forEach((vChild)=> { element.appendChild(vChild.render()) //在遍歷 h1 時,沒有直接跳出,而是在其內部不斷循環。直到 h1 內部的 children 全部循環結束為止,才進入下一個元素 span,當 h1 循環結束時,h1 內部的節點都已經生成好了。 }) return element //返回節點 } function v(tag,children,text){ //如果 chilren 為字符串,那么就把 children 賦值給 text,并把 children 初始化為 [],不然后面會報錯 if(typeof children === "string"){ text = children children = [] } return new vNode(tag,children,text) } //格式參見 nodesData,vNode 的實例化 let vNodes = v("div",[ v("h1",[ v("span",[ v("#text","傅雷家書")]) ]), v("span",[ v("#text","——傅敏")]) ]) const root = document.querySelector(".root") //獲取 root 節點 root.appendChild(vNodes.render()) //這里只運行一次,把最終的 DOM 添加進頁面中實現增刪改
如果此時一個數據變動比如,按照以前的邏輯
root.innerText = "" root.appendChild(vNodes.render())
如果數據非常大,用這種方法根本沒啥意義,每一次改動 DOM 樹都要重新渲染一遍,造成性能低下,有什么好的方法可以實現呢?
function patchElement(parent, newVNodes, oldVNodes, index = 0) { //如果沒有傳遞老的 VNodes,默認就是新的 if(!oldVNodes) { parent.appendChild(newVNodes.render()) } else if(!newVNodes) { parent.removeChild(parent.childNodes[index]) } else if(newVNodes.tag !== oldVNodes.tag || newVNodes.text !== oldVNodse.text) { //如果元素不一樣或者文本不一樣,走這邊 //當有走這邊時,newVNodes 是和 oldVNodes 不同的那個值,這里的 parent 是當前元素或文本的 parent //replaceChild(sp1,sp2),是將 sp2 換成 sp1 parent.replaceChild(newVNodes.render(), parent.childNodes[index]) } else { for(let i = 0; i < newVNodes.children.length || i < oldVNodes.children.length; i++) { //取值永遠是 newVNodes.length,除非不傳 newVNodes //這里 index 只有當 i 變化時,下一次才是 index 才等于 i 的值 //當 i = 0 時,這次的 parent.childNode[index],是下一次的 parent,所以這里要用 index //當循環走完,發現元素或者文本不一樣時,才走第三個邏輯 patchElement(parent.childNodes[index], newVNodes.children[i], oldVNodes.children[i], i) } } }
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/96998.html
摘要:前言是前端最受歡迎的框架之一,解讀其源碼的文章非常多,但是我想從另一個角度去解讀從零開始實現一個,從層面實現的大部分功能,在這個過程中去探索為什么有虛擬為什么這樣設計等問題。 前言 React是前端最受歡迎的框架之一,解讀其源碼的文章非常多,但是我想從另一個角度去解讀React:從零開始實現一個React,從API層面實現React的大部分功能,在這個過程中去探索為什么有虛擬DOM、d...
摘要:要構建自己的虛擬,需要知道兩件事。現在來看看如何處理上面描述的所有情況。代碼如下節點的替換首先,需要編寫一個函數來比較兩個節點舊節點和新節點,并告訴節點是否真的發生了變化。總結現在我們已經編寫了虛擬實現及了解它的工作原理。 showImg(https://segmentfault.com/img/bVbmPue?w=2000&h=684); 要構建自己的虛擬DOM,需要知道兩件事。你甚...
閱讀 3186·2021-11-24 09:39
閱讀 2923·2021-11-23 09:51
閱讀 887·2021-11-18 10:07
閱讀 3544·2021-10-11 10:57
閱讀 2740·2021-10-08 10:04
閱讀 2999·2021-09-26 10:11
閱讀 1046·2021-09-23 11:21
閱讀 2780·2019-08-29 17:28