[译]JavaScript中的变量声明:你可以打破的三条规则
原文:http://www.2ality.com/2012/11/var-statement-rules.html
本文提到了在使用var语句时经常被提到的三条规则,然后告诉你什么时候我们可以打破这些规则.在阅读本文之前,你必须已经了解了JavaScript中函数作用域内的var声明是如何工作的 [1] .
1.你可以打破的三条规则
1.1. 要打破的规则:不要把var语句放在代码块中
以常规看法来说,下面的代码是不好的:
// 非常规写法 function foo(x, y) { if (x > y) { var tmp = x; x = y; y = tmp; } ... }
为什么说是不好的: 看到这样的代码,也许有人会认为变量tmp只存在于if语句块中.而实际情况是,变量tmp的声明会被提升,也就是说变量tmp的声明操作实际上是在函数的最开始处,而赋值操作还留在原来语句存在的地方.因此,下面的写法更能反应出JavaScript引擎内部实际上看到的代码(预解析之后):
// 常规写法 function foo(x, y) { var tmp; if (x > y) { tmp = x; x = y; y = tmp; } ... }
反对观点: 从JavaScript语义的角度看,变量tmp不仅仅存在于if语句块中.可是,从概念上讲,tmp被限制在了那个语句块中:别的地方没有用到它,如果有人删除了这个if语句块,tmp的声明操作也应该被删除.这样的话,非常规写法能更好的表现出作者的意图.
对于函数体很长的函数来说,常规写法是对的.可是,一个函数的行数不应该长于5-10行,这种长度下的话,增加概念上的清晰度更值得考虑.
1.2.要打破的规则: 不要把var语句放在循环体中
常规看法告诉我们: 不要像下面这样写.
// 非常规写法 for (var i = 0; i < 10000; i++) { var foo = 1; }
说是下面这样的写法会更快点:
// 常规写法
var foo; for (var i = 0; i < 10000; i++) { foo = 1; }
可是,常规看法是不对的,在一些引擎中,常规写法甚至更慢,这个jsPerf测试就能证明:这两种写法并没有明显的性能差异.
1.3.要打破的规则: 每个函数都使用单一的var语句
常规看法告诉我们:你应该把所有的变量声明都放在函数体开始的地方,而且仅能使用一条var语句.例如:
// 常规写法 var foo = 1, bar = 2, baz = 3;
这么规定有两个原因.首先,这样写可以防止变量提升(hoisting)带来的诡异问题.其次,还可以避免重复的var关键字.
我们前面已经提到一个比防止变量提升更值得考虑的事情.
避免重复的var会产生一个负面效应:就是如果你漏掉一个逗号,你会意外的创建一些全局变量.例如:
var foo = 1 // 没有逗号 bar = 2, baz = 3;
因为在1和bar之间是由换行符分割的,JavaScript引擎会自动在换行符前面插入一个分号 [2]. 因此,后面的两行就成为了一个只包含了bar和baz赋值操作的逗号表达式语句,在非严格模式中,这意味着会创建两个全局变量.如果同样的代码运行在严格模式中 [3]:
(function () { "use strict"; var foo = 1 bar = 2, baz = 3; }());
会抛出异常:我们为一个不存在的变量执行了赋值操作:
ReferenceError: bar is not defined
与其使用单独的var语句声明多个变量,我更推荐下面的方式:
var foo = 1; var bar = 2; var baz = 3;
这种方式有几个优点:
- 忘记标点符号不会引发问题. 如果忘记写分号,自动分号插入(ASI)会帮助你,而不是为难你.
- 更方便修改代码和删除部分变量的赋值操作.
- 不需要缩进.
2. ECMAScript 6
ECMAScript 6中将会引入块级作用域下的变量声明(通过let语句).另外,还会增加解构赋值(destructuring assignment)语法:在赋值运算符左侧可以访问到右侧数据的内部结构.使用解构赋值可以省去在1.1小节中的函数foo中的临时变量的使用,会让代码更简洁:
function foo(x, y) { if (x > y) { [y, x] = [x, y]; } ... }
3. 结论
本文我们讨论了三个关于var语句的,可以打破而且也经常需要打破的规则.和往常一样,不要盲目的听从任何人的建议(包括那些常规的看法以及本文给出的看法),要确保自己真的知道自己在干什么.