文章内容全是来自《你不知道的JavaScript - 上》,记录学习呀呀呀!!!
作用域共有两种主要的工作模型。第一种叫词法作用域,是最为普遍的,被大多数编程语言所采用。另外一种叫作动态作用域,仍有一些编程语言在使用(比如Bash脚本、Perl中的一些模式等)。
词法作用域
定义: 词法作用域是由你在写代码时将变量和块作用域写在哪里来决定的,因此当词法分析器处理代码时会保持作用域不变(大部分情况),也会存在一些特例称为欺骗词法作用域。
下面这段代码及相应的分析,都是来印证上面的观点的。
function foo(a) {
var b = a * 2;
function bar(c) {
console.log(a, b, c);
}
bar(b * 3);
}
foo(2); // 2, 4, 12
为帮助理解,可以将它们想象成几个逐级包含的气泡。
- ❶ 包含着整个全局作用域,其中只有一个标识符:foo。
- ❷ 包含着foo所创建的作用域,其中有三个标识符:a、bar和b。
- ❸ 包含着bar所创建的作用域,其中只有一个标识符:c。
在上一个代码片段中,引擎执行console.log(..)声明,并查找a、b和c三个变量的引用。它首先从最内部的作用域,也就是bar(..)函数的作用域气泡开始查找。引擎无法在这里找到a,因此会去上一级到所嵌套的foo(..)的作用域中继续查找。在这里找到了a,因此引擎使用了这个引用。对b、c同理。
总结: 假如a、b、c都存在于bar(..)函数中,那么都不需要逐级查找了,直接使用这三个变量,这个例子就是说明词法作用域是由写代码时的位置就决定了。
欺骗词法
定义: 是指通过一些技巧来改变 JavaScript 中的词法作用域,从而实现一些特殊的功能。欺骗词法的方法包括 eval 函数、with 语句等
eval
定义:JavaScript中的eval(..)函数可以接受一个字符串为参数,并将其中的内容视为好像在书写时就存在程序中这个位置的代码。
分析: 通过下面代码具体分析:
function foo(str, a) {
eval(str); // 欺骗!
console.log(a, b);
}
var b = 2;
foo("var b = 3; ", 1); // 1, 3
eval(..)调用中的"var b = 3; "这段代码会被当作本来就在那里一样来处理。由于那段代码声明了一个新的变量b,因此它对已经存在的foo(..)的词法作用域进行了修改。事实上,和前面提到的原理一样,这段代码实际上在foo(..)内部创建了一个变量b,并遮蔽了外部(全局)作用域中的同名变量。
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; }
虽然欺骗词法可以实现一些特殊的功能,但是由于会改变词法作用域,因此会导致代码的可读性和可维护性下降,同时也会带来安全性问题。因此,在编写代码时应该尽量避免使用欺骗词法。