一、毒瘤(Awful Parts)
在毒瘤部分我会展示JavaScript的一些难以避免的问题特性。我们必须知道这些问题并准备好应对的措施。
(一)全局变量(Global Variables)
在JavaScript所有的糟糕特性之中,最为糟糕的一个就是它对全局变量的依赖。如果某些全局变量的名称碰巧和子程序中的变量名相同,那么它们将会相互冲突,可能导致程序无法运行,而且通常难以调试。
共有3种方式定义全局变量:
- 1、在任何函数之外放置一个var语句:
var foo = value; - 2、直接给全局对象添加一个属性。全局对象是所有全局变量的容器。在Web浏览器里,全局对象名为window:
window.foo = value; - 3、直接使用未经声明的变量,这被称为隐式的全局变量,这种方式本来是为了方便,有意让变量在使用前无需声明。遗憾的是,不声明变量成为了一个非常普遍的错误,使其成为了全局变量:
foo = value;
(二)作用域(Scope)
在所有类似C语言风格的语言里,一个代码块会创造一个作用域。代码块中声明的变量在其外部是不可见的。JavaScript采用了这样的块语法,却没有提供块级作用域,只有函数作用域:代码块中声明的变量在包含此代码块的函数的任何位置都是可见的。
在大多数语言中,一般来说,声明变量的最好地方是在第一次用到它的地方。但这种做法在JavaScript里反而是一个坏习惯,因为它没有块级作用域。更好的方式是在每个函数的开头部分声明所有变量。
(三)自动插入分号(Semicolon Insertion)
JavaScript有一个自动修复机制,它试图通过自动插入分号来修正有缺损的程序。但是这很有可能会掩盖更严重的错误。有时它会不合时宜地插入分号。比如在return语句中自动插入分号导致的后果。如果一个return语句返回一个值,这个值表达式的开始部分必须和return位于同一行:
return
{status: true};
这看起来是要返回一个包含status成员元素的对象。遗憾的是,自动插入分号让它变成了返回undefined。我们应该这样避免,把{放在上一行的尾部:
return {status: true};
(四)typeof
typeof运算符返回一个用于识别其运算数类型的字符串。所以:
遗憾的是:
返回的是‘object’,而不是‘null’。这简直太糟糕了。其实,有更简单也更好的检测null的方式:
一个更大的问题是检测对象的值。typeof不能辨别出null与对象:
但我们可以像下面这样做,因为null值为假,而所有对象值为真:
if(xxx && typeof xxx === 'object') {
//xxx是一个对象或数组!
}
(五)+
+运算符可以用于加法运算或字符串连接。如果其中一个运算数是一个空字符串,它会把另一个运算数转换成字符串并返回。如果两个运算数都是数字,它返回两者之和。否则,它把两个运算数都转换为字符串并连接起来。这个复杂的行为是bug的常见来源。因此,如果你打算用+去做加法运算,请确保两个运算数都是整数。
(六)浮点数
二进制的浮点数不能正确地处理十进制的小数,因此0.1+0.2不能与0.3。
这是JavaScript中最经常被报告的bug,并且它是遵循二进制浮点数算术标准而有意导致的结果。幸运的是,浮点数中的整数运算是精确的,所以小数表现出来的错误可以通过指定精度来避免。
(七)NaN
Not a Number,它表示的不是一个数字,尽管下面的表达式返回的是true:
该值可能会在试图把非数字形式的字符串转换为数字时产生。例如:
值得注意的是,NaN本身也不等于本身:
JavaScript提供了一个isNaN函数,可以辨别数字与NaN:
判断一个值是否可用做数字的最佳方法是使用isFinite函数,因为它会筛除掉NaN和Infinity。遗憾的是,isFinite会试图把它的运算数转换为一个数字,所以,如果值事实上不是一个数字,它就不是一个好的测试。我们可以这样定义自己的isNumber函数:
var isNumber = function isNumber(value) {
return typeof value === 'number' && isFinite(value);
}
二、糟粕
在糟粕部分,我会展示JavaScript一些有问题的特性,但我们很容易就能避免它们。
(一)==
JavaScript有两组相等运算符:===和!==,以及它们邪恶的孪生兄弟==和!=。===和!==这一组运算符会按照你期望的方式工作。如果两个运算数类型一致且拥有相同的值,那么===返回true,!==返回false。而==和!=只有在两个运算数类型一致时才会做出正确的判断,如果两个运算数是不同类型,它们试图去强制转换值得类型。
因此,建议永远不要使用那对邪恶的孪生兄弟。请始终使用===和!==。如果以上所有的比较使用===运算符,结果都是false。
(二)continue语句
continue语句跳到循环的顶部。一段代码通过重构移除continue语句之后,性能会得到改善。
(三)位运算符
JavaScript有着与Java相同的一套位运算符&,|,~,>>,>>>,<<。
在大多数语言中,这些位运算符接近于硬件处理,所以非常快。但是JavaScript的执行环境一般接触不到硬件,所以非常慢。而且代码的可读性和可维护性很差,建议不要使用。
(四)函数声明VS函数表达式
JavaScript既有函数声明,同时也有函数表达式。一个函数声明就是其值为一个函数的var语句的速记形式。
下面的语句:
function foo() {}
意思相当于:
var foo = function foo() {};
我们最好用函数表达式,因为它能明确表示foo是一个包含一个函数值的变量。要用好这门语言,理解函数就是数值很重要。
函数声明在解析时会发生被提升的情况。这意味着不管function被放置在哪里,它会被移动到被定义时所在作用域的顶层。这放宽了函数必须先声明后使用的要求,这通常会导致混乱。在if语句中使用function语句也是被禁止的,因为各个浏览器在解析时的处理各不相同。
(五)类型的包装对象
JavaScript有一套类型的包装对象。例如:
这样返回一个对象,该对象有一个valueOf方法会返回被包装的值。这其实完全没有必要,并且有时还令人困惑。不要使用 new Boolean,new Number,new String。
此外也请避免使用new Object以及new Array。可使用 {} 和 [] 来代替。
(六)void
在很多语言中,void是一种类型,表示没有值。而在JavaScript里,void是一个运算符,它接受一个运算数并返回undefined。这没有什么用,而且令人非常困惑。应避免使用它。