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