1 词法阶段
1.1 什么是词法阶段
- 编译器的第一个工作阶段,即上一章中编译原理的第一步:将字符串拆分成有意义的词法单元的过程。
1.2 什么是词法作用域
- 在词法阶段定义的作用域。也就是说,词法作用域仅由你在书写代码时将变量和块作用域写在哪有关,与它在哪被调用或如何调用无关。
1.3 词法作用域可以更改吗
- 原则上来讲,变量和块作用域在定义时其词法作用域就已经生成了,大部分情况下是不可更改的。但事实上有一些欺骗词法作用域的方法,虽然保持词法作用域根据书写位置生成的自然关系不变是最佳实践。
1.4 在词法作用域中的查找
- 词法作用域的查找会在第一次匹配到之后终止
- 由当前词法作用域开始查找,依次向上查找
- 当词法作用域产生嵌套时,允许创建同名变量,但内部作用域的变量会“屏蔽”外部同名变量。重要的是,除非外部同名变量为全局变量,否则一旦产生“屏蔽”,外部同名变量在内部将无法访问得到
- 词法作用域只会查找一级变量,eg:foo.bar.baz,只会查找一级变量foo,找到之后,对象属性的访问规则会分别接管对bar和baz属性的访问
2 欺骗词法
2.1 eval
- eval()接受一个字符串作为参数,并将其中的内容视为好像在书写时就存在于程序那个位置的代码,eg:
function foo(str) {
eval(str)
console.log(b)
}
var b = 2
foo(“var b = 3”) // b = 3,访问到的是foo作用域的局部变量b
但需要注意的是,eval在严格模式下是有自己的作用域的,eg:
function foo(str) {
“use strict”
eval(str)
console.log(b)
}
foo(“var b = 3“) // b is not defined
- 与eval作用类似的还有setTimeout(…)和setInterval(…),它们的第一个参数都可以是字符串,原理同eval,但出于性能和安全性考虑,已经被淘汰,不要使用!
- new Function(…) 的最后一个参数接受字符串,前面的参数是这个新生成函数的形参
2.2 with
- with常被当作重复引用同一对象下不同属性的快捷方式,eg:
var obj = {
a: 1,
b: 2,
c: 3
}
obj.a = 2
obj.b = 3
obj.c = 4
// 功能等价于
with(obj) {
a = 3,
b = 4,
c = 5
}
- 但with相当于在当前作用域下凭空创建了一个新的作用域,不再遵循对象的属性访问规则,而是将其看成是一个新的作用域下的标识符,遵循LHS或RHS查找规则,eg:
function foo(obj) {
with(obj) {
a = 2
}
console.log(a) // 2
console.log(obj) // {b: 3}
console.log(obj.a) // undefined
}
var o2 = {
b: 3
}
foo(o2)
console.log(o2.a) // undefined,遵循对象的属性访问规则,obj内没有属性a,则隐式创建一个,但值为初始值
console.log(a) // 2,o2中没有属性a,所以with中的a会执行LHS查询,导致全局泄漏,即在全局创建了变量a并赋值为2
eval(…)和with会严重影响JS的性能,不必要的时候不推荐使用
3 小结
- eval(…)可以对一段包含一个或多个声明的“代码”字符串进行演算,并借此修改已经存在的词法作用域(在运行时)
- with的本质是通过将一个对象的引用当作作用域来处理,将对象的属性当作作用域中的标识符来处理,从而创建了一个新的词法作用域(同样是在运行时)
- 欺骗词法的副作用:引擎无法在编译时对作用域进行查找优化,因为它们不定地会改变编译完成后已经存在的作用域,只能默认为优化是无效的,不推荐使用

本文详细介绍了编译器的词法阶段,重点讲解了词法作用域的概念及其查找规则。词法作用域在定义时即已确定,通常不可更改,但在JavaScript中,eval和with可以欺骗词法作用域。eval可动态修改作用域,with则创建新的作用域层,但这两种方法可能导致性能下降和全局变量泄露,因此不推荐使用。理解词法作用域对于优化代码和避免意外的全局变量至关重要。
656

被折叠的 条评论
为什么被折叠?



