做用域
被定义为一套规则,这套规则用来管理引擎
如何在当前做用域以及嵌套的子做用域中根据标识符名称进行变量查找
。react
做用域共有两种主要的工做模型。第一种是最为广泛的,被大多数编程语言所采用的词法做用域,咱们会对这种做用域进行深刻讨论。另一种叫做动态做用域。git
大部分标准语言编译器的第一个工做阶段叫做词法化(也叫单词化),词法化的过程会对源代码中的字符进行检查,若是是有状态的解析过程,还会赋予单词语义。github
简单地说,词法做用域
就是定义在词法阶段的做用域
。换句话说,词法做用域
是由你在写代码时将变量和块做用域写在哪里来决定的
,所以当词法分析器处理代码时会保持做用域 不变(大部分状况下是这样的)。编程
看下面的例子性能优化
// (1) 包含着整个全局做用域,其中只有一个标识符:foo。
function foo (a) {
// (2) 包含着 foo 所建立的做用域,其中有三个标识符:a、bar 和 b。
var b = a * 2;
function bar (c) {
// (3) 包含着 bar 所建立的做用域,其中只有一个标识符:c。
console.log(a, b, c);
}
bar(b * 3)
}
foo(2);
复制代码
做用域气泡的结构和互相之间的位置关系给引擎提供了足够的位置信息,引擎用这些信息来查找标识符的位置。bash
在上一个代码片断中,引擎执行 console.log(..) 声明,并查找 a、b 和 c 三个变量的引 用。它首先从最内部的做用域,也就是 bar(..) 函数的做用域气泡开始查找。引擎没法在 这里找到 a,所以会去上一级到所嵌套的 foo(..) 的做用域中继续查找。在这里找到了 a, 所以引擎使用了这个引用。对 b 来说也是同样的。而对 c 来讲,引擎在 bar(..) 中就找到 了它。编程语言
做用域查找会在找到第一个匹配的标识符时中止。在多层的嵌套做用域中能够定义同名的 标识符,这叫做“遮蔽效应”
(内部的标识符“遮蔽”了外部的标识符)。抛开遮蔽效应, 做用域查找始终从运行时所处的最内部做用域开始,逐级向外或者说向上进行,直到碰见 第一个匹配的标识符为止。函数
若是词法做用域彻底由写代码期间函数所声明的位置来定义,怎样才能在运行时来“修 改”(也能够说欺骗)词法做用域呢?post
JavaScript 中有两种机制来实现这个目的。社区广泛认为在代码中使用这两种机制并非 什么好注意。可是关于它们的争论一般会忽略掉最重要的点:欺骗词法做用域会致使性能 降低。性能
在执行 eval(..) 以后的代码时,引擎并不“知道”或“在乎”前面的代码是以动态形式插 入进来,并对词法做用域的环境进行修改的。引擎只会如往常地进行词法做用域查找。
function foo (str, a) {
eval(str) // 词法欺骗
console.log(a, b);
// 此处的 b 永远也没法找到外部的b
// 由于此处内部建立了一个 b 而且覆盖了外部的b
}
var b = 2;
foo('var b = 3; ', a)
// 注意 eval 语法在严格模式下有本身的做用域,因此没法修改做用域
复制代码
JavaScript 中另外一个难以掌握(而且如今也不推荐使用)的用来欺骗词法做用域的功能是 with 关键字。能够有不少方法来解释 with,在这里我选择从这个角度来解释它:它如何同 被它所影响的词法做用域进行交互。
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;
}
console.log(obj) // 3, 4, 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——很差,a 被泄漏到全局做用域上了!
复制代码
此段代码为何 o2.a 为 undefined 呢,咱们能够这样理解。
当咱们传递 o1 给 with 时,with 所声明的做用域是 o1,而这个做用域中含 有一个同 o1.a 属性相符的标识符。
但当咱们将 o2 做为做用域时,其中并无 a 标识符,所以进行了正常的 LHS 标识符查找,在非严格模式下 LHS 引用在全局环境下建立了一个 a 而且赋值为 2
eval(..) 和 with 会在运行时修改或建立新的做用域,以此来欺骗其余在书写时定义的词法做用域。
可是,JavaScript 引擎本来会在编译阶段进行数项的性能优化。其中有些优化依赖于可以根据代码的词法进行静态分析
,并预先肯定全部变量和函数的定义位置,才能在执行过程当中快速找到标识符
。
但若是引擎在代码中发现了 eval(..) 或 with,它只能简单地假设关于标识符位置的判断 都是无效的,由于没法在词法分析阶段明确知道 eval(..) 会接收到什么代码,这些代码会 如何对做用域进行修改,也没法知道传递给 with 用来建立新词法做用域的对象的内容到底 是什么。
最悲观的状况是若是出现了 eval(..) 或 with,全部的优化可能都是无心义的,所以最简单的作法就是彻底不作任何优化。