javascript性能优化技巧

春节在家,把《高性能的JavaScript》刷了一遍,受益不浅。本着每看完一本书都要作读书笔记的习惯,将书中的知识点总结一下。javascript

因为不一样浏览器使用的JavaScript引擎不一样,所以对JavaScript的优化也不尽相同。也所以,有些方法在IE上可能性能相差很大,但在chrome上相差无几,也甚至某些方法在IE上最快,但在chrome上却并非最优的方案,因此,对性能有极致要求的应用,应考虑你的产品使用者最经常使用的浏览器。固然,下面提到的优化方法都是通用法则或者对大多数浏览器都友好的方法。css

JavaScript加载和执行

JavaScript的下载和执行会阻塞用户界面的绘制和其余资源的下载html

优化方法:

1.阻塞式脚本:合并文件(减小http请求),将script标签放在body尾部(减小页面css,html的下载阻塞,减小界面的空白时间(浏览器在解析到script标签以前,不会渲染页面的任何部分))前端

目前流行的构建工具,如webpack,gulp,都有打包、合并文件的功能。java

2.无阻塞式脚本:延迟脚本和动态脚本均不阻塞,即下载过程不阻塞其余进程node

延迟脚本:
defer和async属性:都是并行下载,下载过程不阻塞,区别在于执行时机,async是下载完成后当即执行;defer是等页面加载完成后再执行。defer仅当src属性声明时才生效(HTML5的规范)webpack

动态脚本:
动态添加script标签,返回的代码一般会马上执行,因此,为了确保脚本下载完成且准备就绪后才执行,须侦听load事件。将script添加到head中比添加到body中更保险。web

//动态添加脚本,当脚本下载完成且准备就绪后执行回调函数。(这也是推荐的无阻塞的方法)
function loadScript(url,callback){
    var script=document.creatElement('script');
    script.type='text/javascript';
    if(script.readyState){ //IE
        script.onreadystatechange=function(){
            if(script.readyState == 'loaded' || script.readyState == 'complete'){
                script.onreadystatechange=null;
                callback();
            }
        }
    }else{  //非IE
        script.onload=function(){
            callback();
        }
    }

    script.src=url;
    document.getElementsByTagName('head')[0].appendChild(script);

}

数据存取

将全局变量存储到局部变量中:由于全局变量老是存在于执行环境做用域链的最末端,因此,访问全局变量是最慢的,访问局部变量是最快的。尤为是对于未优化过的JavaScript引擎。ajax

在JavaScript中,只有2个语句能够在执行时临时改变做用域链:with语句和try-catch的catch子句。with语句会使得局部变量位于做用域第二层,会使性能降低,因此应避免使用。try-catch权衡使用(由于可预测的错误说明代码有问题,应及早修复)。正则表达式

尽可能避免使用with,try-catch,eval等动态做用域语句,由于JavaScript引擎没法经过静态分析的方法进行优化。

闭包会影响性能(做用域链加深)和可能致使内存泄漏(IE中)

总结:

  1. 使用对象字面量代替对象

  2. 使用局部变量存储全局变量和对象成员

  3. 尽可能不用with,eval语句,try-catch的catch子句要谨慎使用

  4. 嵌套越深,性能越差,尽可能少用。

DOM编程

DOM和JavaScript是2个独立的功能,只经过API链接,用JavaScript操做DOM天生就慢,因此应尽可能减小用JavaScript操做DOM。

原则:

1.减小访问DOM的次数,把运算尽可能留在ECMAScript这一端处理。
2.innerHTML在绝大多数浏览器中比原生DOM方法要快(最新版的chrome除外),推荐使用。
3.用element.cloneNode()代替document.createElement(),稍快一些。
4.缓存HTML集合的length.

//这会是一个死循环,由于取HTML集合的length会重复执行查询的过程。
    var addDivs=document.getElementsByTagName('div');
    for(var i=0,len=addDivs.length;i<len;i++){
        document.body.appendChild(document.createElement('div'));
    }

5.使用children代替childNodes,由于childNodes会包含文本节点(空格)和注释节点,还须要本身额外过滤这些节点,children已经帮咱们过滤掉这些节点了,并且使用的过滤方法效率很高。
6.原生选择器API:querySelectorAll()和querySelector() ,IE8及以上支持
querySelectorAll()返回的是个nodelist(也是类数组),不是HTML集合(与getElenmentsByTagName等不一样)。
7.减小重绘和重排:
在修改样式的过程当中,最好避免使用下面的属性,由于它们会刷新渲染队列,尽可能少查询下列属性,能够用局部变量缓存结果。

offsetTop,offsetLeft,offsetWidth,offsetHeight,
scrollTop,scrollLeft,scrollWidth,scrollHeight
clientTop,clientLeft,clientWidth,clientHeight
getComputedStyle()  (currentStyle in IE)

8.合并屡次对DOM和样式的修改:

el.style.cssText+=';border-left:2px;';
JavaScript改变class

9.批量修改DOM时,使用document fragment:文档片断是一个轻量级的document对象,它自己就是为了更新和移动节点设计的。

var fragement=document.createDocumentFragment();
var li=document.createElement('li');
fragement.appendChild(li);
document.body.appendChild(fragement);

10.动画中使用绝对定位,使用拖放代理。
11.使用事件委托来减小事件处理器的数量。

