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算法!(本案例提供核心代碼,以及完整案例)
簡(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-02JavaScript閉包和作用域鏈的定義實(shí)現(xiàn)
這篇文章主要為大家介紹了JavaScript閉包和作用域鏈的定義與實(shí)現(xiàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05JavaScript 保護(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