第一部分 编程风格
第4章 变量、函数和运算符
4.1 变量声明
作者建议总是将局部变量的定义作为函数内第一条语句,并且推荐将所有var语句合并为一个语句,每个变量的初始化独占一行,对于那些没有初始值的变量来说,它们应当出现在var语句的尾部。
4.2 函数声明
推荐总是先声明函数然后再使用函数。此外,函数声明不应当出现在语句块之内。
4.3 函数调用间隔
一般情况下,对于函数调用写法推荐的风格是,在函数名和左括号之间没有空格,这样做事为了将它和语句块区分开来。
4.4 立即调用函数
JavaScript中允许声明匿名函数,并将匿名函数赋值给变量或者属性。
这种匿名函数同样可以通过在最后加上一对圆括号来立即执行并返回一个值,然后将这个值赋值给变量。
// 不好的写法
var value = function(){
return {
message: "Hi"
}
}();
在这个例子中,value最终被赋值为一个对象,因为函数立即执行了。这种模式的问题在于,会让人误以为将一个匿名函数赋值给了这个变量,除非读完整段代码看到最后一行的那对圆括号,否则你不会知道将函数赋值给变量还是将函数的执行结果赋值给变量。
为了让立即执行函数能够被一眼看出来,可以将函数用一对圆括号包裹起来,比如:
// 好的写法
var value = (function(){
// 函数体
return {
message: 'Hi'
}
})();
这段代码在第一行就有了一个标识符(左圆括号),表明这是一个立即执行的函数。添加一对圆括号并不会改变代码的逻辑。Crockford的编程规范推荐这种模式,在省略圆括号的情况下JSLing会报警告。
4.5 严格模式
ECMAScript5引入了“严格模式”,希望通过这种方式来谨慎地解析执行JavaScript,以减少错误。
不推荐将“use strict”用在全局作用于中,因为这会让文件中的所有代码都以严格模式来解析,所以如果你将11个文件连接合成一个文件时,当其中一个文件在全局作用于中启用了严格模式,则所有的代码都将以严格模式解析。由于严格模式中的运算规则在非严格模式下的情形有很大不同,因此其他文件中的代码很可能会报错。因此,最好不要在全局作用于中使用“use strict”,来看一些例子。
// 不好的写法-全局的严格模式
"use strict"
function doSomething() {
// 代码
}
// 好的代码
function doSomething() {
"use strict"
// 代码
}
如果你希望在多个文件中应用严格模式而不必写很多行“use strict”的话,可以使用立即执行函数。
// 好的写法
(function() {
"use strict"
function doSomething() {
// 代码
}
function doSomethingElse() {
// 代码
}
})
当“use strict”出现在函数体外,在JSHint和JSLint中都会给出警告。JSLint和JSHint希望所有函数都默认包含“use strict”,当然在这两个工具中可以关闭这个选项,作者推荐尽可能使用严格模式,以减少常见错误的发生。
4.6 相等
由于JavaScript具有强制类型转换机制,JavaScript中的判断相等操作是很微妙的。
发生强制类型转换最常见的场景就是,使用了判断相等运算符==和!=的时候,当要比较的两个值得类型不同时,这两个运算符都会有强制类型转换。
由于强制类型转换的缘故,我们推荐不要使用==和!=,而应当使用===和!==,用这两个运算符作比较不会涉及到强制类型转换。
4.6.1 eval()
在JavaScript中,eval()的参数是一个字符串,eval()会将传入的字符串当做代码来执行。开发者可以通过这个函数来载入外部的JavaScript代码,或者随即生成JavaScript代码并执行它。比如:
eval("alert("Hi!")");
var count = 10;
var number = eval("5 + count");
console.log(count); // 12
在JavaScript中eval()并不是唯一可以执行JavaScript字符串的函数,使用Function构造函数也可以做到这一点,setTimeout()和setInterval()也可以。
默认情况下,对于使用eval()、Function、setTimeout()和setInterval()的情形,JSLint和JSHint都会给出警告。
ECMAScript5严格模式对于eval()有着严格的限制,禁止在一个封闭的作用于中使用它创建新变量或者函数。这条限制帮助我们避免了eval()先天的安全漏洞,然而,如果实在没有别的方法来完成当前任务,这是依然推荐使用eval()。
4.6.2 原始包装类型
JavaScript里有3中原始包装类型:String、Boolean和Number。每种类型都代表全局作用于中的一个构造函数,并分别表示各自对应的原始值得对象。原始包装类型的主要作用是让原始值具有对象般的行为,比如:
var name = "Nicholas";
console.log(name.toUppercase());
尽管name是一个字符串,是原始类型不是对象,但你仍然可以使用诸如toUpperCase()之类的方法,即将字符串当做对象来看待。这种做法之所以行得通,是因为在这条语句的表象背后JavaScript引擎创建了String类型的新实例,紧跟着就被销毁了,当再次需要时就会创建另外一个对象。你可以通过给字符串增加属性来检验这种行为。
var name = "Nicholes";
name.author = true;
console.log(name.author); // undefined
在第2行结束后,author属性就不见了。因为表示这个字符串的临时String对象在第2行执行结束后就销毁了,在第3行中又创建了一个新String对象。同样你也可以自己手动创建这些对象。
// 不好的做法
var name = new String("Nicholes");
var author = new Boolean(true);
var count = new Number(10);
尽管我们可以使用这些包装类型,但作者强烈推荐大家避免使用它们。开发者的思路常常会在对象和原始值之间跳来跳去,这样会增加出Bug的概率,从而是开发者陷入困惑,你也没有理由自己手动创建这些对象。