摘要:最后里面沒(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)容有些不同而已,比如:
可以把這個(gè)例子想象成一個(gè)購(gòu)物車(chē)。此時(shí)如果想往購(gòu)物車(chē)?yán)锩嫣砑右患唐?,性能不?huì)有任何問(wèn)題,因?yàn)橹皇呛?jiǎn)單的在ul的末尾追加元素,前面的元素都不需要更新:
但是,如果我要?jiǎng)h除第一個(gè)元素,根據(jù)VD的比較邏輯,后面的元素全部都要進(jìn)行更新的操作。dom結(jié)構(gòu)簡(jiǎn)單還好說(shuō),如果是一個(gè)復(fù)雜的結(jié)構(gòu),那頁(yè)面渲染的性能將會(huì)受到很大的影響。
有什么方式可以降低這種性能的損耗呢?
最直觀的方法肯定是直接刪除第一個(gè)元素然后其它元素保持不變了。但程序沒(méi)有這么智能,可以像我們一樣一眼就看出變化。程序能做到的是盡量少的修改元素,通過(guò)移動(dòng)元素而不是修改元素來(lái)達(dá)到更新的目的。為了告訴程序要怎么移動(dòng)元素,我們必須給每個(gè)元素加上一個(gè)唯一標(biāo)識(shí),也就是key。
當(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
摘要:經(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è)面的渲染效率。那么,什...
摘要:變化的只有種更新和刪除。頁(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è)面的渲染...
摘要:不同的框架對(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,都不約...
摘要:如果列表是空的,則存入組件后將異步刷新任務(wù)加入到事件循環(huán)當(dāng)中。四總結(jié)本文基于上一個(gè)版本的代碼,加入了事件處理功能,同時(shí)通過(guò)異步刷新的方法提高了渲染效率。 歡迎關(guān)注我的公眾號(hào)睿Talk,獲取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言 目前最流行的兩大前端框架,React和Vue,都不約而同的借助Virtual DO...
摘要:現(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); 一、...
閱讀 1604·2021-11-25 09:43
閱讀 2508·2019-08-30 15:54
閱讀 2981·2019-08-30 15:53
閱讀 1121·2019-08-30 15:53
閱讀 775·2019-08-30 15:52
閱讀 2565·2019-08-26 13:36
閱讀 845·2019-08-26 12:16
閱讀 1242·2019-08-26 12:13