ps:我的以为,原生方法和库封装的方法并不冲突,应根据实际状况和我的的技能掌握状况选择最合适的方法。

算法和流程控制

  1. for...in的循环性能最差(由于它须要搜索实例和原型上的全部属性),除非,你须要遍历一个属性数量未知的对象,不然不要使用它。
    更不要用它遍历数组成员。其他的循环性能都差很少。

  2. 倒序循环,把减法操做放到控制条件中,例如:k--,这样只是比较“它是true吗?”速度更快。

  3. forEach()比数组循环慢,若是对性能有极致要求,仍是用数组循环好。

  4. 当判断值多于2个时,使用switch,不然用if-else (数量少时,性能差异不大,可根据我的喜爱使用)。若判断值不少,且没有什么复杂的操做,能够用数组代替switch。
    在JavaScript中,switch使用全等操做符,不会发生类型转换的损耗。

  5. 把最可能出现的条件放在首位。

  6. 调用栈溢出错误基本都是由递归致使的:不正确的终止条件;包含了太多递归,超过了浏览器的调用栈限制。把递归算法改用迭代算法实现是避免调用栈溢出错误的解决方法之一。

  7. 缓存:避免重复性工做,手动实现缓存(Vue源码中就有不少缓存)

function memfactorial(n){
    if(!memfactorial.cache){
        memfactorial.cache={
            '0':1,
            '1':1
        }
    }

    if(!memfactorial.cache.hasOwnProperty(n)){
        memfactorial.cache[n]=n* memfactorial(n-1);
    }

    return memfactorial.cache[n];
  }

字符串和正则表达式

字符串拼接推荐用+ +=,推荐写法:str=str+'one'+"two";(将str写在左侧)

书上说:在大多数浏览器中,Array.prototype.join()比其余字符串链接方法更慢,但在IE7及早期的浏览器中,在合并大量字符串时是最高效的途径。

每一个浏览器都有它本身的正则表达式引擎,它们有着各自的优点。

提升正则表达式效率的方法

  1. 关注如何让匹配更快失败

  2. 正则表达式以简单,必需的字元开始:例如:起始标记是^,特定字符串,[a-z]或者d等,避免以分组或选择字元开头,避免/one|two/顶层分支。

  3. 减小分支数量,缩小分支范围:例如:将cat|bat 替换为:[cb]at ;将red|read 替换为:rea?d 将red|raw 替换为:r(?:ed|aw) 将(.|r|n)替换为:[sS]。

  4. 当分支必不可少时,将经常使用分支放到前面。

  5. 使用非捕获组

  6. 合理使用捕获:若是须要引用匹配的一部分,应用捕获,而后引用那部分

  7. 暴露必须的字元:用/^(ab|cd)/代替/(^ab|^cd)/

  8. 使用合适的量词:贪婪和惰性量词的匹配过程不同,视状况选择使用。

  9. 将正则表达式赋值给变量(以免对正则从新编译)并重用它们。

  10. 将复杂的正则拆分为简单的片断:若是太复杂,能够先用条件判断分割

//去除字符串首尾空格的方法,推荐写法
if(!String.prototype.trim){    //防止覆盖原生方法
        String.prototype.trim=function(){
            return this.replace(/^\s+/,'').replace(/\s+$/,'');
        }
   }

尽管正则很强大,但也不是任什么时候候都要用正则。对于字面量字符串的操做,字符串原生的方法就很快,例如:indexOf,slice,substring等。

其余

  1. 建议定时器最小延迟时间是25ms.小于10ms时,各浏览器表现不一致。

  2. 多个定时器时,用setInterval()代替多个setTimeout()

  3. 使用动态脚本注入(json-p),要当心第三方域代码的安全性。不要把敏感信息编码在json-p中。即使是带有随机URL或作了cookie判断。

  4. 图片信标:只是用来发送简单数据

    //只是建立一个Image对象,并不把img插入DOM中。
    (new Image()).src=url+params.join('&');
  5. 尽量使用JOSN.parse()解析json字符串,该方法能够捕获json字符串中的词法错误,并容许传入一个函数用来过滤或转换解析结果。

  6. ajax类库的局限:ajax类库为了兼容浏览器,因此不能访问XMLHttpRequests的完整功能。例如不能直接访问readystatechange事件,因此要了解原生的写法。
    因此,要知道什么时候使用成熟的类库,什么时候编写本身的底层代码。

  7. 缩短页面的加载时间,页面主要内容加载完成后,再用ajax获取那些次要的文件。(首页优化)

  8. 经过正确设置响应头来缓存JavaScript文件。

  9. 使用位操做,速度快。

i%2   
    //能够改写成位运算 &1 :
    if(i&1){ 
        //奇数
    }else{ 
        //偶数
    }
    //位掩码:后台经常使用的按位打标,
    var ops=op_a | op_b | op_c;  
    if(ops & op_a){ 
        //op_a存在
    }

我的感想

性能提高有多方面:客户端性能,网络状况,服务器性能,在具体解决及分析问题时,要从各个方面考虑,JavaScript代码质量,http请求数也只是其中一部分而已,要全面考虑。在进行优化时,要弄清楚性能瓶颈,而后对症优化。

新看到一篇很棒的文章:
前端性能优化备忘录:https://www.w3ctech.com/topic...

ps:若有不对,欢迎指正。