词法作用域
今天的读书笔记是JavaScript中的词法作用域,希望对大家有所帮助。
2.1 定义
词法作用域: 词法作用域就是定义在词法阶段的作用域,它由写代码时将变量和块作用域写在哪里来决定的。
坦白说这个定义第一句话是废话!重点是后一句话,它说作用域是由变量和块作用域写在哪里来决定的,也就是说它的作用域不是由(通常为函数)调用的地方来决定,而是由作用域声明的地方来决定,即“无论函数在哪里被调用,也无论它如何被调用,它的词法作用域都只是由函数被声明时所处的位置决定”,这里给出下面的例子:
function foo(){
console.log(a);//打印1
}
function bar(){
var a = 2;
foo();
}
var a = 1;
bar();
上述代码很简单,函数bar中定义了一个变量a其值为2并且调用了另一个函数foo,函数foo做的事情就是把a打印了一下。那么最后打印的a的值是多少呢?上述代码已给出了结果,是1。当函数bar调用函数foo的时候,会打印a,结果foo这个函数的作用域中并没有a这个变量,那么根据作用域冒泡原则它会在上一作用域中查找,上面词法作用域定义中说到”词法作用域由作用域定义的地方来决定”,foo定义在全局作用域中,也就是它冒泡会冒到全局作用域中,全局作用域中有a这么个变量,并且值是1,所以它打印1。从本例中可以看出JavaScript中的作用域并不是基于代码中作用域的嵌套。
2.2 浏览器中调用某个全局变量
如果在某个作用域中想要调用全局的某个变量那么该怎么办呢?浏览器中给出了一个window对象,所有的全局变量都是属于该对象的属性。
var a = 3;
console.log(a);//打印3
console.log(window.a);//打印3
2.3 词法欺骗
JavaScript中作用域由其声明的位置来决定,那么有没有不符合这种情况的时候呢?答案是有的,主要有两种情况:一个是eval函数,另一个是with语句。
2.3.1 eval函数
eval函数可以传递一个字符串的参数,然后调用该函数时会把函数的参数当做代码来执行:
eval("alert(123);");//浏览器会弹出123的对话框
现在考虑如下代码:
function foo(str,a){
eval(str);
console.log(a,b);//打印1,3
}
var b = 2;
foo("var b = 3;",1);
上述代码中按照正常的词法作用域的情况,foo函数中并没有声明变量b它会去上一作用域中查找也就是全局作用域,里面有变量b值为2,但是由于eval相当于动态插入了一行代码“var b = 3;”它把b的值变为了3,所以打印了3,这跟正常情况不符。
在严格模式下eval有自己的作用域,它无法修改该函数所在作用域的值:
"use strict"//严格模式
eval("var a = 1;");
console.log(a);//Uncaught ReferenceError: a is not defined
最后八卦一下这个eval函数,它还有一个用途就是解析json报文,将字符串的json报文转换为json对象,现在应该知道它为什么会是这样了吧,因为它会把该字符串的json按照JavaScript代码来处理,而代码中它就是一个json对象。
var a = eval("("+"{name:'javascript'}" + ")");
console.log(a.name);
不过上述代码为什么要加括号呢?如果不加的话JavaScript会把里面的花括号当做语句中的花括号而不能解析json。不过最好不要用这种方法解析,好一点的做法是用JSON对象或JQuery提供的方法。
2.3.2 with语句
with语句通常被当做重复引用同一个对象中的多个属性的快捷方式,可以不需要重复定义引用的对象,具体如下:
var obj = {
a:1,
b:2
};
//重复调用obj的赋值
obj.a = 2;
obj.b = 3;
//使用with语句的赋值
with(obj){
a = 3;
b = 4;
}
with语句同样会产生词法欺骗现象:
function foo(obj){
with(obj){
a = 2;
}
}
var o1 = {
a:3
};
var o2 = {
b:3
};
foo(o1);
console.log(o1.a);//打印2
foo(o2);//注意这里的o2并没有a这么个属性
console.log(o2.a);//打印undefined
console.log(a);//打印2
上述结果很让人费解,为什么是这样呢?其实with实际上是根据你传递给它的对象凭空创建一个新的作用域。对于上述o2对象来说,它没有a这个属性,也就是这个新的作用域中没有这个变量,那么将会向上一作用域中查找该变量,如果找到了那么就使用改变量的值,如果没有找到的话那么继续向上找。上述代码中很显然直到全局作用域都没有找到,那么它就在最外层的作用域创建了一个变量a并赋值为2,于是就有了这种不安全的现象。
由于with的不安全性,在严格模式下with被禁用了。
2.3 性能
像eval和with这样的词法欺骗会使得性能下降,而且使用起来也是相当不安全的,所以我建议大家最好不要使用它们!