- http优化,加大并发,减少请求数量以及传输量
- domain hash技术突破并发限制。http1.x浏览器对于发起的连接有并发限制,这个限制是针对域名的,所以将静态资源放在多个不同的域名下,也可以突破这个限制。但是也不宜使用太多域名。会增加额外的dns解析成本。
- 合理使用http头。使用expires,cache-control,Etags等http头缓存静态资源。这篇文章介绍的很详细:
- Connection:keep-alive。保持tcp连接。避免三次握手以及tcp的慢启动开销。
- 合理利用空闲时间做一些预操作。预测用户的大概率行为,在页面空闲时加载后续所需要的资源。dns预解析。TCP预连接。页面预渲染。有一些标签属性已经可以很好的做到这些,如prefetch & preload & dns-prefetch。
- 合并css,js文件。
- 图片压缩以及页面需要多大的图片就请求多大的图片。比如页面只显示20*20的图片,就不要返回一个500*500的图片。
- cdn加速。
- gzip压缩。
- 减少不必要的通信量,比如合并发送上报数据。
- 按需加载模块资源。比如js,css。
- 使用http2。
- 缓存请求,并保持缓存内容大小与性能之间的平衡。比如某个请求涉及大量的数据库操作,耗时很长,并且有一定概率多次请求。在这个数据的实时性和重要性不是那么强的情况下,我们可以将请求结果缓存到变量中或浏览器的一些别的存储机制中。但是缓存内容过多也会对性能有影响,所以我们也要根据一定的规则(比如访问频率,访问先后时间,缓存总数量等)及时地清除部分缓存。如果不想改动代码,那么http响应可以返回Expires http请求头,在过期之前就不再发请求。
- cookie优化,减少cookie传输量
- 避免cookie太庞大。不要什么都往cookie上放。cookie的主要作用在于身份识别,而不是信息存储。因为每个请求都会带着cookie,无形中会加大很多传输量。前端的话可以使用一些其他的替代存储方式。比如localStorage,sessionStorage。
- cookie free技术。将一些静态资源放在与主域不同域名的服务器上,浏览器请求的时候就不会带上主域的cookie了,从而减少传输量。
- Bigpipe技术。产生于Facebook公司的前端加载技术,它的提出主要是为了解决重数据页面的加载速度问题,是一种数据渐进式预加载方案,基于HTTP Chunk。
- PWA技术。service worker。
- 避免空的src和href。
- 图片懒加载。lazy load。
- 脚本加载优化
- script标签放到页面最后,</body>标签前面,避免阻塞页面渲染。一个讨论:。
- 合并,压缩脚本。减少连接数和数据传输大小。
-
无阻塞的脚本
- script标签添加defer,async属性。
- 动态生成script标签。使用onload事件或onreadystatechange事件来检测脚本加载完从而执行加载完之后的回调。
- 使用ajax方式加载js内容。插入一个script标签中。好处是加载完之后不会立即执行。
- JS数据存取
- 减少作用域查询。作用域链的查询,变量的位置越深,查询速度越慢。而全局变量在作用域链最深。所以,对于使用一次以上的跨作用域变量我们应该把它用局部变量存起来。
-
避免内存泄露。
- 循环引用
- IE,闭包中有dom对象。
- 减少嵌套成员的查找。可缓存在局部变量中。类似var Dom = YAHOO.util.Dom;
- 减少原型链查找。
- dom优化
- 减少dom数量。如果页面dom数量太多,对性能是有影响的。
- 减少dom访问与修改。dom与JavaScript相当于两个独立的部分以功能接口连接,会带来性能损耗。
- 尽量不要在循环中更新页面内容。一个更有效率的版本将使用局部变量存储更新后的内容,在循环结束时一次性写入。
- 不建议用数组的 length 属性做循环判断条件。访问集合的 length 比数组的length 还要慢,因为它意味着每次都要重新运行查询过程。
- 使用快的API。比如document.querySelector()。
-
事件绑定。
- 多使用事件代理,而不是每个dom节点上去都绑定事件。
- 考虑使用自已定义的事件管理器,一个dom上不要反复绑定事件。而是维护一个事件回调数组,像jQuery做的那样。
-
减少回流与重绘。
- 少使用.style一个属性一个属性地去改。而是合并到一起一起修改。比如用class来控制样式,或者cssText来批量修改。
- 让要操作的元素进行"离线处理",处理完后一起更新。
- 回流必将引起重绘,而重绘不一定会引起回流。
- 减少对位置信息的属性读取以及getComputedStyle与currentStyle的使用。浏览器会维护1个队列,把所有会引起回流、重绘的操作放入这个队列,等队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会flush队列,进行一个批处理。这样就会让多次的回流、重绘变成一次回流重绘。如果代码中频繁读取实时位置属性,会导致浏览器多次重排。引起性能问题。
- 异步优化任务。分割任务异步执行,让出线程。
- 如果用户的操作100ms得不到响应,用户就会感觉到与应用失去联系。如果我们的代码执行时间太长,用户其他的操作得不到响应。所以如果我们无法减少脚本执行时间,我们可能考虑主动地让出线程。分解任务,异步执行。
比如分解成多个任务使用setTimeout或setInterval来异步执行。
- 但是如果我们任务分得太细,比如每个循环体算成一个任务,每个任务结束就让出线程,效率就很低了,因为setTimeout 和 setInterval 本来就设计的慢吞吞的,即使延时时间为0,浏览器环境下每秒也最多执行几百次。而换成while循环,每秒能执行几百万次。 所以我们每个异步任务中应该多处理一些任务,比如我们让它执行50ms。在每个异步中检测一下执行时间,加入while循环,时间如果小于50ms就继续执行,超过50ms就让出线程。这样既保证了不阻塞线程,也让我们的任务能尽快地完成。
- 使用Web Workers。
- ajax的优化。目前一些比较成熟的库的ajax都是在ajax完全接收完响应之后才执行成功的回调。这里其实有很大的优化空间。
- 一般的ajax封装是在XMLHttpRequest的readyState==4(整个请求过程已经完毕)的时候进行成功回调处理。而其实在readyState==3的时候(响应体下载中,responseText中已经获取了部分数据.)已经可以对已经接收到的部分内容进行处理了。比如几十万条数据从后端传过来,要插入dom。如果我们等到所有数据接收完毕,再一次性插入dom,可能会有很大的性能问题。但是如果我们在后台将数据以一定的方式拼装,然后前端接收到一部分处理一部分,就有两方面的性能提升,一方面是提前处理了数据,让用户可以更早地看到数据效果,另一方面是分解了任务。
- 合并请求。比如多个图片base64格式加分割符一起发送过来。前端再把结果分割,分发到多个img标签上去。
- 数据传递格式。不一定非要是json格式。其实可以很灵活。自定义的格式一方面可以减少数据传输量,另一方面更方便前端边接收边处理。
- 循环与递归
- 尾调用优化。会将从内存中清除前面的调用栈,将调用栈清零。一方面是内存释放,另一方面是避免了调用栈溢出引起的错误。
- 减小循环次数。每个循环体中多执行几个循环内容。
- 减少循环体开销。比如使用倒序循环。
- 缓存计算结果。使用Memoization技术来避免重复计算。
- 函数的节流与防抖,限制函数主体执行频率。频繁执行某些函数会严重影响性能,比如一些常见的触发频率很高的浏览器事件,如果每次触发都去执行回调甚至操作dom,性能影响很大,并且我们肉眼对dom变化的实时性要求并没有那么高。所以需要限制主体内容的执行频率。工具库underscore中提供了对应的_.throttle和_.debounce方法。
- window对象的resize、scroll事件
- 拖拽时的mousemove事件
- mousedown、keydown事件
- 文字输入、自动完成的keyup事件
- requestAnimationFrame方法。
- 逻辑优化,减少耗时操作。很多功能并不是只有一种途径去实现。如果一种操作特别耗时。也许可以优化一些逻辑就减少这样的操作。
- 定时器的控制。
应用中存在过多的定时器会影响性能。特别是单页应用中,如果我们定义了一些定时器而没有随着场景消失而清掉定时器,还可能会产生很多逻辑上的问题。
- 正则表达式优化。
- css gpu加速。
欢迎补充交流。github地址:
参考:
- 《高性能JavaScript》
- 《深入浅出Nodejs》