我们活着的每一刻背后都隐藏着成千上万个不一样的瞬间。 ——《忽然七日》
大部分情况下,词法作用域在编译阶段的第一步——词法分析阶段便已经被确定了。不过,也有少数的例外情况,比如说运用作用域欺骗的手法,便能够改变这一现状。
关于词法作用域所需要注意的地方是:作用域查找会在找到第一个匹配的标识符时停止;无论函数在哪里被调用,也无论它如何被调用,它的词法作用域只由函数被声明时所处的位置决定;词法作用域查找只会查找一级标识符,像foo.bar.baz,词法作用域只会查找foo标识符,找到这个变量后,对象属性访问规则会分别接管对bar和baz属性的访问。
通过前段的介绍,我们得知常规情况下,词法作用域完全由函数声明时所处的位置决定,既然是这样的话,那么有办法使得词法作用域所处的位置有运行时所处的状态决定吗?答案是有的,我们可以利用eval()和with()来进行词法作用域欺骗达到在运行时修改词法作用域所处的位置。下面将进行详细介绍,不过丑话说在前头,在程序中大量使用eval()和with()的话,那么将会使得程序的性能出现下滑。
1.关于eval()
JavaScript中的eval函数接收一个字符串,并且将其运行在eval语句所在的位置,这使得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,以至于输出的是1,3而非2,3.
默认情况下,如果eval()中所执行的代码包含有声明时,那么他就会对eval()函数所处的词法作用域进行修改。
注意,在严格模式下eval函数里面执行的函数在自己的作用域里面,意味着其中的声明无法对eval函数所处的作用域进行影响,下面可以看一个例子:
function foo(str){
"use strict";
eval(str);
console.log(a);
}
foo("var a = 2");//ReferenceError
在JavaScript语言中,还有几处地方接收字符串相当于产生函数的效果,他们分别为new Function(),setTimeout(),setInterval(),他们在立执行函数的多种写法一文有略微介绍过。虽然他们可以这样用,但是在实际编程中不推荐使用。
2.关于with
with通常用来被当作重复引用某一个对象的多个属性的时候使用,这样可以避免多次书写这个对象名称的繁琐。但是使用with需要注意的一点时,它有可能会向全局作用域中泄露出变量。下面举一个例子:
var obj = {"a": 0, "b":1, "c": 2};
var obj2 = {"b": 3, "c": 4};
function fun(obj){
with(obj){
a = 1;
b = 2;
c = 3;
}
}
fun(obj);
console.log(obj.a + ' ' + obj.b + ' ' + obj.c);//1 2 3
fun(obj2);
console.log(obj2.a + ' ' + obj2.b + ' ' + obj2.c);//undefined 2 3
console.log(window.a);//1此时a被泄露到了全局作用域中
在看一个例子:
function fun(obj){
with(obj){
var a = 9;
b=0;
}
console.log(a);
};
var obj = {"b":1};
fun(obj);//9
从第一个例子我们可以看出,with可能会泄露出变量到全局作用域里面去,对此我们的解释是:符合作用域查找的规则,分析可得这是一个LHS作用域查找请求,所以根据作用域查找规则,如果在所有除全局作用域的地方都找不到这个变量的话,那么在非严格模式下将会在全局作用域中创建这个变量。所以当with查询的那个对象没有某个属性的时候,那么它将会作为变量名泄露到全局作用域中。从第二个例子我们可以看出:在with代码块中利用var来定义一个变量的话,那么这个变量所属的作用域将会是with()被调用处所在的作用域。很可能这个时候,你会问如果利用let和const定义变量的话还会是这样的吗,答案是当然不会了,利用let和const定义变量的话会强制把变量的作用域定位所在的函数块中。
关于欺骗词法作用域所引起的性能问题:我们知道,JavaScript引擎会在编译阶段进行数项的性能优化。而如果我们在程序中加入了大量的eval()和with(){}的话,那么将会影响引擎对代码优化的一部分工作,使得程序运行起来慢许多。
END