原文:You-Dont-Know-JSjavascript
做用域共有两种主要的工做模型:java
词法做用域
。动态做用域
,仍有一些编程语言在使用(好比 Bash 脚本、Perl 中的一些模式等)。词法做用域 和 动态做用域的区别:git
function foo() {
console.log( a ); // 2
}
function bar() {
var a = 3;
foo();
}
var a = 2;
bar();
复制代码
以上代码:词法做用域让 foo() 中的 a 经过 RHS 引用到了全局做用域中的 a,所以会输出 2。 JavaScript只有词法做用域
。github
若是是动态做用域
:由于当 foo() 没法找到 a 的变量引用时,会顺着调用栈在调用 foo() 的地方查找 a,而不是在嵌套的词法做用域链中向上查找。因为 foo() 是在 bar() 中调用的,引擎会检查 bar() 的做用域,并在其中找到值为 3 的变量 a。chrome
须要明确的是,事实上 JavaScript 并不具备动态做用域。它只有词法做用域,简单明了。可是 this 机制某种程度上很像动态做用域。编程
主要区别:词法做用域是在写代码或者说定义时肯定的,而动态做用域是在运行时肯定的。(this 也是!)词法做用域关注函数在何处声明,而动态做用域关注函数从何处调用。浏览器
以上代码的查找过程:安全
在上一个代码片断中,引擎执行 console.log(..) 声明,并查找 a、b 和 c 三个变量的引用。它首先从最内部的做用域,也就是 bar(..) 函数的做用域气泡开始查找。引擎没法在这里找到 a,所以会去上一级到所嵌套的 foo(..) 的做用域中继续查找。在这里找到了 a,所以引擎使用了这个引用。对 b 来说也是同样的。而对 c 来讲,引擎在 bar(..) 中就找到了它。性能优化
若是 a、c 都存在于 bar(..) 和 foo(..) 的内部,console.log(..) 就能够直接使用 bar(..)中的变量,而无需到外面的 foo(..) 中查找。编程语言
在运行时“修改”词法做用域 JavaScript 有两种这样的机制:eval 和 with。(正常应用场景不多,并且会影响性能在代码中应当被避免。)
JavaScript 中的 eval(..)
函数接收一个字符串做为参数值,并将这个字符串的内容看做是好像它已经被实际编写在程序的那个位置上。换句话说,你能够用编程的方式在你编写好的代码内部生成代码,并且你能够运行这个生成的代码,就好像它在编写时就已经在那里了同样。
在 eval(..)
被执行的后续代码行中,引擎 将不会“知道”或“关心”前面的代码是被动态翻译的,并且所以修改了词法做用域环境。引擎 将会像它一直作的那样,简单地进行词法做用域查询。
考虑以下代码:(非 strict 模式)
function foo(str, a) {
eval( str ); // 做弊!
console.log( a, b );
}
var b = 2;
foo( "var b = 3;", 1 ); // 1 3
// 1,执行 foo();
// 2,执行 eval(str);
// 3,在 eval(..) 调用的位置上 生成var b = 3,修改了现存的 foo(..) 的词法做用域
// 4,执行console.log(..) 在foo(..) 的做用域中找到 a 和 b (并不会在全局做用域中查找)
复制代码
strict 模式下会报错:
function foo(str) {
"use strict";
eval(str);
console.log(a); // ReferenceError: a is not defined
}
foo("var a = 2");
复制代码
with
的常见方式是做为一种缩写,来引用一个对象的多个属性,而没必要每次都重复对象引用自己。
例如:
var obj = {
a: 1,
b: 2,
c: 3
};
// 重复“obj”显得更“繁冗”
obj.a = 2;
obj.b = 3;
obj.c = 4;
// “更简单”的缩写
with (obj) {
a = 3;
b = 4;
c = 5;
}
复制代码
泄漏的状况:
function foo(obj) {
with (obj) {
a = 2;
}
}
var o1 = {
a: 3
};
var o2 = {
b: 3
};
foo(o1);
console.log(o1.a); // 2
foo(o2);
console.log(o2.a); // undefined
console.log(a); // 2 -- 哦,全局做用域被泄漏了!
// 1,执行foo(o1) 赋值 a = 2 , 找到属性 o1.a, o1.a = 2
// 2,执行foo(o2) 赋值 a = 2 , o2 没有 a 属性 o2.a = undefined,
//(这里赋值 a = 2 建立了一个全局变量 a,若是 a = 2 加 var 则a属于foo函数的做用域)
复制代码
“做用域” o2
中没有,foo(..)
的做用域中也没有,甚至连全局做用域中都没有找到标识符 a
,因此当 a = 2
被执行时,其结果就是自动全局被建立(由于咱们没有在 strict 模式下)。
JavaScript引擎在编译阶段期行许多性能优化工做。其中的一些优化原理都归结为实质上在进行词法分析时能够静态地分析代码,并提早决定全部的变量和函数声明都在什么位置,这样在执行期间就能够少花些力气来解析标识符。
但若是引擎在代码中发现一个 eval(..)
或 with
,它实质上就不得不假定本身知道的全部的标识符的位置多是无效的,由于它不可能在词法分析时就知道你将会向eval(..)
传递什么样的代码来修改词法做用域,或者你可能会向with
传递的对象有什么样的内容来建立一个新的将被查询的词法做用域。
换句话说,悲观地看,若是 eval(..)
或 with
出现,那么它将作的几乎全部的优化都会变得没有意义,因此它就会简单地根本不作任何优化。
在旧的浏览器中若是你使用了eval,性能会降低10倍。在现代浏览器中有两种编译模式:fast path
和slow path
。fast path
是编译那些稳定和可预测(stable and predictable)的代码。而明显的,eval不可预测,因此将会使用slow path
,因此会慢。
使用with
关键字或者eval(..)
对性能的影响还有一点就是js压缩工具,它没法对代码进行压缩,这也是影响性能的一个因素。
词法做用域意味着做用域是由编写时函数被声明的位置的决策定义的。编译器的词法分析阶段实质上能够知道全部的标识符是在哪里和如何声明的,并如此在执行期间预测它们将如何被查询。
在 JavaScript 中有两种机制能够“欺骗”词法做用域:eval(..)
和 with
。前者能够经过对一个拥有一个或多个声明的“代码”字符串进行求值,来(在运行时)修改现存的词法做用域。后者实质上是经过将一个对象引用看做一个“做用域”,并将这个对象的属性看做做用域中的标识符,(一样,也是在运行时)建立一个全新的词法做用域。
这些机制的缺点是,它压制了引擎在做用域查询上进行编译期优化的能力,由于引擎不得不悲观地假定这样的优化是无效的。这两种特性的结果就是代码将会运行的更慢。不要使用它们。
原文:www.nczonline.net/blog/2013/0…
eval()
这个简单的函数被设计用来执行一个字符串做为JavaScript代码,有几点须要了解:
滥用与性能或安全无关,而是与不理解如何构建和使用JavaScript中的引用有关。假设您有多个表单输入,其名称包含一个数字,例如“option1”和“option2”,一般会看到:
function isChecked(optionNumber) {
return eval("forms[0].option" + optionNumber + ".checked");
}
var result = isChecked(1);
复制代码
在这种状况下,开发人员正在尝试编写,forms[0].option1.checked
但没有意识到如何在不使用的状况下作到这一点eval()
。你会看到这种类型的模式在大约十岁以上的代码中不少,由于当时的开发人员不明白如何正确使用该语言。在eval()
这里使用不合适,由于它不是没必要要的,不是由于它很差。您能够轻松地将此功能重写为:
function isChecked(optionNumber) {
return forms[0]["option" + optionNumber].checked;
}
var result = isChecked(1);
复制代码
eval()
不容易调试。用 chromeDev
等调试工具没法打断点调试,这意味着你将代码运行到一个黑盒子中,而后从中取出。Chrome开发者工具如今能够调试 eval()
编码,但仍然很痛苦。您必须等待代码执行一次后,才会显示在“来源”面板中。避免 eval()
编辑代码使调试变得更加容易,使您能够轻松查看和逐步浏览代码。
上面有提到,使用时性能确实是一个大问题。
若是你正在接受用户输入并eval()
以某种方式传递它,那么你是在寻求麻烦。永远不要这样作。可是,若是您使用的eval()
输入只有您本身控制而且不能被用户修改,那么就没有安全风险。
因此,只要你的信息源不安全,你的代码就不安全。不仅仅是由于eval引发的。