javascript代码优化

注释

        代码中的注释很重要,自然也是毋庸置疑的。通常我们会强调代码中注释数量的多少,而轻视了对注释质量的提高。编码是及时添加注释,会给后续代码的维护人员带来很大的便利。但是如果注释不注意更新,或者由于拷贝、粘贴引起的错误的注释,则会误导阅读人员,反而给阅读带来障碍。
        除了注释要及时更新外,我们还应对注释的内容要特别关注。注释要尽量简单、清晰明了,避免使用含混晦涩的语言,同时着重 注释的意义,对不太直观的部分进行注解。JavaScript 的注释有两种"//" 和"/* .... */"。
        建议 // 用作代码行注释,

  /* .... */ 形式用作对整个代码段的注销,或较正式的声明中,如函数参数、功能、文件功能等的描述中。

命名规则

JavaScript 中的标识符的命名规则:

        1、以字母、下划线'_'或美元符号'$'开头

        2、允许名称中包含字母,数字,下划线'_'和美元符号'$'

        3、区分大小写 变量、参数、成员变量、函数等名称均以小写字母开头,构造器的名称以大写字母开头。下划线'_'开头的变量一般习惯于标识私有 / 局部成员。而美元符号'$'开头的变量习惯于标识系统相关。

建议:

        $开头变量表示jquery-dom节点。

        全大写表常量(js无常量,用这种方式区分)。

        普通变量用骆峰命名(treeName)。

        全局变量或闭包变量用骆峰命名(TreeName)。

        函数尽量用动名词组合(如setName)。

        变量尽量用名词。

变量的声明    

        尽管 JavaScript 语言并不要求在变量使用前先对变量进行声明。但我们还是应该养成这个好习惯。这样可以比较容易的检测出那些未经声明的变量,避免其变为隐藏的全局变量,造成隐患。 在函数的开始应先用 var 关键字声明函数中要使用的局部变量,注释变量的功能及代表的含义。每个变量单独占一行,以便添加注释。这是因为 JavaScript 中只有函数的 {} 表明作用域,用 var 关键字声明的局部 变量只在函数内有效,而未经 var 声明的变量则被视为全局变量。

减少页面重绘

        减少页面重绘虽然本质不是JS本身的优化,但是其往往是由JS引起的,而重绘的情况往往是严重影响页面性能的,所以完全有必要拿出来,我们看下面例子:

 <div id="demo"></div>   
 <input type="button" value="效率低" onclick="func1()" />   
 <input type="button" value="效率高" onclick="func2()" /> 
 var str = "<div>这是一个测试字符串</div><div>这是一个测试字符串</div><div>这是一个测试字符串</div><div>这是一个测试字符串</div><div>这是一个测试字符串</div><div>这是一个测试字符串</div><div>这是一个测试字符串</div><div>这是一个测试字符串</div><div>这是一个测试字符串</div><div>这是一个测试字符串</div><div>这是一个测试字符串</div><div>这是一个测试字符串</div><div>这是一个测试字符串</div>";   
 //效率低的   
 function func1(){   
     var obj = document.getElementById("demo");   
     var start = new Date().getTime();   
     for(var i = 0; i < 100; i++){   
         obj.innerHTML += str + i;   
     }   
     var end = new Date().getTime();   
     alert("用时 " + (end - start) + " 毫秒");   
 }   
 //效率高的   
 function func2(){   
     var obj = document.getElementById("demo");   
     var start = new Date().getTime();   
     var arr = [];   
     for(var i = 0; i < 100; i++){   
         arr[i] = str + i;   
     }   
     obj.innerHTML = arr.join("");   
     var end = new Date().getTime();   
     alert("用时 " + (end - start) + " 毫秒");   
 } 

        在例子中,我只是用了100次的循环,因为如果用10000次循环的话,浏览器基本上就卡住不动了,但是即使是100次的循环,我们看看下面的执行结果。

  

        可以看到的是,这简直是一个惊人的结果,仅仅100次的循环,出现了如此之大的差别。这里还要注意的是,一般影响页面重绘的不仅仅是innerHTML,如果改变元素的样式,位置等情况都会触发页面重绘,所以在平时一定要注意这点。

