目錄:
1 前言
2 對(duì)前端應(yīng)用狀態(tài)管理思考
3 Virtual DOM 算法
-
4 算法實(shí)現(xiàn)
- 4.1 步驟一:用JS對(duì)象模擬DOM樹(shù)
- 4.2 步驟二:比較兩棵虛擬DOM樹(shù)的差異
- 4.3 步驟三:把差異應(yīng)用到真正的DOM樹(shù)上
5 結(jié)語(yǔ)
6 References
1 前言
本文會(huì)在教你怎么用 300~400 行代碼實(shí)現(xiàn)一個(gè)基本的 Virtual DOM 算法净薛,并且嘗試盡量把 Virtual DOM 的算法思路闡述清楚汪榔。希望在閱讀本文后,能讓你深入理解 Virtual DOM 算法肃拜,給你現(xiàn)有前端的編程提供一些新的思考揍异。
本文所實(shí)現(xiàn)的完整代碼存放在 Github。
2 對(duì)前端應(yīng)用狀態(tài)管理的思考
假如現(xiàn)在你需要寫(xiě)一個(gè)像下面一樣的表格的應(yīng)用程序爆班,這個(gè)表格可以根據(jù)不同的字段進(jìn)行升序或者降序的展示衷掷。
這個(gè)應(yīng)用程序看起來(lái)很簡(jiǎn)單,你可以想出好幾種不同的方式來(lái)寫(xiě)柿菩。最容易想到的可能是戚嗅,在你的 JavaScript 代碼里面存儲(chǔ)這樣的數(shù)據(jù):
var sortKey = "new" // 排序的字段,新增(new)枢舶、取消(cancel)懦胞、凈關(guān)注(gain)、累積(cumulate)人數(shù)
var sortType = 1 // 升序還是逆序
var data = [{...}, {...}, {..}, ..] // 表格數(shù)據(jù)
用三個(gè)字段分別存儲(chǔ)當(dāng)前排序的字段凉泄、排序方向躏尉、還有表格數(shù)據(jù);然后給表格頭部加點(diǎn)擊事件:當(dāng)用戶點(diǎn)擊特定的字段的時(shí)候后众,根據(jù)上面幾個(gè)字段存儲(chǔ)的內(nèi)容來(lái)對(duì)內(nèi)容進(jìn)行排序胀糜,然后用 JS 或者 jQuery 操作 DOM,更新頁(yè)面的排序狀態(tài)(表頭的那幾個(gè)箭頭表示當(dāng)前排序狀態(tài)蒂誉,也需要更新)和表格內(nèi)容教藻。
這樣做會(huì)導(dǎo)致的后果就是,隨著應(yīng)用程序越來(lái)越復(fù)雜右锨,需要在JS里面維護(hù)的字段也越來(lái)越多括堤,需要監(jiān)聽(tīng)事件和在事件回調(diào)用更新頁(yè)面的DOM操作也越來(lái)越多,應(yīng)用程序會(huì)變得非常難維護(hù)。后來(lái)人們使用了 MVC悄窃、MVP 的架構(gòu)模式讥电,希望能從代碼組織方式來(lái)降低維護(hù)這種復(fù)雜應(yīng)用程序的難度。但是 MVC 架構(gòu)沒(méi)辦法減少你所維護(hù)的狀態(tài)轧抗,也沒(méi)有降低狀態(tài)更新你需要對(duì)頁(yè)面的更新操作(前端來(lái)說(shuō)就是DOM操作)恩敌,你需要操作的DOM還是需要操作,只是換了個(gè)地方鸦致。
既然狀態(tài)改變了要操作相應(yīng)的DOM元素潮剪,為什么不做一個(gè)東西可以讓視圖和狀態(tài)進(jìn)行綁定涣楷,狀態(tài)變更了視圖自動(dòng)變更分唾,就不用手動(dòng)更新頁(yè)面了。這就是后來(lái)人們想出了 MVVM 模式狮斗,只要在模版中聲明視圖組件是和什么狀態(tài)進(jìn)行綁定的绽乔,雙向綁定引擎就會(huì)在狀態(tài)更新的時(shí)候自動(dòng)更新視圖(關(guān)于MV*模式的內(nèi)容,可以看這篇介紹)碳褒。
MVVM 可以很好的降低我們維護(hù)狀態(tài) -> 視圖的復(fù)雜程度(大大減少代碼中的視圖更新邏輯)折砸。但是這不是唯一的辦法,還有一個(gè)非常直觀的方法沙峻,可以大大降低視圖更新的操作:一旦狀態(tài)發(fā)生了變化睦授,就用模版引擎重新渲染整個(gè)視圖,然后用新的視圖更換掉舊的視圖摔寨。就像上面的表格去枷,當(dāng)用戶點(diǎn)擊的時(shí)候,還是在JS里面更新?tīng)顟B(tài)是复,但是頁(yè)面更新就不用手動(dòng)操作 DOM 了删顶,直接把整個(gè)表格用模版引擎重新渲染一遍,然后設(shè)置一下innerHTML
就完事了淑廊。
聽(tīng)到這樣的做法逗余,經(jīng)驗(yàn)豐富的你一定第一時(shí)間意識(shí)這樣的做法會(huì)導(dǎo)致很多的問(wèn)題。最大的問(wèn)題就是這樣做會(huì)很慢季惩,因?yàn)榧词挂粋€(gè)小小的狀態(tài)變更都要重新構(gòu)造整棵 DOM录粱,性價(jià)比太低;而且這樣做的話画拾,input
和textarea
的會(huì)失去原有的焦點(diǎn)关摇。最后的結(jié)論會(huì)是:對(duì)于局部的小視圖的更新,沒(méi)有問(wèn)題(Backbone就是這么干的)碾阁;但是對(duì)于大型視圖输虱,如全局應(yīng)用狀態(tài)變更的時(shí)候,需要更新頁(yè)面較多局部視圖的時(shí)候脂凶,這樣的做法不可取宪睹。
但是這里要明白和記住這種做法愁茁,因?yàn)楹竺婺銜?huì)發(fā)現(xiàn),其實(shí) Virtual DOM 就是這么做的亭病,只是加了一些特別的步驟來(lái)避免了整棵 DOM 樹(shù)變更鹅很。
另外一點(diǎn)需要注意的就是,上面提供的幾種方法罪帖,其實(shí)都在解決同一個(gè)問(wèn)題:維護(hù)狀態(tài)促煮,更新視圖。在一般的應(yīng)用當(dāng)中整袁,如果能夠很好方案來(lái)應(yīng)對(duì)這個(gè)問(wèn)題菠齿,那么就幾乎降低了大部分復(fù)雜性。
3 Virtual DOM算法
DOM是很慢的坐昙。如果我們把一個(gè)簡(jiǎn)單的div
元素的屬性都打印出來(lái)绳匀,你會(huì)看到:
而這僅僅是第一層。真正的 DOM 元素非常龐大炸客,這是因?yàn)闃?biāo)準(zhǔn)就是這么設(shè)計(jì)的疾棵。而且操作它們的時(shí)候你要小心翼翼,輕微的觸碰可能就會(huì)導(dǎo)致頁(yè)面重排痹仙,這可是殺死性能的罪魁禍?zhǔn)住?/p>
相對(duì)于 DOM 對(duì)象是尔,原生的 JavaScript 對(duì)象處理起來(lái)更快,而且更簡(jiǎn)單开仰。DOM 樹(shù)上的結(jié)構(gòu)拟枚、屬性信息我們都可以很容易地用 JavaScript 對(duì)象表示出來(lái):
var element = {
tagName: 'ul', // 節(jié)點(diǎn)標(biāo)簽名
props: { // DOM的屬性,用一個(gè)對(duì)象存儲(chǔ)鍵值對(duì)
id: 'list'
},
children: [ // 該節(jié)點(diǎn)的子節(jié)點(diǎn)
{tagName: 'li', props: {class: 'item'}, children: ["Item 1"]},
{tagName: 'li', props: {class: 'item'}, children: ["Item 2"]},
{tagName: 'li', props: {class: 'item'}, children: ["Item 3"]},
]
}
上面對(duì)應(yīng)的HTML寫(xiě)法是:
<ul id='list'>
<li class='item'>Item 1</li>
<li class='item'>Item 2</li>
<li class='item'>Item 3</li>
</ul>
既然原來(lái) DOM 樹(shù)的信息都可以用 JavaScript 對(duì)象來(lái)表示抖所,反過(guò)來(lái)警儒,你就可以根據(jù)這個(gè)用 JavaScript 對(duì)象表示的樹(shù)結(jié)構(gòu)來(lái)構(gòu)建一棵真正的DOM樹(shù)辽狈。
之前的章節(jié)所說(shuō)的寻定,狀態(tài)變更->重新渲染整個(gè)視圖的方式可以稍微修改一下:用 JavaScript 對(duì)象表示 DOM 信息和結(jié)構(gòu)竭缝,當(dāng)狀態(tài)變更的時(shí)候,重新渲染這個(gè) JavaScript 的對(duì)象結(jié)構(gòu)傻粘。當(dāng)然這樣做其實(shí)沒(méi)什么卵用每窖,因?yàn)檎嬲捻?yè)面其實(shí)沒(méi)有改變。
但是可以用新渲染的對(duì)象樹(shù)去和舊的樹(shù)進(jìn)行對(duì)比弦悉,記錄這兩棵樹(shù)差異窒典。記錄下來(lái)的不同就是我們需要對(duì)頁(yè)面真正的 DOM 操作,然后把它們應(yīng)用在真正的 DOM 樹(shù)上稽莉,頁(yè)面就變更了瀑志。這樣就可以做到:視圖的結(jié)構(gòu)確實(shí)是整個(gè)全新渲染了,但是最后操作DOM的時(shí)候確實(shí)只變更有不同的地方。
這就是所謂的 Virtual DOM 算法劈猪。包括幾個(gè)步驟:
- 用 JavaScript 對(duì)象結(jié)構(gòu)表示 DOM 樹(shù)的結(jié)構(gòu)昧甘;然后用這個(gè)樹(shù)構(gòu)建一個(gè)真正的 DOM 樹(shù),插到文檔當(dāng)中
- 當(dāng)狀態(tài)變更的時(shí)候战得,重新構(gòu)造一棵新的對(duì)象樹(shù)充边。然后用新的樹(shù)和舊的樹(shù)進(jìn)行比較,記錄兩棵樹(shù)差異
- 把2所記錄的差異應(yīng)用到步驟1所構(gòu)建的真正的DOM樹(shù)上常侦,視圖就更新了
Virtual DOM 本質(zhì)上就是在 JS 和 DOM 之間做了一個(gè)緩存浇冰。可以類比 CPU 和硬盤(pán)聋亡,既然硬盤(pán)這么慢肘习,我們就在它們之間加個(gè)緩存:既然 DOM 這么慢,我們就在它們 JS 和 DOM 之間加個(gè)緩存杀捻。CPU(JS)只操作內(nèi)存(Virtual DOM)井厌,最后的時(shí)候再把變更寫(xiě)入硬盤(pán)(DOM)蚓庭。
4 算法實(shí)現(xiàn)
4.1 步驟一:用JS對(duì)象模擬DOM樹(shù)
用 JavaScript 來(lái)表示一個(gè) DOM 節(jié)點(diǎn)是很簡(jiǎn)單的事情致讥,你只需要記錄它的節(jié)點(diǎn)類型、屬性器赞,還有子節(jié)點(diǎn):
element.js
function Element (tagName, props, children) {
this.tagName = tagName
this.props = props
this.children = children
}
module.exports = function (tagName, props, children) {
return new Element(tagName, props, children)
}
例如上面的 DOM 結(jié)構(gòu)就可以簡(jiǎn)單的表示:
var el = require('./element')
var ul = el('ul', {id: 'list'}, [
el('li', {class: 'item'}, ['Item 1']),
el('li', {class: 'item'}, ['Item 2']),
el('li', {class: 'item'}, ['Item 3'])
])
現(xiàn)在ul
只是一個(gè) JavaScript 對(duì)象表示的 DOM 結(jié)構(gòu)垢袱,頁(yè)面上并沒(méi)有這個(gè)結(jié)構(gòu)。我們可以根據(jù)這個(gè)ul
構(gòu)建真正的<ul>
:
Element.prototype.render = function () {
var el = document.createElement(this.tagName) // 根據(jù)tagName構(gòu)建
var props = this.props
for (var propName in props) { // 設(shè)置節(jié)點(diǎn)的DOM屬性
var propValue = props[propName]
el.setAttribute(propName, propValue)
}
var children = this.children || []
children.forEach(function (child) {
var childEl = (child instanceof Element)
? child.render() // 如果子節(jié)點(diǎn)也是虛擬DOM港柜,遞歸構(gòu)建DOM節(jié)點(diǎn)
: document.createTextNode(child) // 如果字符串请契,只構(gòu)建文本節(jié)點(diǎn)
el.appendChild(childEl)
})
return el
}
render
方法會(huì)根據(jù)tagName
構(gòu)建一個(gè)真正的DOM節(jié)點(diǎn),然后設(shè)置這個(gè)節(jié)點(diǎn)的屬性夏醉,最后遞歸地把自己的子節(jié)點(diǎn)也構(gòu)建起來(lái)爽锥。所以只需要:
var ulRoot = ul.render()
document.body.appendChild(ulRoot)
上面的ulRoot
是真正的DOM節(jié)點(diǎn),把它塞入文檔中畔柔,這樣body
里面就有了真正的<ul>
的DOM結(jié)構(gòu):
<ul id='list'>
<li class='item'>Item 1</li>
<li class='item'>Item 2</li>
<li class='item'>Item 3</li>
</ul>
完整代碼可見(jiàn) element.js氯夷。
4.2 步驟二:比較兩棵虛擬DOM樹(shù)的差異
正如你所預(yù)料的,比較兩棵DOM樹(shù)的差異是 Virtual DOM 算法最核心的部分靶擦,這也是所謂的 Virtual DOM 的 diff 算法腮考。兩個(gè)樹(shù)的完全的 diff 算法是一個(gè)時(shí)間復(fù)雜度為 O(n^3) 的問(wèn)題。但是在前端當(dāng)中玄捕,你很少會(huì)跨越層級(jí)地移動(dòng)DOM元素踩蔚。所以 Virtual DOM 只會(huì)對(duì)同一個(gè)層級(jí)的元素進(jìn)行對(duì)比:
上面的div
只會(huì)和同一層級(jí)的div
對(duì)比,第二層級(jí)的只會(huì)跟第二層級(jí)對(duì)比枚粘。這樣算法復(fù)雜度就可以達(dá)到 O(n)馅闽。
4.2.1 深度優(yōu)先遍歷,記錄差異
在實(shí)際的代碼中,會(huì)對(duì)新舊兩棵樹(shù)進(jìn)行一個(gè)深度優(yōu)先的遍歷福也,這樣每個(gè)節(jié)點(diǎn)都會(huì)有一個(gè)唯一的標(biāo)記:
在深度優(yōu)先遍歷的時(shí)候孝冒,每遍歷到一個(gè)節(jié)點(diǎn)就把該節(jié)點(diǎn)和新的的樹(shù)進(jìn)行對(duì)比。如果有差異的話就記錄到一個(gè)對(duì)象里面拟杉。
// diff 函數(shù)庄涡,對(duì)比兩棵樹(shù)
function diff (oldTree, newTree) {
var index = 0 // 當(dāng)前節(jié)點(diǎn)的標(biāo)志
var patches = {} // 用來(lái)記錄每個(gè)節(jié)點(diǎn)差異的對(duì)象
dfsWalk(oldTree, newTree, index, patches)
return patches
}
// 對(duì)兩棵樹(shù)進(jìn)行深度優(yōu)先遍歷
function dfsWalk (oldNode, newNode, index, patches) {
// 對(duì)比oldNode和newNode的不同,記錄下來(lái)
patches[index] = [...]
diffChildren(oldNode.children, newNode.children, index, patches)
}
// 遍歷子節(jié)點(diǎn)
function diffChildren (oldChildren, newChildren, index, patches) {
var leftNode = null
var currentNodeIndex = index
oldChildren.forEach(function (child, i) {
var newChild = newChildren[i]
currentNodeIndex = (leftNode && leftNode.count) // 計(jì)算節(jié)點(diǎn)的標(biāo)識(shí)
? currentNodeIndex + leftNode.count + 1
: currentNodeIndex + 1
dfsWalk(child, newChild, currentNodeIndex, patches) // 深度遍歷子節(jié)點(diǎn)
leftNode = child
})
}
例如搬设,上面的div
和新的div
有差異穴店,當(dāng)前的標(biāo)記是0,那么:
patches[0] = [{difference}, {difference}, ...] // 用數(shù)組存儲(chǔ)新舊節(jié)點(diǎn)的不同
同理p
是patches[1]
拿穴,ul
是patches[3]
泣洞,類推。
4.2.2 差異類型
上面說(shuō)的節(jié)點(diǎn)的差異指的是什么呢默色?對(duì) DOM 操作可能會(huì):
- 替換掉原來(lái)的節(jié)點(diǎn)球凰,例如把上面的
div
換成了section
- 移動(dòng)、刪除腿宰、新增子節(jié)點(diǎn)呕诉,例如上面
div
的子節(jié)點(diǎn),把p
和ul
順序互換 - 修改了節(jié)點(diǎn)的屬性
- 對(duì)于文本節(jié)點(diǎn)吃度,文本內(nèi)容可能會(huì)改變甩挫。例如修改上面的文本節(jié)點(diǎn)2內(nèi)容為
Virtual DOM 2
。
所以我們定義了幾種差異類型:
var REPLACE = 0
var REORDER = 1
var PROPS = 2
var TEXT = 3
對(duì)于節(jié)點(diǎn)替換椿每,很簡(jiǎn)單伊者。判斷新舊節(jié)點(diǎn)的tagName
和是不是一樣的,如果不一樣的說(shuō)明需要替換掉间护。如div
換成section
亦渗,就記錄下:
patches[0] = [{
type: REPALCE,
node: newNode // el('section', props, children)
}]
如果給div
新增了屬性id
為container
,就記錄下:
patches[0] = [{
type: REPALCE,
node: newNode // el('section', props, children)
}, {
type: PROPS,
props: {
id: "container"
}
}]
如果是文本節(jié)點(diǎn)汁尺,如上面的文本節(jié)點(diǎn)2法精,就記錄下:
patches[2] = [{
type: TEXT,
content: "Virtual DOM2"
}]
那如果把我div
的子節(jié)點(diǎn)重新排序呢?例如p, ul, div
的順序換成了div, p, ul
均函。這個(gè)該怎么對(duì)比亿虽?如果按照同層級(jí)進(jìn)行順序?qū)Ρ鹊脑挘鼈兌紩?huì)被替換掉苞也。如p
和div
的tagName
不同洛勉,p
會(huì)被div
所替代。最終如迟,三個(gè)節(jié)點(diǎn)都會(huì)被替換收毫,這樣DOM開(kāi)銷就非常大攻走。而實(shí)際上是不需要替換節(jié)點(diǎn),而只需要經(jīng)過(guò)節(jié)點(diǎn)移動(dòng)就可以達(dá)到此再,我們只需知道怎么進(jìn)行移動(dòng)昔搂。
這牽涉到兩個(gè)列表的對(duì)比算法,需要另外起一個(gè)小節(jié)來(lái)討論输拇。
4.2.3 列表對(duì)比算法
假設(shè)現(xiàn)在可以英文字母唯一地標(biāo)識(shí)每一個(gè)子節(jié)點(diǎn):
舊的節(jié)點(diǎn)順序:
a b c d e f g h i
現(xiàn)在對(duì)節(jié)點(diǎn)進(jìn)行了刪除摘符、插入、移動(dòng)的操作策吠。新增j
節(jié)點(diǎn)逛裤,刪除e
節(jié)點(diǎn),移動(dòng)h
節(jié)點(diǎn):
新的節(jié)點(diǎn)順序:
a b c h d f g i j
現(xiàn)在知道了新舊的順序猴抹,求最小的插入带族、刪除操作(移動(dòng)可以看成是刪除和插入操作的結(jié)合)。這個(gè)問(wèn)題抽象出來(lái)其實(shí)是字符串的最小編輯距離問(wèn)題(Edition Distance)蟀给,最常見(jiàn)的解決算法是 Levenshtein Distance蝙砌,通過(guò)動(dòng)態(tài)規(guī)劃求解,時(shí)間復(fù)雜度為 O(M * N)跋理。但是我們并不需要真的達(dá)到最小的操作择克,我們只需要優(yōu)化一些比較常見(jiàn)的移動(dòng)情況,犧牲一定DOM操作薪介,讓算法時(shí)間復(fù)雜度達(dá)到線性的(O(max(M, N))祠饺。具體算法細(xì)節(jié)比較多越驻,這里不累述汁政,有興趣可以參考代碼。
我們能夠獲取到某個(gè)父節(jié)點(diǎn)的子節(jié)點(diǎn)的操作缀旁,就可以記錄下來(lái):
patches[0] = [{
type: REORDER,
moves: [{remove or insert}, {remove or insert}, ...]
}]
但是要注意的是记劈,因?yàn)?code>tagName是可重復(fù)的,不能用這個(gè)來(lái)進(jìn)行對(duì)比并巍。所以需要給子節(jié)點(diǎn)加上唯一標(biāo)識(shí)key
目木,列表對(duì)比的時(shí)候,使用key
進(jìn)行對(duì)比懊渡,這樣才能復(fù)用老的 DOM 樹(shù)上的節(jié)點(diǎn)刽射。
這樣,我們就可以通過(guò)深度優(yōu)先遍歷兩棵樹(shù)剃执,每層的節(jié)點(diǎn)進(jìn)行對(duì)比誓禁,記錄下每個(gè)節(jié)點(diǎn)的差異了。完整 diff 算法代碼可見(jiàn) diff.js肾档。
4.3 步驟三:把差異應(yīng)用到真正的DOM樹(shù)上
因?yàn)椴襟E一所構(gòu)建的 JavaScript 對(duì)象樹(shù)和render
出來(lái)真正的DOM樹(shù)的信息摹恰、結(jié)構(gòu)是一樣的辫继。所以我們可以對(duì)那棵DOM樹(shù)也進(jìn)行深度優(yōu)先的遍歷,遍歷的時(shí)候從步驟二生成的patches
對(duì)象中找出當(dāng)前遍歷的節(jié)點(diǎn)差異俗慈,然后進(jìn)行 DOM 操作姑宽。
function patch (node, patches) {
var walker = {index: 0}
dfsWalk(node, walker, patches)
}
function dfsWalk (node, walker, patches) {
var currentPatches = patches[walker.index] // 從patches拿出當(dāng)前節(jié)點(diǎn)的差異
var len = node.childNodes
? node.childNodes.length
: 0
for (var i = 0; i < len; i++) { // 深度遍歷子節(jié)點(diǎn)
var child = node.childNodes[i]
walker.index++
dfsWalk(child, walker, patches)
}
if (currentPatches) {
applyPatches(node, currentPatches) // 對(duì)當(dāng)前節(jié)點(diǎn)進(jìn)行DOM操作
}
}
applyPatches,根據(jù)不同類型的差異對(duì)當(dāng)前節(jié)點(diǎn)進(jìn)行 DOM 操作:
function applyPatches (node, currentPatches) {
currentPatches.forEach(function (currentPatch) {
switch (currentPatch.type) {
case REPLACE:
node.parentNode.replaceChild(currentPatch.node.render(), node)
break
case REORDER:
reorderChildren(node, currentPatch.moves)
break
case PROPS:
setProps(node, currentPatch.props)
break
case TEXT:
node.textContent = currentPatch.content
break
default:
throw new Error('Unknown patch type ' + currentPatch.type)
}
})
}
完整代碼可見(jiàn) patch.js闺阱。
5 結(jié)語(yǔ)
Virtual DOM 算法主要是實(shí)現(xiàn)上面步驟的三個(gè)函數(shù):element炮车,diff,patch酣溃。然后就可以實(shí)際的進(jìn)行使用:
// 1. 構(gòu)建虛擬DOM
var tree = el('div', {'id': 'container'}, [
el('h1', {style: 'color: blue'}, ['simple virtal dom']),
el('p', ['Hello, virtual-dom']),
el('ul', [el('li')])
])
// 2. 通過(guò)虛擬DOM構(gòu)建真正的DOM
var root = tree.render()
document.body.appendChild(root)
// 3. 生成新的虛擬DOM
var newTree = el('div', {'id': 'container'}, [
el('h1', {style: 'color: red'}, ['simple virtal dom']),
el('p', ['Hello, virtual-dom']),
el('ul', [el('li'), el('li')])
])
// 4. 比較兩棵虛擬DOM樹(shù)的不同
var patches = diff(tree, newTree)
// 5. 在真正的DOM元素上應(yīng)用變更
patch(root, patches)
當(dāng)然這是非常粗糙的實(shí)踐示血,實(shí)際中還需要處理事件監(jiān)聽(tīng)等;生成虛擬 DOM 的時(shí)候也可以加入 JSX 語(yǔ)法救拉。這些事情都做了的話难审,就可以構(gòu)造一個(gè)簡(jiǎn)單的ReactJS了。
本文所實(shí)現(xiàn)的完整代碼存放在 Github亿絮,僅供學(xué)習(xí)告喊。
6 References
https://github.com/Matt-Esch/virtual-dom/blob/master/vtree/diff.js
作者:戴嘉華
原文鏈接:https://github.com/livoras/blog/issues/13
掃描下方二維碼,關(guān)注微信公眾號(hào):H5開(kāi)講啦派昧,獲取更多學(xué)習(xí)資料
公眾號(hào)回臺(tái)回復(fù)比特幣黔姜、以太坊有驚喜