javascript代码性能优化

在这篇文章叙述的关于Javascript代码优化,其实并没有什么新意,关于这方面的优化,一直都是Nicholas C. Zakas的专利。我在这里将要说的是对当前众多关于Javascript代码优化的方式一个小总结,并尝试去归类各种不同的优化方式,方便于记忆众说纷纭的优化技巧。本人归类的结果如下图所示:

Javascript代码优化无非主要围绕:DOM操作、循环、闭包、对象重复出现、对象的声明方式、作用域链、字符串操作、类的声明方式等等。循环、闭包、对象重复出现是从作用域链的角度去优化的;DOM操作主要围绕HTMLCollection、NodeList等来优化;对象的声明方式主要是对象(Object)、数组(Array)、字符串(String)、函数(Function)、正则(RegExp)等内置的对象使用字面量的方式来声明,这个比使用new来实例化相应的对象在性能上要强很多;字符串操作的优化方式主要是通过数组的push和join方法;类的声明方式优化方式主要是分清属性和方法的声明的方式,方法使用prototype的方式来声明;Javascript语言本身的流程操作语句的优化(if、switch、with、eval等等)。

因此,我将Javascript代码优化主要分为六类:DOM“真空”空间缩短作用域链字面量声明方式字符串操作类声明方式流程操作语句。下面将逐个叙述:

(全文…)

javascript精巧编写方式

//////////////// 2010-05-25 updates //////////////

var getPointerPosition=function(evt){
  evt = evt || window.event;
  return {
    x:evt.pageX || evt.clientX+(document.documentElement.scrollLeft || document.body.scrollLeft),
	y:evt.pageY || evt.clientY+(document.documentElement.scrollTop || document.body.scrollTop)
  }
}

var getKeyPressed=function(evt){
  evt = evt || window.event;
  return {
    "code":evt.keyCode,
	"value":String.fromCharCode(evt.keyCode)
  }
}

var getTarget=function(evt){
  evt = evt || window.event;
  var target=evt.target || evt.srcElement;
  return target.nodeType===3 ? target.parentNode : target;
}

//////////////// 2010-05-08 updates //////////////

下面的一个addListener函数通过使用三元运算符和Lazy Defined Pattern的方式来编写,感觉有些意思,避免了每次调用该函数都需要浏览器嗅探的判断:

   var addListener=function(elem,type,handle){
     addListener= elem.addEventListener ? function(elem,type,handle){
	    elem.addEventListener(type,handle,false);
	 } : (elem.attachEvent ? function(elem,type,handle){
	    elem.attachEvent("on"+type,handle);
	 } : function(elem,type,handle){
	    elem["on"+type]=handle;
     });
	 addListener(elem,type,handle);
   }

//////////////// 2010-03-25 updates //////////////

//摘自Mootools release-1.1.1 版本库
function $merge(){
        var mix = {};
        for (var i = 0; i < arguments.length; i++){
                for (var property in arguments[i]){
                        var ap = arguments[i][property];
                        var mp = mix[property];
                        if (mp && $type(ap) == 'object' && $type(mp) == 'object') mix[property] = $merge(mp, ap); //递归
                        else mix[property] = ap;
                }
        }
        return mix;
};
//Usage:var mergedObj = $merge(obj1, obj2, obj3);
///////

//不错的自执行函数编写方式
(function(calc) {
	if (ie) {
		core.pageX = function(event) {
			return event.clientX + calc('Left');
		};
		core.pageY = function(event) {
			return event.clientY + calc('Top');
		};
	}
	else {
		core.pageX = function(event) {
			return event.pageX;
		};
		core.pageY = function(event) {
			return event.pageY;
		};
	}
})(function (side) {
	return (element['scroll' + side] || 0) - (element['client' + side] || 0);
});

//////////////// 2010-03-24 updates //////////////

//Javascript中labeled语句的实例,有助于归类代码,划分代码类型。
//Source:http://james.padolsey.com/javascript/labelled-blocks-useful/
var getJSONP = function(url, success){
   declareVars: {
      var ud = "_" + new Date(),
	      script = document.createElement("script"),
		  head = document.head || document.getElementsByTagName("head")[0] || document.documentElement;

   }
   defineCallback: {
      window[ud] = function(data){
         head.removeChild(script);
         delete window[ud];
         success && success(data);
      }
   }
   addScriptToDocument: {
      script.src = url.replace(/callback=.*/, 'callback=' + ud);
      head.appendChild(script);
   }
}
//Usage:
getJSONP("http://www.ilovejs.net/lab/measure/measure-person-v2.js?callback=", function(d){
   alert("success");
});

//////////////// 2010-03-22 updates //////////////

优化循环:
function getElementsByClassName(className,node,tag){
  var results=node.getElementsByTagName(tag);
  var elements=[],length=results.length,classElements=[];
  //将NodeList里的DOM元素保存到数组里
  while(length--) elements[length]=results[length];
  var pattern = new RegExp("(^|\\s)" + className + "(\\s|$)");
  for(var i=0,l=elements.length;i<l;i++){
     if(pattern.test(elements[i].className)){
        classElements.push(elements[i]);
     }
  }
  return classElements;
} 

