可维护性
JS从完成小的网页特效和验证到现在要处理各种复杂的逻辑,页面中代码的数量也是成倍的增长。这就让编写可维护的代码变得越来越重要了。
可维护代码的特点
可理解性
直观性
可适应性
可拓展性
可调试性
代码约定
注释和缩进。
变量命名以名词开始,函数名命名以动词开始,返回布尔值的函数以is开头。
松散耦合
不同语言间的紧耦合
CSS,JS,HTML他们天生就需要交互。有时我们不得不在一个里面修改另一个,这就造成了紧耦合,出了错调试起来非常麻烦。尽量做到分开。
- HTML里不要有JS这个比较好做到。
- JS中有时需要动态添加HTML, 这个尽量交给JSP,PHP等页面来做,最后用JS加进去就好。
- 尽量不要在HTML中写CSS
- JS中修改样式最好使用类的方式,这样所有样式问题都可以追溯到CSS文件中。
事件处理和应用逻辑间的紧耦合
如果将应用逻辑直接写在事件处理函数中,不管是调试还是重用都会很不方便,将应用逻辑提出来,在事件处理中调用会是一个比较好的方法。有这么几个原则:
- 不要将event对象传给其他方法,只传来自event对象中所需的数据
- 任何应用逻辑层面的操作都应该可以在不执行任何事件处理程序的情况下执行
- 任何事件处理程序都应该处理事件,然后将时间转交给应用逻辑
编程实践
尊重对象所有权
在其他语言中,在没有源码的时候,类和对象一般是不可变的,就算可以也是添加属性和方法,不可能覆盖已有的。但是在JS中,一切都可以修改,覆盖,重写,想干啥干啥。这有时就会造成很严重的问题。
尊重对象的所有权就是说不是属于你的对象不要随便修改,即便你觉得这样会很方便。拥有对象的意思是,这些对象由你创建,由你维护。像Array,document这些对象显然不是你的,就不要尝试修改他们。
避免全局变量
var name = "Nicholas";
function sayName(){
alert(name);
}
在这里创建了两个全局变量name和sayName。
这样看起来没什么,但是是有问题的,比如变量name覆盖了window的name属性,其他需要这个属性的功能就会因此获得错误的信息。
var MyApplication = {
name: "Nicholas",
sayName: function(){
alert(this.name);
}
};
MyApplication.sayName();
这样大家都使用一个全局变量把自己封装起来,使用和共存和调试都很方便:
var Exia = {};
Exia.ProJS = {};
Exia.ProJS.EventUtil = {};
Exia.ProJS.CookieUtil = {};
避免与null进行比较
比如:
function sortArray(values){
if (values != null){
values.sort(comparator);
}
}
function sortArray(values){
if (values instanceof Array){
values.sort(comparator);
}
}
这时明显是应该使用下面这种判断更加合理。大多数情况也应该这样判断。如果是引用类型就使用instanceof,基本类型就使用typeof。
使用常量
比如URL,设置的量之类的,放到一个常量里,以后要修改就只需要改一次就好。
var Constants = {
INVALID_VALUE_MSG: "Invalid value!",
INVALID_VALUE_URL: "/errors/invalid.php"
};
重复值
用户界面字符串
URLs
任意可能会更改的值
以上这些值提出来会比较方便
性能
曾经JS是一种解释型语言,执行速度比编译型语言慢的多,Chrome率先实现了将JS编为本地机器码执行的浏览器。后来主流的浏览器都这么做了。即便是这样,我们还是要避免写出低效率的代码。
注意作用域
随着作用域链中作用域的增加,访问当前作用域以外的变量所花的时间也越多,越远越耗时。
避免全局查找
当访问全局变量时就是最慢的,如果在一个局部环境中要多次访问全局的变量,先用一个局部变量把它保存起来比较好。
function updateUI(){
var doc = document;
var imgs = doc.getElementsByTagName("img");
for (var i=0, len=imgs.length; i < len; i++){
imgs[i].title = doc.title + " image " + i;
}
var msg = doc.getElementById("mydiv");
msg.innerHTML = "Update complete.";
}
避免with语句
with起初的目的是为了让开发人员少写点字
function updateBody(){
alert(document.body.tagName);
document.body.innerHTML = "Hello world!";
}
function updateBody(){
with(document.body){
alert(tagName);
innerHTML = "Hello world!";
}
}
function updateBody(){
var body = document.body;
alert(body.tagName);
body.innerHTML = "Hello world!";
}
虽然确实方便了,不过这样会增加作用域链的长度,会更慢,使用新添加局部变量的办法一般可以达到目的。
选择正确方法
和其他语言一样,性能问题的一部分是和用于解决问题的算法或方法有关的。
避免不必要的属性查找
对于变量和数组中元素的访问都是O(1)级别的操作,但是要访问对象属性的操作则是O(n)级别的。
一旦要多次用到同一个对象的同一个属性就建议把它保存起来,这样第一次是O(n),以后的就是O(1)了。
优化循环
减值迭代,从最大值开始减到0,这样的循环条件一般都会比增值迭代要高效。因为每次循环和终止条件对比时减值对比都是和常量0进行对比,而增量都是和一个变量甚至是一个对象的属性做对比,这样每次循环累计起来就会有一定的差距了。
简化终止条件,每次循环都会和终止条件对比,如果你的终止条件直接是一个对象的一个属性,那每次都要进行O(n)的查找。
简化循环体,这个不用说啥了。
展开循环
当循环次数确定时,使用多次函数调用而不是循环会更快。
不过一般循环的次数都是不确定的呢,
在处理大数据集时,可以使用一个叫Duff装置的技术。这个技术将循环拆成每8次一组。在处理大量数据时提升效果显著。毕竟每8次才有一次处理循环的开销。当然数据量小的时候就并不划算了。
var iterations = Math.floor(values.length / 8);
var leftover = values.length % 8;
var i = 0;
if (leftover > 0){
do {
process(values[i++]);
} while (--leftover > 0);
}
do {
process(values[i++]);
process(values[i++]);
process(values[i++]);
process(values[i++]);
process(values[i++]);
process(values[i++]);
process(values[i++]);
process(values[i++]);
} while (--iterations > 0);
其他提升性能的方法
原生方法较快
Switch语句较快
位运算符较快
最小化语句数
简单来讲,就是完成多个操作的单个语句要比完成单个操作的多个语句快。
多个变量声明
var count = 5,
color = "blue",
values = [1,2,3],
now = new Date();
使用数组和对象字面量
优化DOM交互
最小化现场更新
一旦你需要访问的DOM部分是已经显示页面的一部分,那么你就是在进行一次现场更新。每次现场更新都有一个很大的性能惩罚,浏览器要重新计算各种尺寸和属性。所以更新次数要尽可能少,动作要尽可能的小。
比如你要给一个ul添加多个li,那把所有li准备好一起塞到ul里就比一个一个塞要好。
使用innerHTML
用innerHTML来创建DOM节点,不仅写起来舒服,执行起来也快。这个同样,把字符串拼好了再一次性加到页面里,这样才高效。
使用事件代理
就是在整个文档上添加事件,文档中各个事件冒泡到文档上统一处理。
注意HTMLCollection
任何时候访问这种类型的变量都是在文档上进行了一次查询,这是个很昂贵的操作。尤其是在循环中。。。
var images = document.getElementsByTagName("img"),
image,
i,
len;
for (i=0, len=images.length; i < len; i++){
image = images[i];
//其他操作
}
这里既在循环终止条件中避免了对HTMLCollection的多次访问,又在循环体里使用image储存了当前要使用的元素,从而避免了多次访问HTMLCollection
部署
构建过程
我们开发Web应用时JS文件是按照可维护性优先的原则组织的,但是这样会存在一些问题:
- 把带有自己注释的代码放上去,别人就更容易知道你的意图,有可能被人找到漏洞
- 书写代码时我们保证代码简单易读,但是对于性能是不利的。浏览器并不能从空格和各式各样的变量中获得什么有用的信息
所以使用构建工具将多个JS合并压缩就很必要了。这样的工具有很多,挑顺手的用。
验证
JSLint可以在线验证JS代码中的潜在语法错误。
在开发周期中添加这个环节可以作为发现代码潜在问题的办法。
把验证添加到构建过程中是很常用且推荐的做法。
压缩
文件压缩
如果每次都把带有各种空格和注视的JS通过网络传送到浏览器,显然有太多不必要的开销。
在部署前使用压缩工具压缩JS文件也是必要的。
HTTP压缩
对于服务器向浏览器传送文件这个过程,也是可以优化的,服务器和浏览器现在都有压缩和解压缩功能。