第二章
词法作用域
JS语言是词法作用域(除此之外有的语言是动态作用域),其意思是作用域由你写的代码(变量、块作用域)所在的位置来决定的,在词法分析器处理代码时会保持作用域不变(有些例外情况)
由于作用域嵌套的规则,使得找到指定的标识符(变量)需要从当前的作用域开始找,逐级向上进行查找,当查找到变量后便停止查找
遮蔽效应:根据刚才规则,只要在里层作用域创建相同名字标识符,就可以使外层作用域的变量不会被访问到。但是由于声明的全局变量会自动成为全局对象(比如window对象)的属性,所以可以直接通过全局对象的属性来访问到,除此之外的作用域变量如果被遮蔽就访问不到
欺骗词法
根据词法作用域的规则,其作用域完全由在写代码时所声明的位置来决定,而如果想在运行时修改词法作用域,有如下两个方法(不推荐):
- eval():其可把代码以字符串的形式动态传入进eval函数,而传入声明的变量就像是代码写在eval函数的位置时那样,以此可以修改其作用域(作用域不是在eval函数内部,而是就在eval函数所在的位置)。在严格模式下,eval有自己所在的词法作用域,意味着不能修改所在的作用域了。除此有其他的功能和eval函数类似,比如setTimeOut函数和setInterval函数的第一个参数是可以传递可执行的代码字符串的,但是非常不提倡!以及new Function()的最后一个参数,也是可以传递代码字符串,然后转化为动态函数,虽然比eval安全,但是仍然不提倡
- with:with的出现是为了解决重复引用同一个对象的多个属性的冗余,使用with可以不需要引用对象的本身,比如:
var obj = {
a:1,
b:2,
c:3
}
//重复引用对象
obj.a = 2;
obj.b = 3;
obj.c = 4;
//使用with 快捷
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); //underfined
console.log(a) //2————a被赋值到了全局作用域上了
原因很简单:with是将对象作为一个完全隔离的词法作用域,因此这个对象的属性也会被当做这个词法作用域里面的标识符来处理,但是在其作用域里面的var声明却又不会被添加到with的词法作用域里,而是和with所处的相同函数作用域中。所以在非严格模式下,像上述例子在with中对o2的成员a赋值(LHS查询),但是由于a成员未被声明,所以根据规则隐式自动在全局变量中创建个a变量,值为2
性能
JS引擎会在编译阶段进行数项的性能优化,其中有些优化依赖于代码的词法进行静态的分析,基本能够知道全部标识符在哪里以及是如何声明的,从而能预测在执行过程中如何对它们进行查找。但是如果使用了eval和with等欺骗词法作用域的手段,则无法在编译阶段对作用域查找进行优化,导致代码变慢,所以强烈不建议使用