核心要点
词法作用域是在写代码或者说定义时确定的,而动态作用域是在运行时确定的。
词法作用域关注函数在何处声明,动态作用域关注函数在何处调用。
词法作用域是基于作用域链查询,动态作用域是基于调用栈查询。
Javascript中只有词法作用域,this绑定很像但不是动态作用域。
词法作用域
Javascript在解释执行前会进行编译,编译的第一个阶段叫做“词法化”。
在这个过程中,编译器会扫描源代码并生成词法作用域链。只有使用了 let/const/var 关键字进行声明的变量才会被词法作用域收集。
实际上JS是编译型语言,只不过不是提前编译。
var a = 1 实际上是两个步骤:变量声明和变量赋值,编译阶段根据 var a 进行词法作用域收集,引擎执行阶段首先根据词法作用域查询变量a,然后执行 a = 1 的赋值操作。
词法作用域最重要的特征是它的定义过程发生在代码的书写阶段,所以确定作用域查询结果的最好方式就是看源代码中各级作用域下所有 let/const/var 定义的变量是否存在。
作用域是分层的,有全局作用域,函数作用域,块级作用域等,词法作用域的层级结构也是在定义时决定的。
作用域的规则是内层可以访问外层,外层不能访问内层。
词法作用域关心函数在何处声明,即唯一的因素是声明的位置,声明的位置决定了他所处的作用域及访问权限。
词法作用域链在编译完成之后就不再变化,引擎在执行变量查询时会根据已经创建好的作用域链进行。
动态作用域
动态作用域不关心函数和作用域如何声明以及在何处声明,只关心它们从何处调用。
动态作用域中作用域链是基于调用栈的,如果内部找不到变量引用就会顺着调用栈在上级调用者作用域查找。
动态作用域是变化的,是随着调用者的不同而变化的。
this很像但不是动态作用域,动态作用域是根据调用栈向上查询,this是根据特定绑定规则动态绑定(没有查询过程)。
let a = 2
function foo () {
// 由于遵守词法作用域,变量a的定义位置决定了它所在的foo作用域只能访问到全局作用域中的 let a = 2
// 如果遵守动态作用域,变量a的定义位置无所谓
console.log(a)
}
function bar () {
let a = 3
// 由于遵守词法作用域,foo中不能访问bar中的变量
// 如果遵守动态作用域,foo就会直接访问调用者bar中的变量
foo()
}
bar() // 2