可维护性、性能及部署

可维护性

可维护的代码一般遵循以下特点

  • 可理解性:其他人可以接受代码并理解它的意图和一般途径
  • 直观性:代码中的东西一看就能明白,而不管其操作过程多么复杂
  • 可适应性:代码以一种数据上的变换不要求完全重写的方法撰写
  • 可扩展性:在damageSumarry结构上已经考虑到在未来允许对核心功能进行扩展
  • 可调试性:当有地方出错时,代码可以给予你足够的信息来尽可能直接地确定问题所在

代码约定

由于JavaScript的可适应性,代码约定对它也很重要。由于和大多数面向对象语言不通,JavaScript并不强制开发人员将所有东西都定义为对象。

  • 可读性:一部分和代码缩进相关。一部分是注释。比如要在函数和方法、大段代码、复杂的算法和Hack处进行注释。
  • 变量和函数命名:变量名应该是名词。函数名应该以动词开始。变量和函数都应使用合乎逻辑的名字,长度问题可以通过后处理和压缩来缓解。一般第一个词为小写,以后的词都是大写。
  • 变量类型透明:一种是定义的时候初始化,则知道它是什么类型。一种是在变量名前加一个标记,比如bFound(布尔型)、iCount(整数)。最后一种是使用类型注释。

松解耦合

只要应用的某个部分过于依赖另一部分,代码就是耦合过紧,难于维护。典型的问题如:对象直接引用另一个对象,并且修改其中一个的同时需要修改另外一个。显示问题的唯一来源应该是CSS,行为问题的唯一来源应该是JavaScript。

  • 解耦HTML/JavaScript:HTML是数据,JavaScript是行为。当JavaScript用于插入数据时,尽量不要直接插入标记。一般可以在页面中直接包含并隐藏标记,然后等到整个页面渲染好之后,就可以用JavaScript显示该编辑,而非生成它。更改行为只需要在JavaScript中进行,而更改标记则只要在渲染文件中。
  • 解耦CSS/JavaScript:要更改样式时,不应该直接更改属性,应该更改元素的类名,达到改变样式的目的,可实现CSS对JavaScript的松散耦合。还要避免在CSS中使用表达式。
  • 解耦应用逻辑/事件处理程序:这两者分别处理各自的东西,一个事件处理程序应该从事件对象中提取相关信息,并将这些信息传送到处理应用逻辑的某个方法中。不能讲event对象传给其他方法,只传来自event对象所需的数据;任何可以在应用层面的动作都应该可以再不执行任何事件处理程序的情况下进行;任何事件处理程序都应该处理事件,然后将处理转交给应用逻辑。

其他

  • 尊重对象所有权。不修改不属于自己的对象。
  • 避免全局变量。最多创建一个全局变量,让其他对象和函数都存在其中。
  • 避免与null进行比较,容易出错。而应该使用instanceof、typeof等。
  • 使用常量。关键在于将数据和使用它的逻辑进行分离。

性能

注意作用域

随着作用域链中的作用域数量的增加,访问当前作用域以外的变量的时间也在增加。访问全局变量总是比访问局部变量蛮,因为需要遍历作用域链。只要能减少花费在作用域链上的时间,就能增加脚本的整体性能。总的来说,就是使用局部变量代替全局变量,减少全局变量使用次数。

1
2
3
4
5
6
7
8
9
10
function updateUI(){  //要避免全局查找
var doc = document; //将document对象存在本地的doc变量中,所以只有一次全局查找
var imgs = doc.getElementByTagName("img");
for(var i=0,len=imgs.length;i < len; i++){
imgs[i].title = doc.title + "image" + i;
}
var msg = doc.getElementById("msg");
msg.innerHTML = "update complete";
}
//此外,还要避免with语句。因为with语句会创建自己的作用域。

选择正确的方法

在计算机科学中,算法的复杂度使用O符号来表示。要选择最简单、最快捷的算法。使用变量和数组要比访问对象上的属性更有效率,前者是O(1),后者为O(n)。所以,属性查找越多,执行时间越长。

