==
JavaScript有两组相等的运算符:===和!==
,以及他们邪恶的孪生兄弟==和!=
。===和!==
运算符能够按照你期望的方式工作。如果两个运算数类型一致且拥有相同的值,那么===
就返回true,!==
返回false。但是**==和!=**只有在两个运算符类型一致时才会做出正确的判断,如果两个运算数是不同的类型,他们试图去强制转换值的类型。转换的规则复杂难以记忆。下面的一些有趣的例子:
# 传递性 '0' == 0 # true 0 == '' # true '' == '0' # false 为什么不是true呢 false == 'false' # false false == 0 # true false == undefined # false false == null # false null == undefined # true '\t\r\n' == 0 # true
==
运算符对传递性的缺乏值值得我们警惕。最好永远不要使用那对邪恶的孪生兄弟。相反的,请始终使用===和!==
。如果上面的比较都是用===
运算符,结果都是false,在编程中规定使用,很是受益。
⚠️传递性是一种编程约定。可以理解:对于任意的引用值x、y和z,如果x == y 和 y == z 为 ture,那么 x == z 为true。而JavaScript中的 == 运算符在某种特例上违背了传递性。
with语句
JavaScript提供了一个with语句,本意是想使用它来快捷访问对象的属性。然而,它的结果可能有时不可预料,所以应该避免使用它。
下面的语句:
with (obj){ a = b; }
和下面的代码做的是同样的事情:
if(obj.a === undefined) { a = obj.b === undefined ? b : obj.b; } else { obj.a = obj.b === undefined ? b : obj.b; }
所以,它等于这些语句中的一条:
a = b; a = obj.b; obj.a = b; obj.a = obj.b;
通过阅读代码,你不可能辨别出你会得到的是这些语句的那一条。它可能随着程序运行到下一步时发生变化。它甚至可能在程序运行过程中就发生了变化。如果你不能通过阅读程序就了解它将做什么,你就无法确信它会正确地做你想要做的事情。
with语句在JavaScript中存在,本身就严重影响了JavaScript处理器的速度,因为它阻断了变量名的语法作用域绑定。它的本意是好的,但是如果没有它,JavaScript语言会更好一点。
eval
eval函数传递了一个字符串给JavaScript编译器,并且执行其结果。它是一个被滥用的JavaScript特性。那些对JavaScript语言一知半解的人们最常用到它。例如你知道点表示法,但是不知道下标表示法,就可能会这么写:
eval("myValue = myObject." + myKey + ";");
而不是这么写:
myvalue = myObject[myKey];
使用eval形式的代码更加难以阅读。这种形式使得性能显著降低,因为它需要运行编译器,但也许只是为了执行一个微不足道的赋值语句。它也会让JSLint【⚠️JSLint是一个JavaScript语法检查器和校验器。】失效,让此工具检测问题的能力大打折扣。
eval函数还减弱了你的应用程序的安全性,带来XSS攻击,因为它被求值的文本授予了太多的权力。而且就像with语句执行的方式一样,它降低了语言的性能。
Function 构造器是eval的另一种形式,同样也应该避免使用它。
浏览器提供的setTimeout和setInterval函数,他们能够接受字符串参数或函数参数。当传递的是字符串参数时,setTimeout和setInterval会像eval那样去处理。同样也应该避免使用字符串参数形式。
continue语句
continue语句跳转到循环的顶部。可对代码重构后,性能会得到一定的改善,看下面的代码:
var beginTime = (new Date()).getTime(); var loop = 10000000; for(var i = 0 ; i < loop ; i++){ if(i % 2 == 0){ continue; }else{ console.log(i); } } var endTime = (new Date()).getTime(); console.log('耗费时间:'+ (endTime-beginTime)); # 58625
var beginTime = (new Date()).getTime(); var loop = 10000000; for(var i = 0 ; i < loop ; i++){ if(i % 2 == 0){ }else{ console.log(i); } } var endTime = (new Date()).getTime(); console.log('耗费时间:'+ (endTime-beginTime)); # 58471
var beginTime = (new Date()).getTime(); var loop = 10000000; for(var i = 0 ; i < loop ; i++){ if(i % 2 != 0) console.log(i); } var endTime = (new Date()).getTime(); console.log('耗费时间:'+ (endTime-beginTime)); # 56063
switch穿越
除非是明确中断流程,否则每次条件判断后都穿越到下一个case条件。在使用的时候要小心这种带刺的玫瑰,他们是有用的,也是危险的。
缺少块的语句
If、while、do 或 for 语句可以接受一个括在花括号中的代码块,页可以接受单行语句。单行语句的形式是另一种带刺的玫瑰。它带来的好处是可以节省两个字节,但这是不是一个好处值得商榷。它模糊了程序的结构,使得在随后的操作代码中可能容易插入错误。例如:
if (ok) t = true;
可能变成:
if (ok) t = true; advance();
它看起来就像要这样:
if (ok) { t = true; advance(); }
但是实际上它的本意是这样的:
if (ok) { t = true; } advance(); 或 if (ok) { t = true; } else { advance(); }
貌似是在做一件事情,但实际上是在做另一件事的程序是很难理解清楚的。团队中制定严格的规范要求始终使用代码块是得代码更加容易理解。
++ --
递增和递减运算符使得程序员可以用非常简洁的风格去编码。比如在C语言中,它们使得用一行代码实现字符串的复制成为可能:
for(p = src, q = dest; *p; p++, q++) *q = *p;
事实上,这两个运算符鼓励了一种不够严谨的编码风格。大多数的缓冲区溢出错误所造成的安全漏洞,都是由像这样编码而导致的。
当使用++ 和 --
时,代码往往过于拥挤、复杂和隐晦。因此,作为一条原则,我不再使用它们。团队上也可以规范一波,那样我们的代码风格会变得更加整洁。
位运算符
JavaScript有着和Java相同的一套位运算符:
& and 按位与 | or 按位或 ^ xor 按位异或 ~ not 按位非 >> 带符号的右移动 >>> 无符号的(用0补足的)右移动 << 左位移
在Java中,位运算符处理的是整数。JavaScript没有整数类型,它只有双精度的浮点数。因此,位操作符吧它们的数字运算数先转换成整数,接着执行运算,然后再转换回去。在大多数语言中,这些运算符接近于硬件处理,所以非常快。但JavaScript的执行环境一般接触不到硬件,所以非常慢。JavaScript很少被用来执行位操作。
function语句对比function表达式
JavaScript既有function语句,同时也有function表达式。这令人困惑,因为它们看起来好像是相同的。一个function语句就是其值为一个函数的var语句的速记形式。
下面的语句:
function foo() {}
意思相当于:
var foo = function foo() {}
第二种写法相对友好,因为它明确表示foo是一个包含一个函数值的变量。要学好JavaScript这门语言,理解函数就是数值是很重要的。
function语句在解析时会发生被提升的情况,这意味着不管function被放置在哪里,它会被移动到被定义时所在作用域顶层
。这就放宽了函数必须先声明后使用的要求,这会导致混乱的。在if语句中使用function语句
也是被禁止的。结果表明大多数的浏览器都允许在if语句里使用function语句,但是它们在解析的时候处理上各不相同。这就造成了可移植性的问题。
一个语句不能以一个函数表达式开头,因为官方的语法假定以单词function
开头的语句是一个function
语句。解决的方法就是把函数调用括在一个圆括号中。
(function () { var hidden_variable; # 这个函数可能对环境有一些影响,但是不会映入新的全局变量 })();
类型的包装对象
JavaScript有一套类型的包装对象。例如:
new Boolean(false)
会返回一个对象,该对象有一个valueOf方法会返回被包装的值。这其实完全没有必要,并且有时还令人困惑。不要使用new Boolean、new Number 或 new String
。
此外,页应该避免使用new Object 和 new Array
。可使用{} 和 []
来替代。
new
JavaScript的new运算符创建了一个继承于其运算符的原型的新对象,然后调用该运算数,把新创建的对象绑定给this。这给运算数(它应该是一个构造函数)一个机会在返回给请求者自定义新创建对象。
如果忘记了使用此new运算符
,你得到的就是一个普通的函数调用,并且this被绑定到全局对象,而不是新创建的对象。者意味着当你的函数尝试去初始化新成员属性时它将会污染全局变量。这是一件非常糟糕的事情。而且既没有编译时警告,也没有运行时警告。
按照惯例,打算与new结合使用的函数应该以首字母大写的形式命名,并且首字母大写的形式应该只用来命名那些构造器函数。
这个约定帮助我们进行区分,便于我们发现那些Javascript语言自身经常忽略但是却带来昂贵代价的错误。
一个更好的应对方法策略是根本不去使用new。幻想下就行啦~
void
在很多语言中,void 是一种类型,表示没有值(空值)。而在JavaScript中,void是一个运算符
,它接受一个运算数并且返回undefined。
function getValue(){ a = void ( a = 90 ); document.write('a = ' + a); # a = undefined }
这并没有什么用,而且令人非常困惑。应该避免使用。
参考
《JavaScript语言精粹》Douglas Crockford著 赵泽欣 鄢学鹍 译