JavaScript 学习之路--基本规约
Writing Maintainable Code
因为代码维护的成本是巨大的,你思如泉涌的时候,几个小时洋洋洒洒写出来的代码,在将来发生bug或者发生新需求或者需要移植的时候,可能需要一周的时间来阅读,再花费一周的时间来修改。
书写可维护的代码,需要做到以下几点:
- 可读的
- 看上去就像一个人写的
- 有详尽的注释
- 有最新的注释(过时的注释还不如不要注释,会带来更多麻烦)
Minimizing Globals
全局变量是魔鬼。
JavaScript通过函数来管理作用域,在函数内部生命的变量只可以在函数内部使用,反之,就是全局变量。
每个JavaScript环境都有一个全局对象,可以通过this来访问这个全局对象,创建的每一个全局变量,都是这个全局对象的一个属性,在浏览器中,为了方便起见,这个全局对象会有一个附加属性--window,通常this=window。
global = "全局变量" // 创建一个全局变量
console.log(global);
console.log(window.global);
console.log(window["global"]);
console.log(this.global);
// 输出的全部都是“全局变量”
使用var声明变量来避免全局变量
尽管可以使用命名空间模式或者立即执行函数来避免产生全局变量,但是最好的办法还是使用var来定义变量。
但是由于JavaScript的特性,很容易就不自觉的创建出全局变量了。
- 在JavaScript中,变量不需要声明就可以直接使用
- 在JavaScript中,任何不声明的变量,就是全局变量
因此,在以下例子中,很容易就会产生全局变量而不自觉。
var getMax = function(x,y){
if( x > y ){
ret = x ;
}
else {
ret = y ;
}
return ret;
};
getMax(3,4);
alert(ret);
// 在这个例子中,因为ret没有被声明,因此,他变成了一个全局变量。
// 最终执行,会alert 4
另一种产生全局变量的情况如下:
var getMax = function(){
var a = b = 0;
};
getMax();
alert(b);
// 以上代码中a是局部变量,而b则是全局变量
// 因为在从右向左的赋值过程中,以上代码实际等于var a = (b=0)
// 因此b变成了全局变量
使用单var模式来避免隐式全局变量
在函数顶部,使用单var语句是比较好用的方法,例如:
var foo = function(){
var a = 0,
b = 1,
c = true,
d = "hello",
e = a + b,
arr = [],
obj = {};
};
//同时,也可以在单var语句的声明中做一些实际的事情例如:
var modifyDom = function(){
var el = document.getElementById("id"),
style = el.style;// 例如这里,取得element的样式
};
隐式全局变量和显示全局变量的区别
var global = 1 ; // 显示定义
global2 = 2 ; // 隐式
function foo () {
global3 = 3;// 隐式
}
delete global ; // false 显示定义的变量不能删除
delete global2; // true 隐式定义的可以删除
delete global3; // true 隐式定义的可以删除
alert (typeof global); // number
alert (typeof global2); // undefined
alert (typeof global3); // undefined
预解析:var的散布问题(Hoisting: A Problem with Scattered vars)
JavaScript中,可以在函数的任何地方进行声明,但是他们好像都在函数的顶部被声明过了(自上而下的运行方式中,即使声明在下方,使用在上方也不会有问题),这个现象称之为hoisting(悬置,置顶解析,预解析)
因此如果不注意这个问题,不在函数顶部单var模式预先声明的话就会遇到一些问题:
temp = "hello";
function foo(){
alert(temp);
var temp = "nihao";
alert(temp);
}
foo();
// 这段代码第一反应时弹出hello 和 nihao
// 但是结果却是undefined 和 nihao
// 这是因为这段代码实际上等于
temp = "hello";
function foo(){
var temp ;
alert(temp);
var temp = "nihao";
alert(temp);
}
foo();
// 在函数顶部先执行了 var temp;
// 这样全局变量就被冲掉了
// 看一下运行结果
temp = "hello";
alert(temp); // hello
function foo(){
alert(temp); // undefined
var temp = "nihao";
alert(temp); // nihao
}
foo();
// 该例子说明在函数内部,顶部的预编译
For循环(Loop)
在for循环中,你可以循环取得数组或是数组类似对象的值,譬如arguments和HTMLCollection对象。通常的循环形式如下:
for (var i = 0; i < myarray.length; i++) {
// 使用myarray[i]做点什么
}
这种形式的循环的问题在于,每次循环的时候,数组的长度都要重新去获取一次,这会降低效率,尤其是当循环的对象不是一个数组而是一个HTML Collections的时候,这也就是为什么采用以下方法进行循环会比较好的原因
for(var i= 0, max = myarray.length; i< max; i++){
// 使用myarray[i]做点什么
}
这样,在整个循环中,只检索了一次数组的长度。
伴随着单var形式,以上代码可以进行以下修改
function loop(){
var i,
max,
myarray= [];
for (i=0,max=myarray.length;i<max;i++){
//使用myarray[i]做点什么
}
}
另外JSLint建议使用i=i+1或者i+=1来代替i++
因为这样会使代码看起来过于复杂,这点个人认为可以无视
以上代码还有两种变化:
var max,myarray=[];
for(max=myarray.length;max--){
//使用myarray[max]来做点什么
}
var myarray=[],
max = myarray.length;
while(max--){
//使用myarray[i]做点什么
}
For in循环(for-in Loop)
for-in循环应该运用在非数组对象的遍历上,使用for-in进行循环也被成为“枚举“。
虽然在技术上可以使用for-in去循环遍历数组,但是并不推荐这样做。
有个很重要的方法:hasOwnProperty,当遍历对象属性时,可以过滤掉原型链上继承来的属性。
如下所示:
var man ={
heads:1,
leg:2,
hands:2
};// 定义一个名为man的对象字面量
if(typeof Object.prototype.clone == "undefined"){
Object.prototype.clone = function(){
//clone
};
}// 在对象原型上增加clone方法
for (var i in man) {
if (man.hasOwnProperty(i)) { // 过滤
console.log(i, ":", man[i]);
}
}
如果不使用hasOwnProperty方法来过滤,那么结果将会是
heads : 1
leg : 2
hands : 2
clone : (){
//clone
}
另一种实用hasOwnProperty的方法如下:
for(var i in man){
if (Object.prototype.hasOwnProperty.call(man, i)) {
// 过滤
console.log(i, ":", man[i]);
}
}
这里还可以使用单var模式,把Object.prototype.hasOwnProperty"缓存"起来
var i,has = Object.prototype.hasOwnProperty;
for(i in man){
if(has.call(man,i)){
//过滤
console.log(i,":",man[i]);
}
}
Switch模式(Switch pattern)
可以通过以下形式写法增强代码健壮性
var inspect_me = 0,
result = '';
switch (inspect_me) {
case 0:
result = "zero";
break;
case 1:
result = "one";
break;
default:
result = "unknown";
}
这个简单的例子中所遵循的风格约定如下:
- 每个case和switch对齐(花括号缩进规则除外)
- 每个case中代码缩进
- 每个case以break清除结束
- 避免贯穿(故意忽略break)。如果你非常确信贯穿是最好的方法,-务必记录此情况,因为对于有些阅读人而言,它们可能看起来是错误的。
- 以default结束switch:确保总有健全的结果,即使无情况匹配。
避免隐式转换
JavaScript的变量在比较的时候会隐式类型转换。这就是为什么一些诸如:false = = 0 或 “” = = 0 返回的结果是true。为避免引起混乱的隐含类型转换,在你比较值和表达式类型的时候始终使用= = =和!==操作符。
比如:
var zero = 0;
if (zero === false) {
// 不执行,因为zero为0, 而不是false
}
// 反面示例
if (zero == false) {
// 执行了...
}
命名规范
以大写字母写构造函数(Capitalizing Constructors)
JavaScript并没有类,但有new调用的构造函数:
var adam = new Person();
//因为构造函数仍仅仅是函数,仅看函数名就可以帮助告诉你这应该是一个构造函数还是一个正常的函数。
//命名构造函数时首字母大写具有暗示作用,使用小写命名的函数和方法不应该使用new调用:
function MyConstructor() {...}
function myFunction() {...}
分割单词
当你的变量或是函数名有多个单词的时候,最好单词的分离遵循统一的规范,有一个常见的做法被称作“驼峰(Camel)命名法”,就是单词小写,每个单词的首字母大写。
对于构造函数,可以使用大驼峰式命名法(upper camel case),如MyConstructor()。对于函数和方法名称,你可以使用小驼峰式命名法(lower camel case),像是myFunction(), calculateArea()和getFirstName()。
要是变量不是函数呢?开发者通常使用小驼峰式命名法,但还有另外一种做法就是所有单词小写以下划线连接:例如,first_name, favorite_bands, 和 old_company_name,这种标记法帮你直观地区分函数和其他标识——原型和对象。
ECMAScript的属性和方法均使用Camel标记法,尽管多字的属性名称是罕见的(正则表达式对象的lastIndex和ignoreCase属性)。
其他命名规范
JavaScript中没有定义常量的方法(尽管有些内置的像Number, MAX_VALUE),所以开发者都采用全部单词大写的规范来命名这个程序生命周期中都不会改变的变量,如:
// 珍贵常数,只可远观
var PI = 3.14,
MAX_WIDTH = 800;
还有另外一个完全大写的惯例:全局变量名字全部大写。全部大写命名全局变量可以加强减小全局变量数量的实践,同时让它们易于区分。
另外一种使用规范来模拟功能的是私有成员。虽然可以在JavaScript中实现真正的私有,但是开发者发现仅仅使用一个下划线前缀来表示一个私有属性或方法会更容易些。考虑下面的例子:
var person = {
getName: function () {
return this._getFirst() + ' ' + this._getLast();
},
_getFirst: function () {
// ...
},
_getLast: function () {
// ...
}
};
在此例中,getName()就表示公共方法,部分稳定的API。而_getFirst()和_getLast()则表明了私有。它们仍然是正常的公共方法,但是使用下划线前缀来警告person对象的使用者这些方法在下一个版本中时不能保证工作的,是不能直接使用的。注意,JSLint有些不鸟下划线前缀,除非你设置了noman选项为:false。
下面是一些常见的_private规范:
使用尾下划线表示私有,如name_和getElements_()
使用一个下划线前缀表_protected(保护)属性,两个下划线前缀表示__private (私有)属性
Firefox中一些内置的变量属性不属于该语言的技术部分,使用两个前下划线和两个后下划线表示,如:__proto__和__parent__。