第二章 词法作用域

作用域有两种主要的工作模型:

  • 词法作用域
  • 动态作用域

1.词法阶段

理解词法作用域及其名称来历的基础:

大部分标准语言编译器的第一个工作阶段叫词法化(单词化)。词法化的过程会对源代码中的字符进行检查,如果有状态的解析过程,还会赋予单词含义。

词法作用域:定义在词法阶段的作用域。(词法作用域是由你写代码时将变量和块作用域写在哪里来决定的,当词法分析器处理代码时会保持作用域不变)

2.查找

  • 作用域查找会在找到第一个匹配的标识符时停止。

作用域查找始终从运行时所处的最内部作用域开始,逐渐向外或者向上进行,直到第一个匹配的标识符为止。

  • “遮蔽效应”: 在多层的嵌套作用域中可以定义同名的标识符(内部标识符“遮蔽”了外部的标识符)。

全局变量会自动成为全局对象(浏览器的 window 对象)的属性,因此可以不直接通过全局对象的词法名称,而是间接地通过全局对象属性引用来对其进行访问( window.a )。通过这种技术可以访问那些被同名变量遮蔽的全局变量。但非全局变量如果被遮蔽了,无论如何都无法被访问到。

  • 无论函数在哪里被调用,无论它如何被调用,它的词法作用域都只由函数被声明时所处的位置决定。
  • 词法作用域查找只会查找一级标识符。( foo.bar.baz :词法作用域查找只会试图查找 foo 标识符,找到这个变量后,对象属性访问规则会分别接管 barbaz 属性的访问)

3.欺骗词法

欺骗词法作用会导致新性能下降
欺骗词法有两种机制:evalwith

eval :可以在运行期修改书写期的词法作用域

原理:JavaScript 中的 eval(..) 函数可以接受一个字符串为参数,并将其中的内容视为好像在书写时就存在于程序中这个位置的代码。

执行 eval(..) 之后的代码时,引擎并不“知道”或“在意”前面的代码是以动态形式插入进来,并对词法作用域的环境进行修改的。引擎只会如常地进行词法作用查找。

例子:

function foo(str, a){
    eval(str);   //欺骗!
    console.log(a, b);
}

var b = 2;
foo("var b = 3",1);  // 1,3

由于 eval(str) 声明了一个新的变量 b ,因此它对已经存在的 foo(...) 的词法作用域进行了修改。
上面例子实际上在 foo(..) 内部创建了一个变量 b ,并遮蔽了外部(全局)作用域中的同名变量。

但是在严格模式下,eval(..) 在运行时有其自己的词法作用域,意味着其中的声明无法修改所在的作用域。

拓展:
eval(..) 功能效果相似的函数(以下函数需尽量避免使用):
setTimeout(..)setInterval(..) 的第一个参数可以是字符串,字符串的内容可以解释为一段动态生成的函数代码。
new Function(..) 函数的最后一个参数可以接受代码字符串,并将其转化为动态生成的函数(前面的参数是这个新生成的函数的形参)。

with : 通常被当作重复引用同一个对象中的多个属性的快捷方式,可以不需要重复引用对象本身。

例子:

function foo(obj){
    with(obj){
        a= 2;
     }
}

var o1 = {
    a:3
}

var o2 = {
    b:3
}

foo(o1);
console.log(01.a) // 2

foo(o2);
console.log(02.a) // undefined
console.log(a) // 2  a被泄漏到全局作用域上

传递 o1with 时,with 所声明的作用域是 o1 ,而这个作用域中含有一个同 o1.a 属性相符的标识符。但当我们将 o2 作为作用域时,其中并没有 a 标识符,因此进行了正常的 LHS 标识符查找。
o2 的作用域、 foo(..) 的作用域和全局作用域中都没有找到标识符 a ,因此当 a=2 执行时,自动创建了一个全局变量(非严格模式下)。

with : 可以将一个没有或有多个属性的对象处理为一个完全隔离的词法作用域,因此这个对象的属性也会被处理为定义在这个作用域中的词法标识符。
尽管 with 可以将一个没有或有多个属性的对象处理为词法作用域,因此这个对象的属性也会被处理为定义在这个作用域中的词法标识符。

eval(..) 函数如果接受了含有一个或多个声明的代码,就会修改其所处的词法作用域,而 with 声明实际上是根据你传递给它的对象凭空创建了一个全新的词法作用域。

4. 性能

JavaScript 引擎会在编译阶段进行数项的性能优化。其中有些优化依赖于能够根据代码的词法进行静态分析,并预先确定所有变量和函数的定义位置,才能在执行过程中快速找到标识符。
但如果引擎在代码中发现 eval(..)with',它只能简单地假设关于标识符位置的判断都是无效的,因此无法在词法分析阶段明确知道 eval(...) 会接收到什么代码,这些代码会如何对作用域进行修改,也无法知道传递给with 用来创建新词法作用域的对象的内容到底是什么。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值