摘要:直到內(nèi)部的全部循環(huán)結(jié)束為止,才進入下一個元素,當循環(huán)結(jié)束時,內(nèi)部的節(jié)點都已經(jīng)生成好了。
自己實現(xiàn)虛擬 DOM 從 HTML 中提煉數(shù)據(jù)結(jié)構(gòu)
先來看下我們的 HTML
傅雷家書
讀家書,想付雷
從 HTML 中我們可以抽離出它的數(shù)據(jù)結(jié)構(gòu):
首先頁面中只需要一個根節(jié)點root,定義為:nodesDate數(shù)組
root內(nèi)有兩個子元素h1和span,數(shù)組有兩項,每項為內(nèi)容為tag和children
接下來內(nèi)部所有元素都是如此定義,直到遇到文本元素,將他定義為text
nodesDate = { tag:"div", children:[{ tag:"h1", children:[{ tag:"span", children:[{ tag:"#text", text:"傅雷家書" }] }] },{ tag:"span", children:[{ tag:"#text", text:"讀家書,想傅雷" }] }] }
用這種視野在看 HTML 的話,就不是單純的 HTML 了,而是一堆hash
從上面數(shù)據(jù)結(jié)構(gòu)中我們可以提煉出3個有用的屬性,分別是tag、children、text,那我們是不是可以定義一個方法,傳遞這三個參數(shù),就能滿足我們的需求呢?
構(gòu)造 HTML 方法想一下我們拿到這三個參數(shù)后,要干什么呢?
當然是在頁面中生成 DOM 元素??!
對,這三個參數(shù)是我們各自私有屬性,通過這三個屬性能生成各自的 DOM,生成 DOM 的方法是不是公用的呢?
所以可以用用構(gòu)造函數(shù)模式創(chuàng)建我們要的方法,(PS:之前講過new操作符的背后的邏輯,不理解的可移步:使用 new 操作符內(nèi)部到底在做什么)
function vNode(tag,children,text){ this.tag = tag this.children = children this.text = text } vNode.prototype.render = function(){ //如 tag 為文本的話,創(chuàng)建一個文本節(jié)點 if(this.tag === "#text"){ return document.createTextNode(this.text) // 返回文本 } //tag 不是文本的話,創(chuàng)建一個 DOM 節(jié)點,并且遍歷 children,每次遍歷都是調(diào)用自身的 render 方法 let element = document.createElement(this.tag) this.children.forEach((vChild)=> { element.appendChild(vChild.render()) //在遍歷 h1 時,沒有直接跳出,而是在其內(nèi)部不斷循環(huán)。直到 h1 內(nèi)部的 children 全部循環(huán)結(jié)束為止,才進入下一個元素 span,當 h1 循環(huán)結(jié)束時,h1 內(nèi)部的節(jié)點都已經(jīng)生成好了。 }) return element //返回節(jié)點 } 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 節(jié)點 root.appendChild(vNodes.render()) //這里只運行一次,把最終的 DOM 添加進頁面中實現(xiàn)增刪改
如果此時一個數(shù)據(jù)變動比如,按照以前的邏輯
root.innerText = "" root.appendChild(vNodes.render())
如果數(shù)據(jù)非常大,用這種方法根本沒啥意義,每一次改動 DOM 樹都要重新渲染一遍,造成性能低下,有什么好的方法可以實現(xiàn)呢?
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 //當循環(huán)走完,發(fā)現(xiàn)元素或者文本不一樣時,才走第三個邏輯 patchElement(parent.childNodes[index], newVNodes.children[i], oldVNodes.children[i], i) } } }
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/96998.html
摘要:前言是前端最受歡迎的框架之一,解讀其源碼的文章非常多,但是我想從另一個角度去解讀從零開始實現(xiàn)一個,從層面實現(xiàn)的大部分功能,在這個過程中去探索為什么有虛擬為什么這樣設(shè)計等問題。 前言 React是前端最受歡迎的框架之一,解讀其源碼的文章非常多,但是我想從另一個角度去解讀React:從零開始實現(xiàn)一個React,從API層面實現(xiàn)React的大部分功能,在這個過程中去探索為什么有虛擬DOM、d...
摘要:要構(gòu)建自己的虛擬,需要知道兩件事?,F(xiàn)在來看看如何處理上面描述的所有情況。代碼如下節(jié)點的替換首先,需要編寫一個函數(shù)來比較兩個節(jié)點舊節(jié)點和新節(jié)點,并告訴節(jié)點是否真的發(fā)生了變化??偨Y(jié)現(xiàn)在我們已經(jīng)編寫了虛擬實現(xiàn)及了解它的工作原理。 showImg(https://segmentfault.com/img/bVbmPue?w=2000&h=684); 要構(gòu)建自己的虛擬DOM,需要知道兩件事。你甚...
閱讀 3225·2021-11-24 09:39
閱讀 2950·2021-11-23 09:51
閱讀 903·2021-11-18 10:07
閱讀 3553·2021-10-11 10:57
閱讀 2765·2021-10-08 10:04
閱讀 3013·2021-09-26 10:11
閱讀 1062·2021-09-23 11:21
閱讀 2805·2019-08-29 17:28