成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

你不知道的Virtual DOM(四):key的作用

DirtyMind / 919人閱讀

摘要:最后里面沒(méi)有第四個(gè)元素了,才會(huì)把蘋(píng)果從移除。四總結(jié)本文基于上一個(gè)版本的代碼,加入了對(duì)唯一標(biāo)識(shí)的支持,很好的提高了更新數(shù)組元素的效率。

歡迎關(guān)注我的公眾號(hào)睿Talk,獲取我最新的文章:

一、前言

目前最流行的兩大前端框架,React和Vue,都不約而同的借助Virtual DOM技術(shù)提高頁(yè)面的渲染效率。那么,什么是Virtual DOM?它是通過(guò)什么方式去提升頁(yè)面渲染效率的呢?本系列文章會(huì)詳細(xì)講解Virtual DOM的創(chuàng)建過(guò)程,并實(shí)現(xiàn)一個(gè)簡(jiǎn)單的Diff算法來(lái)更新頁(yè)面。本文的內(nèi)容脫離于任何的前端框架,只講最純粹的Virtual DOM。敲單詞太累了,下文Virtual DOM一律用VD表示。

這是VD系列文章的第四篇,以下是本系列其它文章的傳送門(mén):
你不知道的Virtual DOM(一):Virtual Dom介紹
你不知道的Virtual DOM(二):Virtual Dom的更新
你不知道的Virtual DOM(三):Virtual Dom更新優(yōu)化
你不知道的Virtual DOM(四):key的作用
你不知道的Virtual DOM(五):自定義組件
你不知道的Virtual DOM(六):事件處理&異步更新

今天,我們繼續(xù)在之前項(xiàng)目的基礎(chǔ)上進(jìn)行優(yōu)化。用過(guò)React或者Vue的朋友都知道在渲染數(shù)組元素的時(shí)候,編譯器會(huì)提醒加上key這個(gè)屬性,那么key是用來(lái)做什么的呢?

二、key的作用

在渲染數(shù)組元素時(shí),它們一般都有相同的結(jié)構(gòu),只是內(nèi)容有些不同而已,比如:

  • 商品:蘋(píng)果 數(shù)量:1
  • 商品:香蕉 數(shù)量:2
  • 商品:雪梨 數(shù)量:3

可以把這個(gè)例子想象成一個(gè)購(gòu)物車(chē)。此時(shí)如果想往購(gòu)物車(chē)?yán)锩嫣砑右患唐?,性能不?huì)有任何問(wèn)題,因?yàn)橹皇呛?jiǎn)單的在ul的末尾追加元素,前面的元素都不需要更新:

  • 商品:蘋(píng)果 數(shù)量:1
  • 商品:香蕉 數(shù)量:2
  • 商品:雪梨 數(shù)量:3
  • 商品:橙子 數(shù)量:2

但是,如果我要?jiǎng)h除第一個(gè)元素,根據(jù)VD的比較邏輯,后面的元素全部都要進(jìn)行更新的操作。dom結(jié)構(gòu)簡(jiǎn)單還好說(shuō),如果是一個(gè)復(fù)雜的結(jié)構(gòu),那頁(yè)面渲染的性能將會(huì)受到很大的影響。

  • 商品:香蕉 數(shù)量:2
  • 商品:雪梨 數(shù)量:3
  • 商品:橙子 數(shù)量:2

有什么方式可以降低這種性能的損耗呢?

最直觀的方法肯定是直接刪除第一個(gè)元素然后其它元素保持不變了。但程序沒(méi)有這么智能,可以像我們一樣一眼就看出變化。程序能做到的是盡量少的修改元素,通過(guò)移動(dòng)元素而不是修改元素來(lái)達(dá)到更新的目的。為了告訴程序要怎么移動(dòng)元素,我們必須給每個(gè)元素加上一個(gè)唯一標(biāo)識(shí),也就是key。

  • 商品:蘋(píng)果 數(shù)量:1
  • 商品:香蕉 數(shù)量:2
  • 商品:雪梨 數(shù)量:3
  • 商品:橙子 數(shù)量:2

當(dāng)把蘋(píng)果刪掉的時(shí)候,VD里面第一個(gè)元素是香蕉,而dom里面第一個(gè)元素是蘋(píng)果。當(dāng)元素有key屬性的時(shí)候,框架就會(huì)嘗試根據(jù)這個(gè)key去找對(duì)應(yīng)的元素,找到了就將這個(gè)元素移動(dòng)到第一個(gè)位置,循環(huán)往復(fù)。最后VD里面沒(méi)有第四個(gè)元素了,才會(huì)把蘋(píng)果從dom移除。

三、代碼實(shí)現(xiàn)

