当前位置: 首页 > Web前端 > JavaScript

手写现代前端框架diff算法-高级前端访谈_0

时间:2023-03-27 12:48:07 JavaScript

前言在前端工程中,在日益复杂的性能优化的今天已经成为不可或缺的环境。前端需要从每一个细节去优化。那么如何变得更好,当然和他的实现方式有关。比如key为什么不能用index呢?为什么不使用随机数呢?答案当然是影响性能,为什么呢?相信看完这篇文章的diff算法,你能稍微明白一点。diff算法的概念diff算法是VirtualDOM产生的一个概念。用于计算VirtualDOM发生变化的部分,然后根据算法计算出的dom结果进行原生DOM操作,无需重新渲染整个页面,从而提高了页面渲染效率,已经成为不可或缺的一部分当今的框架(vue、react)。手写diff算法的过程背景:dom非常消耗性能,所以前辈们提出用js对象模拟dom的运行,最后计算出需要更新的部分。而dom本身算法的时间复杂度是O(n^3)。这个时候react团队提出了diff算法!(本案例提供了核心代码,一个完整的案例)简单理解该版本的核心思想可以分为三个步骤:1.模拟“dom树”,将dom转化为js数组。定义js构造函数来同步dom对象。通常一个对象可以由以下内容组成:tag('string'):标签的名称props('object'):属性和属性的值{class:'a',type:'hidden'}children('array'):childattributeskey('string'):表示元素'nowKeys'的唯一标识2.获取两个dom号的差值(diff算法)比较两个dom对应的实体,获取它们差异,并按顺序排列。当前demo处理了以下几个方法:Change:'Change',//表示元素发生了变化Move:'Move',//表示位置被移动了Add:'Add',//表示元素被移动了newlyaddedDel:'Del',//表示该元素已被删除DiffPropsList:'DiffPropsList',//表示该元素对应的属性列表发生了变化DelProps:'DelProps',//表示该属性已被删除deletedChangeProps:'ChangeProps',//表示该属性发生了变化AddProps:'AddProps',//表示该属性是新添加的3.“渲染”出“差异”根据步骤2),相应的处理区别如下:vara1=newWzElement('div',{class:'a1Class'},['a1'],"a1");vara2=newWzElement('div',{class:'a2Class'},['a2'],"a2")letroot=a1.render();//js模拟dom生成step2)letpathchs=diff(a1,a2);//获取当前两个dom的不同点step3)reloadDom(root,pathchs);//根据不同点重新加载渲染核心代码(第1步):_createDom(tag,props,children,key){letdom=文档.createElement(标签);for(letpropKeyinprops){dom.setAttribute(propKey,props[propKey]);}if(!key){dom.setAttribute("key",key);}children.forEach(item=>{if(iteminstanceofWzElement){//varroot=this._createDom(item.tag,item.props,item.children,item.key)dom.appendChild(root);}else{varchildNode=document.createTextNode(item);dom.appendChild(childNode);}});返回国内;}核心代码(第2步)://判断当前对象functiondfs(oldElement,newElement,index,patches){//如果新对象为空,则不需要比较//如果是新对象,key和tag他们都不同,说明元素变了,直接替换//如果新对象key和tag相同,则遍历子集,观察子集是否不同,观察元素属性是否不同varcurPatches=[];if(!newElement){}elseif(oldElement.key!=newElement.key||oldElement.tag!=newElement.tag){curPatches.push({type:stateType.Change,node:newElement});}else{varpropsDiff=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);}returnpatches;}//比较子集是否不同他是);if(changeList.length>0){if(!patches[index]){patches[index]=[]}patches[index]=patches[index].concat(changeList);}让last=null;oldChild&&oldChild.forEach((item,i)=>{letchild=item&&item.children;if(child){if(last&&last.children!=null){//有子节点index=index+last.children.length+1;}else{index+=1;}letkeyIndex=resultList.indexOf(item.key);letnode=newChild[keyIndex]//只遍历新旧都存在的节点,其他newor不需要遍历if(node){dfs(item,node,index,patches)}}else{index+=1;}last=item});}参考前端进阶面试题详细回答核心代码(步骤3):varnum=0;//根据virtDom函数的结果渲染页面reloadDom(node,patches){varchanges=patches[num];让childNodes=节点&&节点.childNodes;if(!childNodes)num+=1if(changes!=null){changeDom(node,changes);}//一直保持diff算法的numvarlast=nullchildNodes&&childNodes.forEach((item,i)=>{if(childNodes){if(last&&last.children!=null){//有子节点num=num+last.children.length+1;}else{num+=1;}}reloadDom(item,patches);last=item;})}//执行每个dom变化函数changeDom(node,更改){更改&&changes.forEach(change=>{let{type}=change;switch(type){casestateType.Change:node.parentNode&&node.parentNode.replaceChild(change.node.create(),node);中断;casestateType.Move:letfromNode=node.childNodes[change.from];lettoNode=node.childNodes[change.to];让formClone=fromNode.cloneNode(true);让toClone=toNode.cloneNode(true);node.replaceChild(fromNode,toClone);node.replaceChild(toNode,formClone);休息;casestateType.Add:node.insertBefore(change.node.create(),node.childNodes[change.index])中断;casestateType.Del:node.childNodes[change.index].remove();休息;casestateType.DiffPropsList:让{props}=change.node;for(letkeyinprops){if(key==stateType.DelProps){node.removeAttribute();}else{node.setAttribute(key,props[key]);}}休息;默认值:中断;}});}