编写可维护的代码
这是一个老生常谈的话题,一般读代码的时间可能要比你写代码的时间还要多,可维护的代码需要达到如下几点要求。
- 可读性:有缩进、适当的空白等。
- 一致性:编写的代码前后规范必须一致。
- 可预测性:如果你来写,可以按照现有代码编写出一致性的代码。
- 所有代码看起来是一个人所写的。
- 文档化。
尽量避免全局变量
JavaScript使用函数来管理作用域,在函数内使用var定义的变量就成了局部变量,没有使用var定义的变量就成了全局对象(例如window)的一个属性,由于各种库、插件、第三方代码,全局变量很容易被污染,应尽量避免或减少全局变量的使用。
// antipattern
myglobal = "hello";
console.log(myglobal);
console.log(window.myglobal);
console.log(window["myglobal"]);
console.log(this.myglobal);
function sum(x, y) {
// antipattern: implied global,use var result = x + y
result = x + y;
return result;
}
function foo() {
// antipattern: antipattern,same as var a = ( b = 0 ), b is implied global
var a = b = 0;
}
因此得出的最佳实践使用var定义变量(无论全局还是局部),如果一个全局变量没有使用var定义,该全局变量不能算作一个真正的变量,而只是全局对象的一个属性,因为它能够被delete运算符删除,真正的变量是无法被delete删除的(返回false)。
// define trhee globals
var global_var = 1;
// antipattern
global_novar = 2;
(function(){
// antipattern
global_fromfunc = 3;
}());
delete global_var;
delete global_noval;
delete global_fromfunc;
typeof global_val;
typeof global_novar;
typeof global_fromfunc;
在ES5的严格模式中,给未定义的变量赋值会报错。
访问全局变量
除非你重新定义了window,否则一般获取的全局变量就为window,直接调用window还是有一定的风险,属于硬编码,更好的获取全局变量的方式如下。
var global = (function(){
// this always return the global object
return this;
}());
单var模式
该模式是指在函数的最上面将函数中出现的所有变量定义一遍,并加以说明,以后有人想查看某个变量的定义,直接来函数头部找就行,提供的好处有:
- 提供一个公共变量区域,便于查找。
- 避免变量在定义之前使用的问题。
- 使用var定义变量,避免变量变成全局变量。
- 更少代码。
需要注意的是,删除变量时或者增加变量时,要注意逗号和分号的正确使用。
// uses single var pattern
function() {
var a = 1,
b = 2,
c = 1 + 2,
i = 0,
j;
}
变量提升
JavaScript能够让你在函数的任意地方定义变量,这些变量实际上都会在函数的头部(所以叫变量提升)定义一遍。
// antipattern
myname = 'global';
function func() {
console.log(myname);
var myname = 'local';
console.log(myname);
}
func();
// the above code behaves as follows
// antipattern
myname = 'global';
function func() {
// variable hoisting
var myname;
console.log(myname);
var myname = 'local';
console.log(myname);
}
func();
for循环
当使用for循环来遍历arguments或者HTMLCollection之类的对象时,先缓存length,避免每次取length花费的开销。 常见的HTMLCollections,如下: * document.getElementsByName() * document.getElementsByClassName() * document.getElementsByTagName() * document.images * document.links * document.forms * document.forms[0].elements
for (var i = 0, max = document.images.length; i < max; i++) {
// do something
}
上面使用++版的for循环需要两个变量,使用–版的for循环能够减少一个变量(JSHint会警告使用++或–,可以配置不显示此类警告)。
var i, myarr = [];
for (i = myarr.length; i--) {
// do something
}
var myarr = [],
i = myarr.length;
while (i--) {
// do something
}
for-in循环
一个典型的for-in循环如下,该for-in循环跳过了继承的属性和对象中的方法。
for (var key in object) {
// 跳过继承属性
if (!object.hasOwnProperty(key)) continue;
// Object.prototype.hasOwnProperty.call(object, key)
// 跳过方法
if (typeof object[key] === 'function') continue;
// dosomething
}
尽量不要扩展内置对象的原型
尽管给构造函数的原型对象扩展方法是功能强大的体现之一,也正是由于其过于强大的功能,需要限制其使用,如果符合以下条件,可以考虑使用。
- 可预测:符合未来ES规范,例如Array有isArray方法,但是低版本IE不支持,你可以给Array定义一个isArray方法
- 先检测:检测某个功能不存在,然后再添加该功能
- 有说明:对扩展的方法进行详细说明
if (!Array.isArray) {
Array.isArray = function(obj) {
return Object.prototype.toString.call(obj) === "[object Array]" ||
(obj instanceof Array);
};
}
swtich模式
下面的switch语句结果是什么?
var x = 0;
var result;
switch (x) {
case '0':
result = 0; break;
case '1':
result = 1; break;
default:
result = 2;
}
result
上面的代码说明了switch语句中,变量与case中的值比较是严格比较===,因此需要注意做类型转化!
避免使用eval
eval() is evil
,之所以大家给eval这样的评价,部分原因eval能将传递给它的字符串解释成JavaScript执行,执行出来的结果,对于当前环境,可能造成不可预知的影响,所谓能力越大,破坏力也越强,不能不防,见下面的代码。
// antipattern
var property = 'name';
alert(eval("obj." + property));
// preferred
var property = 'name';
alert(obj[property]);
在JavaScript中,如果给new Function()传递字符串,或者给setTimeout和setInterval传递字符串时,会隐式的调用eval来解析字符串,因此要避免这些情况,直接传递函数。
parseInt注意事项
parseInt能够将字符串转化成整数,完整的形式为parseInt(string, radix)
,第二个参数指明了整数的进制,先看下面的代码。
var month = '06';
var year = '09';
month = parseInt(month)
year = parseInt(year)
month = parseInt(month, 10)
year = parseInt(year, 10)
编码习惯
缩进、空格、括号、命名、注释、文档,每个团队有每个团队的习惯,尽量遵守即可。