在上一個(gè)版本代碼的基礎(chǔ)上,主要的改動(dòng)點(diǎn)是diffChildren這個(gè)函數(shù)。原來(lái)的實(shí)現(xiàn)很簡(jiǎn)單,遞歸的調(diào)用diff就可以了:

function diffChildren(newVDom, parent) {
    // 獲取子元素最大長(zhǎng)度
    const childLength = Math.max(parent.childNodes.length, newVDom.children.length);

    // 遍歷并diff子元素
    for (let i = 0; i < childLength; i++) {
        diff(newVDom.children[i], parent, i);
    }
}

現(xiàn)在,我們要對(duì)這個(gè)函數(shù)進(jìn)行一個(gè)大改造,讓他支持key的查找:

function diffChildren(newVDom, parent) {
    // 有key的子元素
    const nodesWithKey = {};
    let nodesWithKeyCount = 0;

    // 沒(méi)key的子元素
    const nodesWithoutKey = [];
    let nodesWithoutKeyCount = 0;

    const childNodes = parent.childNodes,
          nodeLength = childNodes.length;

    const vChildren = newVDom.children,
          vLength = vChildren.length;

    // 用于優(yōu)化沒(méi)key子元素的數(shù)組遍歷
    let min = 0;

    // 將子元素分成有key和沒(méi)key兩組
    for (let i = 0; i < nodeLength; i++) {
        const child = childNodes[i],
              props = child[ATTR_KEY];

        if (props !== undefined && props.key !== undefined) {
            nodesWithKey[props.key] = child;
            nodesWithKeyCount++;
        } else {
            nodesWithoutKey[nodesWithoutKeyCount++] = child;
        }
    }

    // 遍歷vdom的所有子元素
    for (let i = 0; i < vLength; i++) {
        const vChild = vChildren[i],
              vProps = vChild.props;
        let dom;

        vKey = vProps!== undefined ? vProps.key : undefined;
        // 根據(jù)key來(lái)查找對(duì)應(yīng)元素
        if (vKey !== undefined) {
            if (nodesWithKeyCount && nodesWithKey[vKey] !== undefined) {
                dom = nodesWithKey[vKey];
                nodesWithKey[vKey] = undefined;
                nodesWithKeyCount--; 
            }
        } 
        // 如果沒(méi)有key字段,則找一個(gè)類(lèi)型相同的元素出來(lái)做比較
        else if (min < nodesWithoutKeyCount) {
            for (let j = 0; j < nodesWithoutKeyCount; j++) {
                const node = nodesWithoutKey[j];
                if (node !== undefined && isSameType(node, vChild)) {
                    dom = node;
                    nodesWithoutKey[j] = undefined;
                    if (j === min) min++;
                    if (j === nodesWithoutKeyCount - 1) nodesWithoutKeyCount--;
                    break;
                }
            }
        }

        // diff返回是否更新元素
        const isUpdate = diff(dom, vChild, parent);

        // 如果是更新元素,且不是同一個(gè)dom元素,則移動(dòng)到原先的dom元素之前
        if (isUpdate) {
            const originChild = childNodes[i];
            if (originChild !== dom) {
                parent.insertBefore(dom, originChild);
            }
        }
    }

    // 清理剩下的未使用的dom元素
    if (nodesWithKeyCount) {
       for (key in nodesWithKey) {
           const node = nodesWithKey[key];
           if (node !== undefined) {
               node.parentNode.removeChild(node);
           }
       } 
    }
    // 清理剩下的未使用的dom元素
    while (min <= nodesWithoutKeyCount) {
        const node = nodesWithoutKey[nodesWithoutKeyCount--];
        if ( node !== undefined) {
            node.parentNode.removeChild(node);
        }
    }
}

代碼比較長(zhǎng),主要是以下幾個(gè)步驟:

將所有dom子元素分為有key和沒(méi)key兩組

遍歷VD子元素,如果VD子元素有key,則去查找有key的分組;如果沒(méi)key,則去沒(méi)key的分組找一個(gè)類(lèi)型相同的元素出來(lái)

diff一下,得出是否更新元素的類(lèi)型

如果是更新元素且子元素不是原來(lái)的,則移動(dòng)元素

最后清理刪除沒(méi)用上的dom子元素

diff也要改造一下,如果是新建、刪除或者替換元素,返回false。更新元素則返回true:

function diff(dom, newVDom, parent) {
    // 新建node
    if (dom == undefined) {
        parent.appendChild(createElement(newVDom));
        return false;
    }

    // 刪除node
    if (newVDom == undefined) {
        parent.removeChild(dom);
        return false;
    }

    // 替換node
    if (!isSameType(dom, newVDom)) {
        parent.replaceChild(createElement(newVDom), dom);
        return false;
    }

    // 更新node
    if (dom.nodeType === Node.ELEMENT_NODE) {
        // 比較props的變化
        diffProps(newVDom, dom);

        // 比較children的變化
        diffChildren(newVDom, dom);
    }

    return true;
}

