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 } } }