React?diff算法面試考點(diǎn)超詳細(xì)講解
前言
在前端工程上,日益復(fù)雜的今天,性能優(yōu)化已經(jīng)成為必不可少的環(huán)境。前端需要從每一個(gè)細(xì)節(jié)的問題去優(yōu)化。那么如何更優(yōu),當(dāng)然與他的如何怎么實(shí)現(xiàn)的有關(guān)。比如key為什么不能使用index呢?為什么不使用隨機(jī)數(shù)呢?答案當(dāng)然是影響性能,那為什么?相信你看完本文的diff算法就能略懂一些。
diff算法的概念
diff算法, 是 Virtual DOM 產(chǎn)生的一個(gè)概念, 作用是用來計(jì)算出 Virtual DOM 中被改變的部分,然后根據(jù)算法算出dom的結(jié)果進(jìn)行原生DOM操作,而不用重新渲染整個(gè)頁面,從而提高了頁面渲染效率,已經(jīng)成為當(dāng)今框架(vue,react)必不可少的部分。
手寫diff算法的過程
背景:dom對(duì)性能的消耗特別高,因此前輩們提出用js對(duì)象模擬dom的操作,計(jì)算出最后需要更新的部分。而dom本身的算法的時(shí)間復(fù)雜度是O(n^3)。這時(shí)react團(tuán)隊(duì),提出了diff算法?。ū景咐峁┖诵拇a,以及完整案例)
簡(jiǎn)單理解版本的思路的核心,可分為三個(gè)步驟:
1.模擬"dom樹",將dom轉(zhuǎn)換為js數(shù)組。
定義js構(gòu)造函數(shù),可同步dom對(duì)象。通常對(duì)象可由下邊組成:
tag('string'):標(biāo)簽的名稱
props('object'):屬性與屬性的值{ class: 'a', type: 'hidden'}
children('array'):子屬性
key('string'):表示元素的唯一標(biāo)識(shí) 'nowKeys'
2.獲取兩個(gè)dom數(shù)之間的差異(diff算法)
對(duì)比兩個(gè)dom對(duì)應(yīng)的實(shí)體,獲取他們的不同點(diǎn),根據(jù)順序數(shù)組。當(dāng)前demo處理了以下方法:
Change: 'Change',//表示元素有變化
Move: 'Move',//表示移動(dòng)了位置
Add: 'Add',//表示元素是新增的
Del: 'Del',//表示元素給刪除了
DiffPropsList: 'DiffPropsList',//表示元素對(duì)應(yīng)的屬性列表有變動(dòng)
DelProps: 'DelProps',//表示該屬性給刪除
ChangeProps: 'ChangeProps',//表示該屬性有變化
AddProps: 'AddProps',//表示該屬性是新增的
3.將“差異”進(jìn)行“渲染”
根據(jù)步驟2),將差異進(jìn)行對(duì)應(yīng)的處理
實(shí)例方法如下:
var a1 =new WzElement('div', { class: 'a1Class' }, ['a1'], "a1");
var a2 =new WzElement('div', { class: 'a2Class' }, ['a2'], "a2")
let root = a1.render();//js模擬dom生成
步驟2)
let pathchs = diff(a1, a2); //獲取當(dāng)前的兩個(gè)dom的差異
步驟3)
reloadDom(root, pathchs);//根據(jù)差異重新渲染
核心的代碼(步驟1):
_createDom( tag, props, children, key ){
let dom = document.createElement( tag );
for( let propKey in props ){
dom.setAttribute( propKey, props[propKey] );
}
if( !key ){
dom.setAttribute( "key", key );
}
children.forEach( item => {
if( item instanceof WzElement ){//
var root = this._createDom(
item.tag,
item.props,
item.children,
item.key
)
dom.appendChild( root );
}else{
var childNode = document.createTextNode( item );
dom.appendChild( childNode );
}
});
return dom;
}
核心的代碼(步驟2):參考:前端手寫面試題詳細(xì)解答
//判斷當(dāng)前對(duì)象
function dfs(oldElement, newElement, index, patches) {
//如果新的對(duì)象為空,無需要對(duì)比
//如果新的對(duì)象,key跟tag都不同,說明元素變了,直接替換。
//如果新的對(duì)象,key跟tag都相同,則遍歷子集,觀察子集是否不同,觀察元素屬性是否不同
var curPatches = [];
if (!newElement) {
} else if (oldElement.key != newElement.key || oldElement.tag != newElement.tag) {
curPatches.push({
type: stateType.Change,
node: newElement
});
} else {
var propsDiff = diffProps(oldElement.props, newElement.props);
if (propsDiff.length > 0) {
curPatches.push({
type: stateType.DiffPropsList,
node: newElement
});
}
diffChildren(oldElement.children, newElement.children,index, patches);//對(duì)比子集是否不同
}
if (curPatches.length > 0) {
if (!patches[index]) {
patches[index] = []
}
patches[index] = patches[index].concat(curPatches);
}
return patches;
}
//對(duì)比子集是否不同
function diffChildren(oldChild, newChild, index, patches) {
let { changeList, resultList } = listDiff(oldChild, newChild, index, patches);
if (changeList.length > 0) {
if (!patches[index]) {
patches[index] = []
}
patches[index] = patches[index].concat(changeList);
}
let last = null;
oldChild && oldChild.forEach((item, i) => {
let child = item && item.children;
if (child) {
if ( last && last.children != null) {//有子節(jié)點(diǎn)
index = index + last.children.length + 1;
} else {
index += 1;
}
let keyIndex = resultList.indexOf( item.key ) ;
let node = newChild[keyIndex]
//只遍歷新舊中都存在的節(jié)點(diǎn),其他新增或者刪除的沒必要遍歷
if ( node ) {
dfs(item, node, index, patches)
}
} else {
index += 1;
}
last = item
});
}核心的代碼(步驟3):
var num = 0;
//根據(jù)virtDom的結(jié)果渲染頁面
function reloadDom( node, patchs ){
var changes = patchs[ num ];
let childNodes = node && node.childNodes;
if (!childNodes) num += 1
if( changes != null ){
changeDom( node, changes );
}
//保持更diff算法的num一直
var last = null
childNodes && childNodes.forEach(( item, i ) => {
if( childNodes ){
if ( last && last.children != null) {//有子節(jié)點(diǎn)
num = num + last.children.length + 1;
} else {
num += 1;
}
}
reloadDom( item, patchs );
last = item;
})
}
//執(zhí)行每個(gè)dom的變化
function changeDom( node, changes ){
changes && changes.forEach( change => {
let {type} = change;
switch( type ){
case stateType.Change:
node.parentNode && node.parentNode.replaceChild( change.node.create() , node );
break;
case stateType.Move:
let fromNode = node.childNodes[change.from];
let toNode = node.childNodes[change.to];
let formClone = fromNode.cloneNode(true);
let toClone = toNode.cloneNode(true);
node.replaceChild( fromNode, toClone ) ;
node.replaceChild( toNode, formClone ) ;
break;
case stateType.Add:
node.insertBefore( change.node.create(), node.childNodes[ change.index ] )
break;
case stateType.Del:
node.childNodes[change.index ].remove();
break;
case stateType.DiffPropsList:
let {props} = change.node;
for( let key in props ){
if( key == stateType.DelProps ){
node.removeAttribute( );
} else {
node.setAttribute( key, props[key] );
}
}
break;
default:
break;
}
});
}
到此這篇關(guān)于React diff算法面試考點(diǎn)超詳細(xì)講解的文章就介紹到這了,更多相關(guān)React diff算法內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript 短路運(yùn)算的實(shí)現(xiàn)
本文主要介紹了JavaScript 短路運(yùn)算的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06
原生js?XMLhttprequest請(qǐng)求onreadystatechange執(zhí)行兩次的解決
這篇文章主要介紹了原生js?XMLhttprequest請(qǐng)求onreadystatechange執(zhí)行兩次的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-02-02
JavaScript閉包和作用域鏈的定義實(shí)現(xiàn)
這篇文章主要為大家介紹了JavaScript閉包和作用域鏈的定義與實(shí)現(xiàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05
JavaScript 保護(hù)變量不被隨意修改的實(shí)現(xiàn)代碼
本文通過實(shí)例代碼給大家分享JavaScript 保護(hù)變量不被隨意修改的實(shí)現(xiàn)方法,需要的朋友參考下吧2017-09-09
只需五句話搞定JavaScript作用域(經(jīng)典)
javascript作用域是前端開發(fā)比較難理解的知識(shí)點(diǎn),下面小編給大家提供五句話幫助大家很快的了解js作用域,非常不錯(cuò),具有參考借鑒價(jià)值,感興趣的朋友一起學(xué)習(xí)吧2016-07-07