for循环

        for循环是我们经常会遇到的情况,我们先看看下面例子:

 <input type="button" value="效率低" onclick="func1()" />   
 <input type="button" value="效率高" onclick="func2()" /> 
 var arr = [];   
 for(var i = 0; i < 10000; i++){   
     arr[i] = "<div>" + i + "</div>";   
 }   
 document.body.innerHTML += arr.join("");   
     
 //效率低的   
 function func1(){   
     var divs = document.getElementsByTagName("div");   
     var start = new Date().getTime();   
     for(var i = 0; i < divs.length; i++){   
         //"效率低"   
     }   
     var end = new Date().getTime();   
     alert("用时:" + (end - start) + "毫秒");   
 }   
 //效率高的   
 function func2(){   
     var divs = document.getElementsByTagName("div");   
     var start = new Date().getTime();   
     for(var i = 0, len = divs.length; i < len; i++){   
         //"效率高"   
     }   
     var end = new Date().getTime();   
     alert("用时:" + (end - start) + "毫秒");   
 } 

        这里for循环在执行中,第一种情况会每次都计算一下长度,而第二种情况却是在开始的时候计算长度,并把其保存到一个变量中,所以其执行效率要高点,所以在我们使用for循环的时候,特别是需要计算长度的情况,我们应该开始将其保存到一个变量中。

 <input type="button" value="效率低" onclick="func1()" />   
 <input type="button" value="效率高" onclick="func2()" />  
 var arr2 = [];   
 for(var i = 0; i < 10000; i++){   
     arr2[i] = "<div>" + i + "</div>";   
 }   
 //效率低的   
 function func1(){   
     var start = new Date().getTime();   
     for(var i = 0; i < arr2.length; i++){   
         //"效率低"   
     }   
     var end = new Date().getTime();   
     alert("用时:" + (end - start) + "毫秒");   
 }   
 //效率高的   
 function func2(){   
     var start = new Date().getTime();   
     for(var i = 0, len = arr2.length; i < len; i++){   
         //"效率高"   
     }   
     var end = new Date().getTime();   
     alert("用时:" + (end - start) + "毫秒");   
 } 

 

        对于for循环的优化,也有人提出很多点,有人认为用-=1,或者从大到小的方式循环等等,我认为是完全没有必要的,这些优化往往实际情况下根本没有表现出来,换句话说只是计算机级别的微小的变化,但是给我们带来的却是代码的可读性大大的降低,所以实在是得不偿失。

减少作用域链上的查找次数

        我们知道,js代码在执行的时候,如果需要访问一个变量或者一个函数的时候,它需要遍历当前执行环境的作用域链,而遍历是从这个作用域链的前端一级一级的向后遍历,直到全局执行环境,所以这里往往会出现一个情况,那就是如果我们需要经常访问全局环境的变量对象的时候,我们每次都必须在当前作用域链上一级一级的遍历,这显然是比较低效一点的。

避免双重解释

        双重解释的情况也是我们经常会碰到的,有的时候我们没怎么考虑到这种情况会影响到效率,双重解释一般在我们使用eval、new Function和setTimeout等情况下会遇到,我们看看下面的例子:

 <div id="demo"></div>   
 <input id="but1" type="button" onclick="func1()" value="效率低"/>   
 <input id="but2" type="button" onclick="func2()" value="效率高"/>   
 var sum, num1 = 1, num2 = 2;   
 function func1(){   
     var start = new Date().getTime();   
     for(var i = 0; i < 10000; i++){   
         var func = new Function("sum+=num1;num1+=num2;num2++;");   
         func();   
     }   
     var end = new Date().getTime();   
     alert("用时 " + (end - start) + " 毫秒");   
 }   
     
 function func2(){   
     var start = new Date().getTime();   
     for(var i = 0; i < 10000; i++){   
         sum+=num1;   
         num1+=num2;   
         num2++;   
     }   
     var end = new Date().getTime();   
     alert("用时 " + (end - start) + " 毫秒");   
 } 
 var sum, num1 = 1, num2 = 2;   
 function func1(){   
     var start = new Date().getTime();   
     for(var i = 0; i < 1000; i++){   
         eval("sum+=num1;num1+=num2;num2++;");   
     }   
     var end = new Date().getTime();   
     alert("用时 " + (end - start) + " 毫秒");   
 }   
 function func2(){   
     var start = new Date().getTime();   
     for(var i = 0; i < 1000; i++){   
         sum+=num1;   
         num1+=num2;   
         num2++;   
     }   
     var end = new Date().getTime();   
     alert("用时 " + (end - start) + " 毫秒");   
 } 

  双重解释是有一定开销的,所以在实际当中要尽量避免双重解释。

使用一次innerHTML赋值代替构建dom元素

对于大的DOM更改,使用innerHTML要比使用标准的DOM方法创建同样的DOM结构快得多。