对于循环来说,如果循环的次数是确定的,消除循环并使用多次函数调用往往更快。

性能优化过程中很重要的一部分还有优化循环。一个循环的基本优化步骤如下:

  • 减值迭代:在很多情况下,从最大值(特定值)开始,在循环中不断减值的迭代器更加高效
  • 简化终止条件:由于每次循环都要计算终止条件,所以要避免属性查找和其他复杂度高的操作。
  • 简化循环体:确保没有某些可以被很容易移除循环的密集计算。
  • 使用后测试循环:使用do-while这种后测试循环,可以避免最初终止条件的计算。
1
2
3
4
var query = window.location.href.substring(window.location.href.indexof("?)); //6次属性查找

var url = window.location.href;
var query = url.substring(url.indexof("?")); //4次属性查找

最小化语句数

多个变量声明可以写成一个语句。在插入迭代值的时候,尽可能合并语句。尽量使用字面量而不是构造函数来创建数组和对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var count = 5,
color = "blue",
now = new Date(); //合并变量声明语句

var name = values[i++]; //插入迭代值时,合并语句

var person = {
name:"liming",
age:20
}; //应该使用这种字面量的方式创建对象或数组

var person = new Object(); //这种方式不好。这种问题要重点关注
person.name = "liming";
person.age = 20;

优化DOM交互

在JavaScript各个方面中,DOM毫无疑问是最慢的一部分。DOM操作与交互要消耗大量时间。

最小化现场更新

一旦需要访问的DOM部分是已经显示的页面的额一部分,那么就是在进行一个现场更新。每一个修改,都有一个性能惩罚,因为浏览器要重新计算无数尺寸已进行更新。所以要进行减少现场更新次数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var list = document.getElementById("myList"),
fragment = document.createDocumentFragment(),
item,
i;

for(i=0;i<10;i++){
item = document.createElement("li");
fragment.appendChild(item);
item.appendChild(document.createTextNode("Item" + i));
}

list.appendChild(fragment); //只有一次现场更新,而不是10次。

//方法2 使用innerHTML,速度更快
for(i=0;i<10;i++){
html += "<li>Item" +i+ "</li>";
}
list.innerHTML = html;

使用innerHTML

对于小的DOM更改而言,使用createElement()和appendChild(),以及innerTHML来说效率差不多。然而,对于大的DOM更改,使用innerHTML要比使用标准的DOM方法创建同样的DOM结构快得多。当把innerHTML设置为某个值时,后台会创建一个HTML解析器,然后使用内部的DOM调用来创建DOM结构,而非基于JavaScript的DOM调用。由于内部方法是编译好的而非解释执行的,所以执行快得多。

使用事件代理

大多数Web应用在用户交互上大量用到事件处理程序。页面上的时间处理程序的数量和页面相应用户交互的速度之间负相关。为了减轻这种惩罚,最好使用事件代理。

注意HTMLCollection

HTMLCollection对于Web应用的性能而言是巨大的损害。也许优化HTMLCollection访问最重要的地方就是循环了。编写JavaScript的时候,一定要知道何时返回HTMLCollection对象,这样你就可以最小化对他们的访问。发生以下情况时会返回HTMLCollection对象:

  • 进行了对getElementsByTagName()的调用;
  • 获取了元素的childNodes属性;
  • 获取了元素的attributes属性;
  • 访问了特殊的集合,如document.forms、document.images等。

其他

避免解析包含了JavaScript代码的字符串,因为要解析字符串还需要再启动一个解析器,这就是JavaScript代码想解析JavaScript的时候的双重解释乘法。所以要避免出现需要按照JavaScript解释的字符串,比如eval("alert('hello')");
原生方法比较快,所以尽量使用原生方法。因为原生方法是用诸如c/c++之类的编译型语言写出来的。

switch语句较快,如果有一系列复杂的if-else语句,可以转换成单给switch语句。

位运算符比较快,比如取模、逻辑与和逻辑或都可以考虑用位运算来替换。