优化遍历childNodes:nextSibling() better that childNodes[].
function loopChildren(elem){
   var node = elem.firstChild;
   while(node != null){
      node=node.nextSibling;
   }
}
/////worse:not recommended.
function loopChildren(elem){
   var nodes=elem.childNodes();
   var length=nodes.length;
   for(var i=0;i<length;i++){
      var node=nodes[i]; // bad
      ...
   }
}

俺非常喜欢Javascript一些另类的编写方式,也喜欢捣鼓捣鼓,在看一些js库源码的时候,当看到一些出乎意料的编写方式的时候,亦都兴奋不已,不得不佩服:他妈太有才了!!重在思维!!!下面是俺收集和原创的一些捣鼓代码,会陆续更新:

document.getElementsByTagName('body')[0].appendChild((function(){
   var s = document.createElement('script');
   s.type = 'text/javascript';
   s.src = API + encodeURIComponent(tinypath) + '&callback=FireworkDisplay.updateTinyUrl';
   return s;
})());

///////////////////////////////////////////////////

var typeOf = (function(){
    var toString = Object.prototype.toString;
    return function(a,b) {
         return toString.call(a).match(/\s(.+)\]/)[1].toLowerCase() === b;
    };
})();

///////////////////////////////////////////////////

var setHTML=function(id,html){
    (function(i){
         return typeof i === "string" ? document.getElementById(i) : i;
    )(id).innerHTML=html;
    return arguments.callee;
}
//setHTML("div","a")("div","aa")("div","aaa");

///////////////////////////////////////////////////

//同一个函数三种写法:
function a(){
   (arguments[0] || function() { console.log("it is funny");})();
}

(function(){
   (arguments[0] || function() { console.log("it is funny");})();
}(function(){ console.log("it is funny");});

new function(){  //相当于自执行函数
  (arguments[0] || function() { console.log("it is funny");})();
}

///////////////////////////////////////////////////

Function.prototype.partial = function(){
    var fn = this, args = Array.prototype.slice.call(arguments);
    return function(){
        var arg = 0;
        for (var i = 0; i < args.length && arg < arguments.length; i++)
            if (args[i] === undefined)
                args[i] = arguments[arg++];
        return fn.apply(this, args);
    };
};

///////////////////////////////////////////////////

Function.prototype.bind=function(context){
	var that=this;
	return function(){
		return that.apply(context || that,arguments);
	}
}

/////////////////////////////////////////////////////

Function.prototype.method=function(name,fn){
    this.prototype[name]=fn;
    return this;
}

////////////////////////////////////////////////////
//用于优化数组循环的操作,当每个循环需要花费比较多的时间来执行的时候,尤其有用
function chunk(array, process, context){
    var items = array.concat();   //clone the array
    setTimeout(function(){
        var item = items.shift();
        process.call(context, item);

        if (items.length > 0){
            setTimeout(arguments.callee, 100);
        }
    }, 100);
}

Javascript Show Room

/////////////// 2010-6-26 update /////////////////

鉴于在添加DOM元素的style行内样式的时候有时候可能需要连续插入几个的需求,使用循环插入的方式可能会造成多次的reflow,故一次性的插入可能会好些,不过下面实现的需求比较单一,不能重复后续添加其他的style的属性值,除非重复设置。看具体需求而定吧:

var setStyle=function(elem,s){
  var styles=[];
  if(Object.prototype.toString.call(s) === "[object Object]"){
    for(var k in s){
	  styles.push(k+":"+s[k]);
	}
	s=styles.join(";");
  }
  return elem.setAttribute("style",s) || (elem.style.cssText=s);
}

/////////////// 2010-6-20 update /////////////////

有时候为了实现去除数组重复项的需求,为此写了个小函数,效率也比较可观:

var pureMultilArray=function (arr){
  var o={};
  var a = [];
  for(var i=0,l=arr.length;i < l;i++){
      !(arr[i] in o) && a.push(arr[i]) && (o[arr[i]]="");
  }
  return a;
}

/////////////// 2010-5-21 update /////////////////

下面的extend函数是从baseJS库里面摘取的片段,觉得实现方式不错。本人对它进行了一点小修改:

var extend=function() {
	var argu=arguments;
    function ext(destination, source) {
        for(var property in source) {
            destination[property] = source[property];
        }
    }
    if(argu.length == 2) {
        ext(argu[0], argu[1])
    } else {
        var l = argu.length,i=l-1;
        while(i) { ext(argu[0],argu[i--]);
     }
    }
}
//usage:
extend(obj1,obj2,obj3,obj4); //all extend to obj1

/////////////// 2010-5-14 update /////////////////

鉴于我们需要频繁的操作DOM元素的childNodes,所以编写了一个foreachChildNodes函数,参数depth表示需要处理的childNodes的深度,索引从1开始;参数fn是一个函数,带有每一个childNode的DOM元素作为参数,《测试效果页面》。同时附加上一个foreachAttributes函数作为操作DOM元素的属性:

var foreachChildNodes = function(el, fn, depth){
    var nodes = el.childNodes;
    depth = depth || 1;
    var d = --depth;
    for (var i = 0, l = nodes.length; i < l; i++) {
        var node = nodes[i];
        if (d > 0 && node.childNodes.length > 0) {
            foreachChildNodes(node, fn, d);
        }
        if (node.nodeType === 1) {
            fn(node);
        }
    }
}
//Usage:
foreachChildNodes(document.getElementById("ul"),function(el){
  alert(el.innerHTML);
},2);

var foreachAttributes = function(el, fn){
    var attrs = el.attributes;
    for (var i = 0, l = attrs.length; i < l; i++) {
        var attr = attrs[i];
        fn(attr, attr.nodeValue);
    }
}

/////////////// 2010-3-8 update /////////////////

var LoadJavascript=function(pattern){
   pattern = pattern || "Insert-DOM";
   var loadPattern={
      "XHR-Eval":function(url,callback){
	        url = url+"?noCache="+(new Date()).getTime();
           callback=callback || function(obj){};
	        var xhr=false;
			   if(window.XMLHttpRequest){
			       xhr=new XMLHttpRequest();
			   }else{
			       try{
				        xhr=new ActiveXObject("Msxml2.XMLHTTP");
				    }catch(e){
					     xhr = new ActiveXObject("Microsoft.XMLHTTP");
					 }
			   }
			   xhr.open("GET",url,true);
			   xhr.onreadystatechange=function(){
			       if(xhr.readyState === 4 && ( xhr.status === 200 || xhr.status === 304)){
				         eval(xhr.responseText);
						    callback(xhr);
					  }
			   }
xhr.send(null);
	    },
		"XHR-Injection":function(url,callback){
		      url = url+"?noCache="+(new Date()).getTime();
           callback=callback || function(obj){};
	        var xhr=false;
			   if(window.XMLHttpRequest){
			       xhr=new XMLHttpRequest();
			   }else{
			       try{
				        xhr=new ActiveXObject("Msxml2.XMLHTTP");
				    }catch(e){
					     xhr = new ActiveXObject("Microsoft.XMLHTTP");
					 }
			   }
			   xhr.open("GET",url,true);
			   xhr.onreadystatechange=function(){
			       if(xhr.readyState === 4 && ( xhr.status === 200 || xhr.status === 304)){
				        var script = document.createElement("script");
						   document.getElementsByTagName("head")[0].appendChild(script);
						   script.setAttribute("id",url.match(/\/(\w+)\.js/)[1]);
						   script.setAttribute("type","text/javascript");
						   script.text=xhr.responseText;
						   callback(xhr);
					  }
			   }
xhr.send(null);
		},
		"Iframe":function(){
		    var iframe=document.getElementById("iframe");
			  document.body.appendChild(iframe);
			  iframe.setAttribute("width","0");
			  iframe.setAttribute("height","0");
			  iframe.setAttribute("src",url);
		},
		"Insert-DOM":function(url,callback){
		    url = url+"?noCache="+(new Date()).getTime();
         callback=callback || function(obj){};
		    var script=document.createElement("script");
			  document.getElementsByTagName("head")[0].appendChild(script);
			  script.setAttribute("id",url.match(/\/(\w+)\.js/)[1]);
			  script.setAttribute("type","text/javascript");
			  script.setAttribute("src",url);
			  if(script.onreadystatechange){
			     script.onreadystatechange=function(){
				      if(script.status === "loaded" || script.status === "complete"){
					        callback(script);
					    }
				   }
			  }else{
			     script.onload=function(){
				       callback(script);
				   }
			  }
		}
   }
   return loadPattern[pattern];
}

//Usage:
// LoadJavascript("XHR-Eval")("http://www.ilovejs.net/test.js");

/////////////// 2010-2-27 update /////////////////

var getChildNodesBy = function(elem, method){
    var childs = elem.childNodes;
    var returnChilds = [];
    for (var i = 0, len = childs.length; i < len; i++) {
        var child = childs[i];
        if (method.call(child) && child.nodeType === 1) {
            returnChilds.push(child);
        }
    }
    return returnChilds;
}

/////////////// 2010-2-26 update /////////////////

var createElementWithName = function(tag, name){
    try {
        return elem = document.createElement('<' + tag + ' name="' + name + '"></' + tag + '>');
    }
    catch (e) {
        elem = document.createElement(tag);
        elem.setAttribute("name", name);
    }
    return elem;
}

/////////////// 2010-2-21 update /////////////////

//得到一个由参数n指定长度的随机字符串,可以自定义种字符串seek。
var randomString = function(n, seek){
    seek = seek || "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    var rand = "";
    var l = seek.length;
    for (var i = 0; i < n; i++) {
        rand += seek[Math.floor(Math.random() * l)];
    }
    return rand;
}

///////////////////////////////////////////////////////////////////

喜欢show和read代码的你是否愿意展示自己平时积累的认为自豪的代码片段呢?来吧,在这里展示一下你的code的特色,比拼一下,狠批一下,臭骂一下,鄙视一下吧,不要吝啬你的代码。

(全文…)