[译]严格模式=>静态作用域
原文:http://domenic.me/2013/03/19/strict-mode-static-scoping/
现在的确是时候使用JavaScript的严格模式了.之所以这么说,是因为使用严格模式的确能带来很多好处,其中最能说服我(使用严格模式)的一点就是:严格模式可以通过确保脚本的静态作用域来让JavaScript的作用域机制更加合理.
简单来说,如果代码的作用域是静态的,那么你就可以通过静态分析来判断出代码中每个标识符真正指向的是哪个值.换句话说,就是静态作用域能够让你知道每个变量是在代码的哪个部位声明的.正如我们下面要讲的,JavaScript的非严格模式不能保证这一点,这就给了我们足够充分的理由来使用"use strict"
.
非严格模式 => 动态作用域
在大多数情况下,JavaScript代码中的作用域情况都是相当简单的,你只需要沿着作用域链(源代码中能一眼看到的静态作用域链)向上查找,如果找不到你要查找的变量,则它只能是全局对象上的一个属性(不报引用错误的前提下).但在非严格模式中,有几种情况会干扰这一算法的正确性.
如果使用了with
使用with
语句会完全破坏代码中作用域链的合理性:
var __filename = "/my/cool/file.js";
var anotherContext = { __filename: "/another/file.js", __dirname: "/another" };
var context = Math.random > 0.5 ? anotherContext : {};
with(context) {
console.log(__filename);
}
在这个例子中,我们无法静态分析出语句console.log(__filename)
中的__filename
标识符指向的是上层作用域中的自由变量__filename
(值为"/my/cool/file.js"
),还是指向了anotherContext
对象的同名属性(值为"/another/file.js"
).
严格模式通过禁用with
语句来修复了这一缺点,因此,如果将上面的代码放在严格模式上下文中,就会报错.
如果使用了eval
在非严格模式中用使用eval
可能会在当前作用域内引入新的变量绑定:
function require(moduleId) {
// 这里省略了具体实现代码. }
function requireStuff() {
if (Math.random > 0.5) {
eval("function require(things) { console.log('We require more ' + things); }");
}
require("minerals");
}
在这个例子中,我们也有和上个例子类似的问题:就是requireStuff
函数中的eval
可能会在当前函数作用域内动态的添加一个新的变量绑定(require
).因此,函数尾部的require
可能会指向那个由eval
在requireStuff
函数内部新建的require
函数,也可能会指向requireStuff
函数的外层作用域中那个已经存在的同名函数.只有到了真正运行的时候,我们才能知道真正的结果是哪个!
严格模式通过禁止eval
创建新的绑定来修复这一情况,因此,如果上面的代码处于严格模式中,则require("minerals")
必定会指向那个我们真正想要的,用来加载模块的require
函数.
这又有什么影响?
静态分析受到限制,除了会对JavaScript引擎中JIT编译器的的优化操作产生明显影响以外,有时还会给我们自己的编码操作带来困难.
比如,你准备写一个能够在浏览器中使用Node.js风格的模块语法的工具.那么你可能需要去检测require
函数的调用,就是当你看到一个require
标识符时,你会想知道这个require
来自哪里,是全局的还是局部的?类似的,你可能还需要检测__filename
, __dirname
, Buffer
,等.
但在非严格模式中,你无法知道某个给定的标识符到底指的是那个值.一个with
或者一个eval
就可能让你的判断错误.正如我们上面给出的例子一样,类似require
或者__filename
这样的标识符,直到运行时才能确定它具体的值是什么.
那么,我们自己不要去用那些东西(eval,with等)不就行了吗?
那些不愿意多打一句"use strict"
的人们常用的理由就是,他们可以简单的不去使用那些不好的特性.是的,这样的确也可以.如果你借助了一些工具比如JSHint帮助你来编写代码,你的确可以得到一个更好的编程环境.
类似的论点也被普遍应用在其他语言上,比如说,"在C++中不要使用异常和模板".甚至告诉别人,"在Node.js的模块中不要将表达式传给require
函数(理由是这样会干扰一个比较流行的名为browserify的JavaScript工具)".
我并不赞同这些理由,因为语言本身就应该给用户提供一些内置的工具来让用户更好的并且更正确的使用这门语言.在JavaScript中,已经有了这样的一个工具,那就是:把一个简单的,并且向后兼用的 "use strict"
编译指示(pragma)放到源文件的顶部就可以了.如果你认为这样还难,那么你应该尝试成为一名C++程序员,编写一下异常安全的代码看看:你需要用到的技术可比一个简单的编译指示多多了.
使用严格模式吧!
Mark Miller(TC39成员)4年前就曾经说过(youtube),ECMAScript 5中的严格模式让JavaScript成为了"很好"的一类编程语言中的一员,让我们使用严格模式吧!