作用域共有两种主要模型
- 词法作用域
- 动态作用域
作用域是一套规则,这套规则用来管理引擎如何在当前作用域以及嵌套子作用域中根据标识符名称进行变量查找,这套规则是在代码的编译阶段生成
# 代码执行过程
js代码的整个执行过程分为两个阶段,编译阶段和代码执行阶段
# 编译阶段
编译阶段由编译器完成,将代码翻译成可执行代码,这个阶段作用域规则会确定,编译阶段一般经历三个步骤:
分词/词法分析
这个过程会将字符组成的字符串分解成有意义的代码块,这些代码块被称为词法单元
解析/语法分析
这个过程将词法单元流(数组)转换成一个由元素逐级嵌套所组成的代表了程序语法结构的树,这个树被称为“抽象语法树”(AST)
代码生成
将AST转换为可执行代码的过程被称为代码生成
# 执行阶段
执行阶段由引擎完成,主要任务是执行可执行代码
# 词法作用域
词法作用域就是定义在词法阶段的作用域,换句话说,词法作用域是由你在书写代码时将变量和块作用域写在哪里来决定的
词法作用域意味着作用域是由书写代码时函数声明的位置来决定的
# 查找
作用域查找会在找到第一个匹配的标识符时停止,在多层的嵌套作用域中可以定义同名的标识符(变量),这叫做遮蔽效应(内部的标识符“遮蔽”了外的标识符),作用域查找始终从运行时所处的最内部作用域开始,逐级向上或者向外进行,直到遇到第一个匹配的标识符为止
var a = 11
function foo () {
var a = 10
function bar () {
console.log(a) // 10
}
bar()
}
foo()
2
3
4
5
6
7
8
9
提示
无论函数在哪里被调用,也无论它如何被调用,它的词法作用域都只由函数被声明时所处的位置决定
# 欺骗词法
在运行时修改词法作用域,欺骗词法作用域会导致性能下降
js中有两种可以实现,eval和with
这里只说下eval()
# eval()
js中的eval()函数可以接受一个字符串作为参数,并将其中的内容视为好像在书写时就存在与程序中这个位置的代码,换句话说,可以在你写的代码中用程序生成代码并运行,就好像代码是写在那个位置的一样
在执行eval()之后的代码时,引擎并不知道前面的代码是以动态形式插入进来,并对词法作用域的环境进行修改,引擎只会如往常的进行词法作用域查找
var a = 1
function foo () {
eval("var a = 2, b = 3")
console.log(a, b) // 2 3
}
foo()
2
3
4
5
6
上面例子中eval()中的代码会被当做本来就在那一样处理
提示
如果eval()中包含一个或多个声明(无论是变量还是函数),就会对eval()所处的词法作用域进行修改
在严格模式的程序中,eval()在运行时有其自己的作用域,意味着其中的声明无法修改所在作用域
var a = 1
function foo() {
"use strict"
eval("var a = 2")
console.log(a) // 1
}
foo()
2
3
4
5
6
7
注意
js中还有其他一些功能效果和eval()相似,setTimeout()和setInterval()的第一个参数可以是字符串,字符串的内容可以被解释为一段动态生成的函数代码