亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

vitual-dom原理與簡(jiǎn)單實(shí)現(xiàn)

Yangder / 3521人閱讀

摘要:記錄當(dāng)前節(jié)點(diǎn)的標(biāo)志這是當(dāng)前節(jié)點(diǎn)的差異深度遍歷子節(jié)點(diǎn)對(duì)當(dāng)前節(jié)點(diǎn)進(jìn)行操作將差異的部分應(yīng)用到中這次的粗糙的基本已經(jīng)實(shí)現(xiàn)了,具體的情況更加復(fù)雜。

前言

目前廣為人知的React和Vue都采用了virtual-dom,Virtual DOM憑借其高效的diff算法,讓我們不再關(guān)心性能問(wèn)題,可以隨心所欲的修改數(shù)據(jù)狀態(tài)。在實(shí)際開(kāi)發(fā)中,我們并不需要關(guān)心Virtual DOM是如何實(shí)現(xiàn)的,但是理解Virtual DOM的實(shí)現(xiàn)原理確實(shí)有必要的。本文是參照https://github.com/livoras/si... DOM。

一、前端應(yīng)用狀態(tài)管理

在日益復(fù)雜的前端應(yīng)用中,狀態(tài)管理是一個(gè)經(jīng)常被提及的話題,從早期的刀耕火種時(shí)代到j(luò)Query,再到現(xiàn)在流行的MVVM時(shí)代,狀態(tài)管理的形式發(fā)生了翻天覆地的變化,我們?cè)僖膊挥镁S護(hù)茫茫多的事件回調(diào)、監(jiān)聽(tīng)來(lái)更新視圖,轉(zhuǎn)而使用使用雙向數(shù)據(jù)綁定,只需要維護(hù)相應(yīng)的數(shù)據(jù)狀態(tài),就可以自動(dòng)更新視圖,極大提高開(kāi)發(fā)效率。

但是,雙向數(shù)據(jù)綁定也并不是唯一的辦法,還有一個(gè)非常粗暴有效的方式:一旦數(shù)據(jù)發(fā)生變化,重新繪制整個(gè)視圖,也就是重新設(shè)置一下innerHTML。這樣的做法確實(shí)簡(jiǎn)單、粗暴、有效,但是如果只是因?yàn)榫植恳粋€(gè)小的數(shù)據(jù)發(fā)生變化而更新整個(gè)視圖,性價(jià)比未免太低了,而且,像事件,獲取焦點(diǎn)的輸入框等,都需要重新處理。所以,對(duì)于小的應(yīng)用或者說(shuō)局部的小視圖,這樣處理完全是可以的,但是面對(duì)復(fù)雜的大型應(yīng)用,這樣的做法不可取。所以我們可以采取用JavaScript的方法來(lái)模擬DOM樹(shù),用新渲染的對(duì)象樹(shù)去和舊的樹(shù)進(jìn)行對(duì)比,記錄下變化的變化,然后應(yīng)用到真實(shí)的DOM樹(shù)上,這樣我們只需要更改與原來(lái)視圖不同的地方,而不需要全部重新渲染一次。這就是virtual-DOM的優(yōu)勢(shì)

二、視圖渲染

相對(duì)于DOM對(duì)象,原生的JavaScript對(duì)象處理得更快,而且簡(jiǎn)單。DOM樹(shù)上的結(jié)構(gòu),屬性信息我們都能通過(guò)JavaScript進(jìn)行表示出來(lái),例如:

var element = {
    tagName: "ul", // 節(jié)點(diǎn)標(biāo)簽名
    props: { // dom的屬性鍵值對(duì)
        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é)果就是:

  • Item 1
  • Item 2
  • Item 3

既然能夠通過(guò)JavaScript表示DOM樹(shù)的信息,那么就可以通過(guò)使用JavaScript來(lái)構(gòu)建DOM樹(shù)。

然而光是構(gòu)建DOM樹(shù),沒(méi)什么卵用,我們需要將JavaScript構(gòu)建的DOM樹(shù)渲染到真實(shí)的DOM樹(shù)上,用JavaScript表現(xiàn)一個(gè)dom一個(gè)節(jié)點(diǎn)非常簡(jiǎn)單,我們只需要記錄他的節(jié)點(diǎn)類型,屬性鍵值對(duì),子節(jié)點(diǎn):

function Element(tagName, props, children) {
    this.tagName = tagName
    this.props = props
    this.children = children
}

那么ul標(biāo)簽我們就可以使用這種方式來(lái)表示

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"]}
])

說(shuō)了這么多,他只是用JavaScript表示的一個(gè)結(jié)構(gòu),那該如何將他渲染到真實(shí)的DOM結(jié)構(gòu)中呢:

Element.prototype.render = function() {
    let el = document.createElement(this.tagName), // 節(jié)點(diǎn)名稱
        props = this.props // 節(jié)點(diǎn)屬性

    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)

  • Item 1
  • Item 2
  • Item 3
三、比較虛擬DOM樹(shù)的差異

React的核心算法是diff算法(這里指的是優(yōu)化后的算法)我們來(lái)看看diff算法是如何實(shí)現(xiàn)的:

diff只會(huì)對(duì)相同顏色方框內(nèi)的DOM節(jié)點(diǎn)進(jìn)行比較,即同一個(gè)父節(jié)點(diǎn)下的所有子節(jié)點(diǎn)。當(dāng)發(fā)現(xiàn)節(jié)點(diǎn)不存在,則該節(jié)點(diǎn)和子節(jié)點(diǎn)會(huì)被完全刪除,不會(huì)做進(jìn)一步的比較。

