摘要:記錄當前節(jié)點的標志這是當前節(jié)點的差異深度遍歷子節(jié)點對當前節(jié)點進行操作將差異的部分應(yīng)用到中這次的粗糙的基本已經(jīng)實現(xiàn)了,具體的情況更加復雜。
前言
目前廣為人知的React和Vue都采用了virtual-dom,Virtual DOM憑借其高效的diff算法,讓我們不再關(guān)心性能問題,可以隨心所欲的修改數(shù)據(jù)狀態(tài)。在實際開發(fā)中,我們并不需要關(guān)心Virtual DOM是如何實現(xiàn)的,但是理解Virtual DOM的實現(xiàn)原理確實有必要的。本文是參照https://github.com/livoras/si... DOM。
一、前端應(yīng)用狀態(tài)管理在日益復雜的前端應(yīng)用中,狀態(tài)管理是一個經(jīng)常被提及的話題,從早期的刀耕火種時代到j(luò)Query,再到現(xiàn)在流行的MVVM時代,狀態(tài)管理的形式發(fā)生了翻天覆地的變化,我們再也不用維護茫茫多的事件回調(diào)、監(jiān)聽來更新視圖,轉(zhuǎn)而使用使用雙向數(shù)據(jù)綁定,只需要維護相應(yīng)的數(shù)據(jù)狀態(tài),就可以自動更新視圖,極大提高開發(fā)效率。
但是,雙向數(shù)據(jù)綁定也并不是唯一的辦法,還有一個非常粗暴有效的方式:一旦數(shù)據(jù)發(fā)生變化,重新繪制整個視圖,也就是重新設(shè)置一下innerHTML。這樣的做法確實簡單、粗暴、有效,但是如果只是因為局部一個小的數(shù)據(jù)發(fā)生變化而更新整個視圖,性價比未免太低了,而且,像事件,獲取焦點的輸入框等,都需要重新處理。所以,對于小的應(yīng)用或者說局部的小視圖,這樣處理完全是可以的,但是面對復雜的大型應(yīng)用,這樣的做法不可取。所以我們可以采取用JavaScript的方法來模擬DOM樹,用新渲染的對象樹去和舊的樹進行對比,記錄下變化的變化,然后應(yīng)用到真實的DOM樹上,這樣我們只需要更改與原來視圖不同的地方,而不需要全部重新渲染一次。這就是virtual-DOM的優(yōu)勢
二、視圖渲染相對于DOM對象,原生的JavaScript對象處理得更快,而且簡單。DOM樹上的結(jié)構(gòu),屬性信息我們都能通過JavaScript進行表示出來,例如:
var element = { tagName: "ul", // 節(jié)點標簽名 props: { // dom的屬性鍵值對 id: "list" }, children: [ {tagName: "li", props: {class: "item"}, children: ["Item 1"]}, {tagName: "li", props: {class: "item"}, children: ["Item 2"]}, {tagName: "li", props: {class: "item"}, children: ["Item 3"]} ] }
那么在html渲染的結(jié)果就是:
既然能夠通過JavaScript表示DOM樹的信息,那么就可以通過使用JavaScript來構(gòu)建DOM樹。
然而光是構(gòu)建DOM樹,沒什么卵用,我們需要將JavaScript構(gòu)建的DOM樹渲染到真實的DOM樹上,用JavaScript表現(xiàn)一個dom一個節(jié)點非常簡單,我們只需要記錄他的節(jié)點類型,屬性鍵值對,子節(jié)點:
function Element(tagName, props, children) { this.tagName = tagName this.props = props this.children = children }
那么ul標簽我們就可以使用這種方式來表示
var ul = new Element("ul", {id: "list"}, [ {tagName: "li", props: {class: "item"}, children: ["Item 1"]}, {tagName: "li", props: {class: "item"}, children: ["Item 2"]}, {tagName: "li", props: {class: "item"}, children: ["Item 3"]} ])
說了這么多,他只是用JavaScript表示的一個結(jié)構(gòu),那該如何將他渲染到真實的DOM結(jié)構(gòu)中呢:
Element.prototype.render = function() { let el = document.createElement(this.tagName), // 節(jié)點名稱 props = this.props // 節(jié)點屬性 for (var propName in props) { propValue = props[propName] el.setAttribute(propName, propValue) } this.children.forEach((child) => { var childEl = (child instanceof Element) ? child.render() : document.createTextNode(child) el.appendChild(childEl) }) return el }
如果我們想將ul渲染到DOM結(jié)構(gòu)中,就只需要
ulRoot = ul.render() document.appendChild(ulRoot)
這樣就完成了ul到DOM的渲染,也就有了真正的DOM結(jié)構(gòu)
React的核心算法是diff算法(這里指的是優(yōu)化后的算法)我們來看看diff算法是如何實現(xiàn)的:
diff只會對相同顏色方框內(nèi)的DOM節(jié)點進行比較,即同一個父節(jié)點下的所有子節(jié)點。當發(fā)現(xiàn)節(jié)點不存在,則該節(jié)點和子節(jié)點會被完全刪除,不會做進一步的比較。
在實際的代碼中,會對新舊兩棵樹進行深度的遍歷,給每一個節(jié)點進行標記。然后在新舊兩棵樹的對比中,將不同的地方記錄下來。
// diff 算法,對比兩棵樹 function diff(oldTree, newTree) { var index = 0 // 當前節(jié)點的標志 var patches = {} // 記錄每個節(jié)點差異的地方 dfsWalk(oldTree, newTree, index, patches) return patches } function dfsWalk(oldNode, newNode, index, patches) { // 對比newNode和oldNode的差異地方進行記錄 patches[index] = [...] diffChildren(oldNode.children, newNode.children, index, patches) } function diffChildren(oldChildren, newChildren, index, patches) { let leftNode = null var currentNodeIndex = index oldChildren.forEach((child, i) => { var newChild = newChildren[i] currentNodeIndex = (leftNode && leftNode.count) // 計算節(jié)點的標記 ? currentNodeIndex + leftNode.count + 1 : currentNodeIndex + 1 dfsWalk(child, newChild, currentNodeIndex, patches) // 遍歷子節(jié)點 leftNode = child }) }
例如:
在圖中如果div有差異,標記為0,那么:
patches[0] = [{difference}, {difference}]
同理,有p是patches[1], ul是patches[3],以此類推
patches指的是差異變化,這些差異包括:1、節(jié)點類型的不同,2、節(jié)點類型相同,但是屬性值不同,文本內(nèi)容不同。所以有這么幾種類型:
var REPLACE = 0, // replace 替換 REORDER = 1, // reorder 父節(jié)點中子節(jié)點的操作 PROPS = 2, // props 屬性的變化 TEXT = 3 // text 文本內(nèi)容的變化
如果節(jié)點類型不同,就說明是需要替換,例如將div替換成section,就記錄下差異:
patches[0] = [{ type: REPLACE, node: newNode // section },{ type: PROPS, props: { id: "container" } }]四、將差異應(yīng)用到DOM樹上
在標題二中構(gòu)建了真正的DOM樹的信息,所以先對那一棵DOM樹進行深度優(yōu)先的遍歷,遍歷的時候同
patches對象進行對比,找到其中的差異,然后應(yīng)用到DOM操作中。
function patch(node, patches) { var walker = {index: 0} // 記錄當前節(jié)點的標志 dfsWalk(node, walker, patches) } function dfsWalk(node, walker, patches) { var currentPatches = patches[walker.index] // 這是當前節(jié)點的差異 var len = node.childNodes ? node.childNodes.length : 0 for (var i = 0; i < len; i++) { // 深度遍歷子節(jié)點 var child = node.childNodes[i] walker.index++ dfsWalk(child, walker, patches) } if (currentPatches) { applyPatches(node, currentPatches) // 對當前節(jié)點進行DOM操作 } } // 將差異的部分應(yīng)用到DOM中 function applyPatches(node, currentPatches) { currentPatches.forEach((currentPatch) => { switch (currentPatch.type) { case REPLACE: var newNode = (typeof currentPatch.node === "string") ? document.createTextNode(currentPatch.node) : currentPatch.node.render() node.parentNode.replaceChild(newNode, node) break; case REORDER: reorderChldren(node, currentPatch.moves) break case PROPS: setProps(node, currentPatch.props) break case TEXT: if (node.textContent) { node.textContent = currentPatch.content } else { node.nodeValue = currentPatch.content } break default: throw new Error("Unknown patch type " + currentPatch.type) } }) }
這次的粗糙的virtual-dom基本已經(jīng)實現(xiàn)了,具體的情況更加復雜。但這已經(jīng)足夠讓我們理解virtual-dom。
具體的帶解析的代碼已經(jīng)上傳到github
https://www.cnblogs.com/justa...
https://github.com/livoras/bl...
https://github.com/y8n/blog/i...
https://medium.com/@deathmood...
http://www.infoq.com/cn/artic...
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/90120.html
摘要:下面我們從前端基礎(chǔ)和底層原理開始講起。對于和這三個對應(yīng)于矢量圖位圖和圖的渲染來說,給前端開發(fā)帶來了重武器,很多小游戲也因此蓬勃發(fā)展。這篇文章受眾之大,后來被人重新整理并發(fā)布為,其中還包括中文版。 showImg(https://segmentfault.com/img/bVbjM5r?w=1142&h=640); 想閱讀更多優(yōu)質(zhì)文章請猛戳GitHub博客,一年百來篇優(yōu)質(zhì)文章等著你! 這...
摘要:在他的重學前端課程中提到到現(xiàn)在為止,前端工程師已經(jīng)成為研發(fā)體系中的重要崗位之一。大部分前端工程師的知識,其實都是來自于實踐和工作中零散的學習。一基礎(chǔ)前端工程師吃飯的家伙,深度廣度一樣都不能差。 開篇 前端開發(fā)是一個非常特殊的行業(yè),它的歷史實際上不是很長,但是知識之繁雜,技術(shù)迭代速度之快是其他技術(shù)所不能比擬的。 winter在他的《重學前端》課程中提到: 到現(xiàn)在為止,前端工程師已經(jīng)成為研...
摘要:在他的重學前端課程中提到到現(xiàn)在為止,前端工程師已經(jīng)成為研發(fā)體系中的重要崗位之一。大部分前端工程師的知識,其實都是來自于實踐和工作中零散的學習。一基礎(chǔ)前端工程師吃飯的家伙,深度廣度一樣都不能差。開篇 前端開發(fā)是一個非常特殊的行業(yè),它的歷史實際上不是很長,但是知識之繁雜,技術(shù)迭代速度之快是其他技術(shù)所不能比擬的。 winter在他的《重學前端》課程中提到: 到現(xiàn)在為止,前端工程師已經(jīng)成為研發(fā)體系...
閱讀 2901·2021-11-22 09:34
閱讀 1223·2021-11-19 09:40
閱讀 3349·2021-10-14 09:43
閱讀 3578·2021-09-23 11:22
閱讀 1612·2021-08-31 09:39
閱讀 895·2019-08-30 15:55
閱讀 1422·2019-08-30 15:54
閱讀 864·2019-08-30 15:53