这篇文章主要是看完《高性能Javascript》之后,想记录一些有用的优化方案,和大家分享一些自己的心得。Javascript的加载和执行大家都知道。浏览器在解析DOM树时,当解析完script标签后,会阻塞其他所有任务,直到js文件下载解析执行完毕后才会继续执行。所以此时浏览器会被屏蔽在这里。如果把script标签放在head中,在js文件加载执行前,用户只能看到一个空白页面。这样的用户体验一定特别差。对此,常用的做法是:将所有的script标签放在body的底部,这样可以保证js文件最后加载执行,页面能最先展示给用户。但是你首先要知道页面的首屏渲染是否依赖于你的一些js文件,如果是的话,你需要把这部分js文件放在最前面。使用defer,比如下面的。使用defer时,虽然浏览器在解析标签的时候会下载相应的js文件,但不会立即执行,而是等到DOM解析完成后(在DomContentLoader之前)执行这些js文件。因此,浏览器不会被阻塞。动态加载js文件,这种方式可以在页面加载完成后加载需要的代码,也可以通过这个方法实现延迟加载/按需加载js文件。比如现在比较常见的是webpack结合vue-router/react-router实现按需加载。只有访问特定路由时,才会加载相应的代码。具体方法如下:1.动态插入script标签加载脚本,比如通过下面的代码functionloadScript(url,callback){constscript=document.createElement('script');script.type='文本/javascript';//处理IE打回来();}}}else{//处理其他浏览器script.onload=function(){callback();}}script.src=url;document.body.append(脚本);}//动态加载jsloadScript('file.js',function(){console.log('loadingcomplete');})2.通过xhr加载js文件,但是这种方式可能会遇到跨域问题.示例:constxhr=newXMLHttpRequest();xhr.open('get','file.js');xhr.onreadystatechange=function(){if(xhr.readyState===4){if(xhr.status>=200&&xhr.status<300||xhr.status===304){constscript=document.createElement('脚本');script.type='文本/javascript';script.text=xhr.responseText;document.body.append(脚本);}}}3。将多个js文件合并为一个并压缩。原因:目前大部分浏览器已经支持js文件的并行下载,但是还是存在一定的并发下载(基于浏览器,有的浏览器只能下载4个),每个js文件需要创建一次。http连接,加载四个25KB的文件比加载一个100KB的文件耗时更多。所以,我们最好的办法是将多个js文件合并成一个,压缩代码。JavaScript作用域当一个函数被执行时,会生成一个执行上下文,它定义了函数执行的环境。当函数完成执行时,执行上下文被销毁。因此,多次调用同一个函数会导致创建多个执行上下文。每个执行上下文都有自己的作用域链。相信大家应该早就知道scope了。对于一个函数,它的第一个作用域是它函数内部的变量。在函数执行过程中,每遇到一个变量,就会搜索函数的作用域链,找到第一个匹配的变量。首先查找函数内部的变量,然后沿着作用域链逐层查找。因此,如果我们要访问最外层的变量(全局变量),相比直接访问内部变量,会带来比较大的性能损失。因此,我们可以将经常使用的全局变量引用存放在一个局部变量中。常量=5;函数外部(){consta=2;函数内部(){constb=2;控制台日志(b);//2控制台日志(a);//2}内部();javascript中对象的读取主要分为四种:字面量、局部变量、数组元素、对象。访问字面量和局部变量最快,而访问数组元素和对象成员相对较慢。访问对象成员时,就像作用域链一样,在原型链(prototype)上查找。因此,如果查找到的成员在原型链中过深,访问速度会变慢。因此,我们应该尽可能减少对象成员的搜索次数和嵌套深度。例如,下面的代码//执行两个对象成员查找functionhasEitherClass(element,className1,className2){returnelement.className===className1||元素.className===className2;}//优化,如果变量不会变化,可以使用局部变量保存搜索到的内容functionhasEitherClass(element,className1,className2){constcurrentClassName=element.className;返回当前类名===类名1||当前类名===类名2;}DOM操作优化了最小化DOMTimes的操作,尽量使用javascript处理,尽量使用局部变量存储DOM节点。例如下面的代码://优化前,在每次循环中,获取id为t的节点,并设置其innerHTML函数innerHTMLLoop(){for(letcount=0;count<15000;count++){document.getElementById('t').innerHTML+='a';}}//优化后,functioninnerHTMLLoop(){consttNode=document.getElemenById('t');constinsertHtml='';for(letcount=0;count<15000;count++){insertHtml+='a';}tNode.innerHtml+=insertHtml;}尽可能的减少重排和重绘,重排和重组的代价可能是非常大的,所以为了减少重排和重提交的次数,我们可以做如下优化1.当我们要修改Dom的样式时,我们应该merge尽可能将所有修改一次性处理,以减少重新排序和重组的次数。//优化前constel=document.getElementById('test');el.style.borderLeft='1px';el.style.borderRight='2px';el.style.padding='5px';//优化后,一次性修改样式,可以将3次重排缩减为1次重排constel=document.getElementById('test');el.style.cssText+=';左边框:1px;右边框:2px;填充:5px;'2。当我们要批量修改DOM节点时,可以隐藏DOM节点,然后进行一系列的修改操作,然后设置为可见,这样最多只能重写两次。排。具体方法如下://优化前,constele=document.getElementById('test');//一系列dom修改操作//优化方案1,将要修改的节点设置为不显示,然后修改,修改完成后显示该节点,所以只需要重排两次constele=document.getElementById('测试');ele.style.display='无';//一系列dom修改操作ele.style.display='block';//优化方案二,先创建一个文档片段(documentFragment),然后修改片段,然后将文档片段插入到文档中,只有文档片段最后插入到文档中才会引起重排,所以只有一次reflow会被触发。.constfragment=document.createDocumentFragment();constele=document.getElementById('测试');//一系列dom修改操作ele.appendChild(fragment);3、使用事件委托:事件委托就是将目标节点的事件移动到父节点去处理。由于浏览器的冒泡特性,当目标节点触发事件时,父节点也会触发事件。因此,父节点负责监听和处理事件。那么,它有什么优势呢?假设你有一个列表,每个列表项都需要绑定同一个事件,而这个列表可能会被频繁的插入和删除。如果按照通常的方法,每个列表项只能绑定一个事件处理程序,而每当插入一个新的列表项时,还需要为新的列表项注册一个新的事件处理程序。在这种情况下,如果列表项很大,会导致事件处理程序特别多,造成很大的性能问题。而通过事件委托,我们只需要在列表项的父节点监听这个事件,让它统一处理即可。这样就不需要对新增的列表项进行额外的处理。而且,事件委托的用法其实很简单:functionhandleClick(target){//点击列表项的处理事件}functiondelegate(e){//判断目标对象是否为列表项if(e.target.nodeName==='LI'){handleClick(e.target);}}constparent=document.getElementById('parent');parent.addEventListener('click',delegate);本文地址为->我的博客地址,欢迎开始或关注