在實(shí)際的代碼中,會(huì)對(duì)新舊兩棵樹(shù)進(jìn)行深度的遍歷,給每一個(gè)節(jié)點(diǎn)進(jìn)行標(biāo)記。然后在新舊兩棵樹(shù)的對(duì)比中,將不同的地方記錄下來(lái)。

// diff 算法,對(duì)比兩棵樹(shù)
function diff(oldTree, newTree) {
    var index = 0   // 當(dāng)前節(jié)點(diǎn)的標(biāo)志
    var patches = {} // 記錄每個(gè)節(jié)點(diǎn)差異的地方
    dfsWalk(oldTree, newTree, index, patches)
    return patches
}
function dfsWalk(oldNode, newNode, index, patches) {
    // 對(duì)比newNode和oldNode的差異地方進(jìn)行記錄
    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) // 計(jì)算節(jié)點(diǎn)的標(biāo)記
                ? currentNodeIndex + leftNode.count + 1
                : currentNodeIndex + 1
        dfsWalk(child, newChild, currentNodeIndex, patches) // 遍歷子節(jié)點(diǎn)
        leftNode = child
    })
}

例如:

在圖中如果div有差異,標(biāo)記為0,那么:

patches[0] = [{difference}, {difference}]

同理,有p是patches[1], ul是patches[3],以此類推
patches指的是差異變化,這些差異包括:1、節(jié)點(diǎn)類型的不同,2、節(jié)點(diǎn)類型相同,但是屬性值不同,文本內(nèi)容不同。所以有這么幾種類型:

var REPLACE = 0,    // replace 替換
    REORDER = 1,    // reorder 父節(jié)點(diǎn)中子節(jié)點(diǎn)的操作
    PROPS   = 2,    // props 屬性的變化
    TEXT    = 3     // text 文本內(nèi)容的變化

如果節(jié)點(diǎn)類型不同,就說(shuō)明是需要替換,例如將div替換成section,就記錄下差異:

patches[0] = [{
    type: REPLACE,
    node: newNode // section
},{
    type: PROPS,
    props: {
        id: "container"
    }
}]
四、將差異應(yīng)用到DOM樹(shù)上

在標(biāo)題二中構(gòu)建了真正的DOM樹(shù)的信息,所以先對(duì)那一棵DOM樹(shù)進(jìn)行深度優(yōu)先的遍歷,遍歷的時(shí)候同
patches對(duì)象進(jìn)行對(duì)比,找到其中的差異,然后應(yīng)用到DOM操作中。

function patch(node, patches) {
    var walker = {index: 0} // 記錄當(dāng)前節(jié)點(diǎn)的標(biāo)志
    dfsWalk(node, walker, patches)
}

function dfsWalk(node, walker, patches) {
    var currentPatches = patches[walker.index] // 這是當(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操作
    }
}
// 將差異的部分應(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)實(shí)現(xiàn)了,具體的情況更加復(fù)雜。但這已經(jīng)足夠讓我們理解virtual-dom。
具體的帶解析的代碼已經(jīng)上傳到github

五、 References

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)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/90120.html

相關(guān)文章

  • 程序員練級(jí)攻略(2018):前端基礎(chǔ)和底層原理

    摘要:下面我們從前端基礎(chǔ)和底層原理開(kāi)始講起。對(duì)于和這三個(gè)對(duì)應(yīng)于矢量圖位圖和圖的渲染來(lái)說(shuō),給前端開(kāi)發(fā)帶來(lái)了重武器,很多小游戲也因此蓬勃發(fā)展。這篇文章受眾之大,后來(lái)被人重新整理并發(fā)布為,其中還包括中文版。 showImg(https://segmentfault.com/img/bVbjM5r?w=1142&h=640); 想閱讀更多優(yōu)質(zhì)文章請(qǐng)猛戳GitHub博客,一年百來(lái)篇優(yōu)質(zhì)文章等著你! 這...

    widuu 評(píng)論0 收藏0
  • 一名【合格】前端工程師的自檢清單

    摘要:在他的重學(xué)前端課程中提到到現(xiàn)在為止,前端工程師已經(jīng)成為研發(fā)體系中的重要崗位之一。大部分前端工程師的知識(shí),其實(shí)都是來(lái)自于實(shí)踐和工作中零散的學(xué)習(xí)。一基礎(chǔ)前端工程師吃飯的家伙,深度廣度一樣都不能差。 開(kāi)篇 前端開(kāi)發(fā)是一個(gè)非常特殊的行業(yè),它的歷史實(shí)際上不是很長(zhǎng),但是知識(shí)之繁雜,技術(shù)迭代速度之快是其他技術(shù)所不能比擬的。 winter在他的《重學(xué)前端》課程中提到: 到現(xiàn)在為止,前端工程師已經(jīng)成為研...

    羅志環(huán) 評(píng)論0 收藏0
  • 一名【合格】前端工程師的自檢清單

    摘要:在他的重學(xué)前端課程中提到到現(xiàn)在為止,前端工程師已經(jīng)成為研發(fā)體系中的重要崗位之一。大部分前端工程師的知識(shí),其實(shí)都是來(lái)自于實(shí)踐和工作中零散的學(xué)習(xí)。一基礎(chǔ)前端工程師吃飯的家伙,深度廣度一樣都不能差。開(kāi)篇 前端開(kāi)發(fā)是一個(gè)非常特殊的行業(yè),它的歷史實(shí)際上不是很長(zhǎng),但是知識(shí)之繁雜,技術(shù)迭代速度之快是其他技術(shù)所不能比擬的。 winter在他的《重學(xué)前端》課程中提到: 到現(xiàn)在為止,前端工程師已經(jīng)成為研發(fā)體系...

    isaced 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<