读书笔记《你不知道的JavaScript上卷》1.2词法作用域

时间:2021-10-28 14:49:18

词法作用域

今天的读书笔记是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这样的词法欺骗会使得性能下降,而且使用起来也是相当不安全的,所以我建议大家最好不要使用它们!