為了看效果,view函數(shù)也要改造下:

const arr = [0, 1, 2, 3, 4];

function view() {
    const elm = arr.pop();

    // 用于測(cè)試能不能正常刪除元素
    if (state.num !== 9) arr.unshift(elm);

    // 用于測(cè)試能不能正常添加元素
    if (state.num === 12) arr.push(9);

    return (
        
Hello World
    { arr.map( i => (
  • 第{i}
  • )) }
); }

通過(guò)變換數(shù)組元素的順序和適時(shí)的添加/刪除元素,驗(yàn)證了代碼按照我們的設(shè)計(jì)思路正確運(yùn)行。

四、總結(jié)

本文基于上一個(gè)版本的代碼,加入了對(duì)唯一標(biāo)識(shí)(key)的支持,很好的提高了更新數(shù)組元素的效率。基于當(dāng)前這個(gè)版本的代碼還能做怎樣的優(yōu)化呢,請(qǐng)看下一篇的內(nèi)容:你不知道的Virtual DOM(五):自定義組件。

P.S.: 想看完整代碼見(jiàn)這里,如果有必要建一個(gè)倉(cāng)庫(kù)的話請(qǐng)留言給我:代碼

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

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

相關(guān)文章

  • 你不知道Virtual DOM(三):Virtual Dom更新優(yōu)化

    摘要:經(jīng)過(guò)這次優(yōu)化,計(jì)算的時(shí)間快了那么幾毫秒?;诋?dāng)前這個(gè)版本的代碼還能做怎樣的優(yōu)化呢,請(qǐng)看下一篇的內(nèi)容你不知道的四的作用。 歡迎關(guān)注我的公眾號(hào)睿Talk,獲取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言 目前最流行的兩大前端框架,React和Vue,都不約而同的借助Virtual DOM技術(shù)提高頁(yè)面的渲染效率。那么,什...

    xiongzenghui 評(píng)論0 收藏0
  • 你不知道Virtual DOM(二):Virtual Dom更新

    摘要:變化的只有種更新和刪除。頁(yè)面的元素的數(shù)量隨著而變。四總結(jié)本文詳細(xì)介紹如何實(shí)現(xiàn)一個(gè)簡(jiǎn)單的算法,再根據(jù)計(jì)算出的差異去更新真實(shí)的。 歡迎關(guān)注我的公眾號(hào)睿Talk,獲取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言 目前最流行的兩大前端框架,React 和 Vue,都不約而同的借助 Virtual DOM 技術(shù)提高頁(yè)面的渲染...

    testbird 評(píng)論0 收藏0
  • 你不知道Virtual DOM(一):Virtual Dom介紹

    摘要:不同的框架對(duì)這三個(gè)屬性的命名會(huì)有點(diǎn)差別,但表達(dá)的意思是一致的。它們分別是標(biāo)簽名屬性和子元素對(duì)象。我們先來(lái)看下頁(yè)面的更新一般會(huì)經(jīng)過(guò)幾個(gè)階段。元素有可能是數(shù)組的形式,需要將數(shù)組解構(gòu)一層。 歡迎關(guān)注我的公眾號(hào)睿Talk,獲取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言 目前最流行的兩大前端框架,React和Vue,都不約...

    lavor 評(píng)論0 收藏0
  • 你不知道Virtual DOM(六):事件處理&異步更新

    摘要:如果列表是空的,則存入組件后將異步刷新任務(wù)加入到事件循環(huán)當(dāng)中。四總結(jié)本文基于上一個(gè)版本的代碼,加入了事件處理功能,同時(shí)通過(guò)異步刷新的方法提高了渲染效率。 歡迎關(guān)注我的公眾號(hào)睿Talk,獲取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言 目前最流行的兩大前端框架,React和Vue,都不約而同的借助Virtual DO...

    caozhijian 評(píng)論0 收藏0
  • 你不知道Virtual DOM(五):自定義組件

    摘要:現(xiàn)在流行的前端框架都支持自定義組件,組件化開(kāi)發(fā)已經(jīng)成為提高前端開(kāi)發(fā)效率的銀彈。二對(duì)自定義組件的支持要想正確的渲染組件,第一步就是要告訴某個(gè)標(biāo)簽是自定義組件。下面的例子里,就是一個(gè)自定義組件。解決了識(shí)別自定義標(biāo)簽的問(wèn)題,下一步就是定義標(biāo)簽了。 歡迎關(guān)注我的公眾號(hào)睿Talk,獲取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、...

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

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

0條評(píng)論

DirtyMind

|高級(jí)講師

TA的文章

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