首先本文章是http://www.zhangxinxu.com/wordpress/?p=1173 的读书笔记,读者可以自己到鑫旭的空间去阅读原文,这里我对其进行了简化。
可维护的代码的一些要求:
(1)可读的 (2)一致的 (3)可预测的
(4)看上去就像是同一个人写的 (5)已记录
原则一:最小全局变量
JavaScript通过函数管理作用于,在函数内声明的变量值在这个函数内部,函数外面不可用。而全局变量就是在任何函数外面声明的,或者是未声明就直接使用的。
每个js环境都有一个全局对象,当我们在任意的函数外面使用this的时候可以访问到,我们创建的每个全局变量都成了这个全局对象的属性。在浏览器中,为了方便起见,该全局对象有个附加属性叫做window,而且此window对象只想该全局对象本身。
下面的代码显示了如何在浏览器中创建和访问全局变量,当然这是不推荐的做法:
xin = "hello";
console.log(xin);
console.log(window.xin);
console.log(window[‘xin‘]);
console.log(this.xin);
但是全局变量会造成的问题就在于,我们的js应用程序和web页面上的所有代码都共享了这些全局变量,但是它们在同一个全局命名空间,所以当程序的两个不同部分定义同名但是不同作用的全局变量的时候,命名冲突在所难免。
而web页面包含不是该页面开发者所写的代码是很常见的,比如:
(1)第三方的JavaScript库
(2)广告方的脚本代码
(3)第三方用户跟踪和分析代码
(4)不同类型的小组件,比如按钮
为了和其他脚本兼容,尽可能少的使用全局变量是很重要的。而常见的减少全局变量的策略,就是使用命名空间模式或者是函数立即自动执行,如果还想使用全局变量,我们还是建议使用var来声明变量。
由于JavaScript的两个特征,不自觉地创建全局变量特别容易,这两个特性是:
(1)不需要声明就可以使用变量。
(2)JavaScript有隐含的全局概念,意味着我们不声明的任何变量都会成为一个全局对象属性。
比如function sum(x,y) { result = x+y; return result; } 就会声明一个全局变量。
比如function foo(){ var a = b = 0;} 中的a是本地变量,但是b却是全局变量。
当我们使用任务链进行部分var声明的时候,就可能会创建隐式全局变量。
原则二:for循环需要注意
在for循环中,我们可以循环取得数组或者是数组类似对象的值,比如arguments和HTMLCollection对象,通常循环形式如下:
for (var i = 0;i < myarray.length; i++){
//对myarray[i]做的操作
}
上述形式的循环的不足在于每次循环的时候的数组的长度都需要去获取,这会降低我们代码的性能。特别是myarray不是数组,而是一个HTMLCollection对象的时候。
所谓HTMLCollection值得是DOM方法返回的对象,比如:
document.getElementsByName()
document.getElementsByClassName()
document.getElementsByTagName()
还有一些其他的HTMLCollection,这些是在DOM标准之前引进并且现在还在使用的,比如:
document.images :页面上所有的图片元素
document.links :页面所有a标签元素
document.forms :页面上的所有表单
document.forms[0].elements: 页面上第一个表单中的所有域
集合的麻烦在于它们实时查询基本文档(html页面),这意味着每次我们访问任何集合的长度,我们都需要实时查询dom,而dom操作一般都是比较昂贵的。
因此,当我们使用循环获取值得时候,缓存数组(或者集合)的长度是比较好的形式,如下代码:
for( var i = 0 ,max = myarray.length;i < max ; i++){
//其他操作
}
使用单var形式,我们可以这么做:
function looper (){
var i= 0,
max ,
myarray= [];
for(i = 0, max = myarray.length ; i < max; i ++){
//其他操作
}
}
我们通常把for-in循环应用在非数组对象的遍历上,使用for-in进行循环也被称为"枚举".虽然我们也可以通过for-in循环数组,因为在js中数组也可以看成对象,但是不推荐。因为如果数组对象已经被自定义的功能增强,则可能产生逻辑错误。而且在for-in中,属性列表的顺序是不能保证的,最好数组使用正常的for循环,对象使用for-in循环。
原则三:不要扩展内置原型
扩增构造函数的prototype属性是个很强大的方法,但是有时候它太强大了。如果我们增加内置的构造函数,比如Object()、Array()或者Function()的话,它会严重降低可维护性,因为它让我们的代码变得难以预测。
原则四:使用更清晰的switch形式
推荐使用如下的switch形式:
var inspect_me = 0,
result = ‘‘;
switch (inspect_me) {
case 0 :
result = "zero";
break;
case 1:
result = "one";
break;
default:
result = "unknown";
}
上面例子中我们约定的风格规定如下:
(1)每个case和switch对齐,花括号缩进规则除外
(2)每个case中都进行代码缩进
(3)每个case 以break清除结束
(4)避免贯穿,也就是故意忽略break,通常我们不建议忽略break
(5)以default结束switch
原则五:避免隐式类型转换
JavaScript的变量在比较的时候会隐式类型转换,这就是诸如false == 0 或者 "" == 0 返回的结果总是true,为了避免引起混乱的隐式类型转换,我们需要使用===或者!==操作符。
原则六:避免eval()
此方法接收任意的字符串,并且当做JavaScript代码来处理。如果代码是在运行时动态生成的,我们通常不要使用eval。比如我们可以使用方括号的方法来访问动态属性会更好。
变化前的不推荐代码:
var property ="name";
alert(eval("obj."+property));
变化后的推荐代码:
var property = "name";
alert(obj[property]);
同样重要的是,给setInterval(),setTimeout()和Function()构造函数传递字符串,大部分情况下,与使用eval()是类似的,因此要避免。在幕后,JavaScript仍然需要评估和执行我们给程序传递的字符串。
变化前的不推荐代码:
setTimeout("myFunc()",1000);
setTimeout("myFunc(1,2,3)",1000);
变化后的推荐代码:
setTimeout(myFunc,1000);
setTimeout(function () { myFunc(1,2,3);} ,1000);
使用新的Function()构造就类似于eval(),应该小心谨慎。
原则七:parseInt()下的数值转换
使用parseInt(),我们可以从字符串中获取数值,该方法接收另一个基数参数,它经常被忽略,但是不应该。当字符串以"0"开头的时候可能会出问题,比如在ECMAScript 3中,开头为0的字符串会当做8进制处理,不过它在ECMAScript 5中已经改变了。
但是为了避免矛盾,我们建议总是指定基数:
var month ="06",
year = "09";
month = parseInt(month,10);
year = parseInt(year,10);
如果我们忽略了基数参数,比如parseInt(year),返回的值会是0,因为09被当做八进制的话,而09在8进制中不是有效数字。
替换方式是将字符串转换成数字,包括 +"08" 或者 Number("08") 等等。
至于命名规范,写到另一篇博客中了,实在是太拥挤了。