var frag = document.createDocumentFragment();
        for (var i = 0; i < 1000; i++) {
            var el = document.createElement('p');
            el.innerHTML = i;
            frag.appendChild(el);
        }
        document.body.appendChild(frag);
        //可以替换为:
        var html = [];
        for (var i = 0; i < 1000; i++) {
            html.push('<p>' + i + '</p>');
        }
        document.body.innerHTML = html.join('');

条件分支

        将条件分支,按可能性顺序从高到低排列:可以减少解释器对条件的探测次数

        在同一条件子的多(>2)条件分支时,使用switch优于if:switch分支选择的效率高于if,在IE下尤为明显。4分支的测试,IE下switch的执行时间约为if的一半。

        使用三元运算符替代条件分支

 if (a > b) {
            num = a;
        } else {
            num = b;
        }
        //可以替换为:
        num = a > b ? a : b;

循环引用

        如果循环引用中包含DOM对象或者ActiveX对象,那么就会发生内存泄露。内存泄露的后果是在浏览器关闭前,即使是刷新页面,这部分内存不会被浏览器释放。

        简单的循环引用:

var el = document.getElementById('MyElement');
        var func = function () {
            //…        }
        el.func = func;
        func.element = el;

但是通常不会出现这种情况。通常循环引用发生在为dom元素添加闭包作为expendo的时候。

function init() {
            var el = document.getElementById('MyElement');
            el.onclick = function () {
                //……            }
        }
        init();

        init在执行的时候,当前上下文我们叫做context。这个时候,context引用了el,el引用了function,function引用了context。这时候形成了一个循环引用。

        下面2种方法可以解决循环引用:        

        1)  置空dom对象

function init() {
            var el = document.getElementById('MyElement');
            el.onclick = function () {
                //……            }
        }
        init();
        //可以替换为:
        function init() {
            var el = document.getElementById('MyElement');
            el.onclick = function () {
                //……            }
            el = null;
        }
        init();

        将el置空,context中不包含对dom对象的引用,从而打断循环应用。

        如果我们需要将dom对象返回,可以用如下方法:

 function init() {
            var el = document.getElementById('MyElement');
            el.onclick = function () {
                //……            }
            return el;
        }
        init();
        //可以替换为:
        function init() {
            var el = document.getElementById('MyElement');
            el.onclick = function () {
                //……            }
            try {
                return el;
            } finally {
                el = null;
            }
        }
        init();

        2)  构造新的context

function init() {
            var el = document.getElementById('MyElement');
            el.onclick = function () {
                //……            }
        }
        init();
        //可以替换为:
        function elClickHandler() {
            //……        }
        function init() {
            var el = document.getElementById('MyElement');
            el.onclick = elClickHandler;
        }
        init();

        把function抽到新的context中,这样,function的context就不包含对el的引用,从而打断循环引用。

释放dom元素占用的内存

        将dom元素的innerHTML设置为空字符串,可以释放其子元素占用的内存。

        在rich应用中,用户也许会在一个页面上停留很长时间,可以使用该方法释放积累得越来越多的dom元素使用的内存。

释放javascript对象

        在rich应用中,随着实例化对象数量的增加,内存消耗会越来越大。所以应当及时释放对对象的引用,让GC能够回收这些内存控件。

        对象:obj = null

        对象属性:delete obj.myproperty

        数组item:使用数组的splice方法释放数组中不用的item

  函数节流(throttle)与函数去抖(debounce)

  函数去抖:

  如果用手指一直按住一个弹簧,它将不会弹起直到你松手为止。

  也就是说当调用动作n毫秒后,才会执行该动作,若在这n毫秒内又调用此动作则将重新计算执行时间。

var debounce = function(idle, action){
  var last
  return function(){
    var ctx = this, args = arguments
    clearTimeout(last)
    last = setTimeout(function(){
        action.apply(ctx, args)
    }, idle)
  }
}

  函数节流:

  如果将水龙头拧紧直到水是以水滴的形式流出,那你会发现每隔一段时间,就会有一滴水流出。

  也就是会说预先设定一个执行周期,当调用动作的时刻大于等于执行周期则执行该动作,然后进入下一个新周期。

var throttle = function(delay, action){
  var last = Date.now();
  return function(){
    var curr = Date.now();
    if (curr - last > delay){
      action.apply(this, arguments)
      last = curr 
    }
  }
}

 

posted @ 2017-11-02 19:54  诸葛正当年  阅读(388)  评论(0编辑  收藏  举报