结论先行:
当在函数内部访问一个变量的时候,(JS 引擎)程序会先在当前作用域中查找(是否存在该变量),如果找到了就直接使用。否则就会向上层作用域逐级查找,直到找到全局作用域为止。这个查找过程形成了作用域链。
在函数中定义的变量也可以被嵌套在其他函数的作用域中,这样就形成了作用域链。
它保证了变量在正确的作用域中被访问和使用。
作用域链呢,有2个作用。一个就是刚说的变量查找,再一个就是闭包实现,通过作用域链,内部函数可以访问外部函数的变量,从而实现闭包。
- 变量查找:当在函数内部引用一个变量时,JavaScript 引擎会在当前函数的作用域中查找该变量,如果找不到,则会沿着作用域链向上查找,直到找到全局作用域。
- 闭包实现:通过作用域链,内部函数可以访问外部函数的变量,从而实现闭包(Closure),使得外部函数的作用域在内部函数执行完毕后仍然可以被引用。
具体解析:
1、什么是作用域链(Scope Chain)?
在JavaScript中,函数存在一个隐式属性 [[ scopes ]],这个属性用来保存当前函数在执行时的环境(上下文),由于在数据结构上是链式的,也称为作用域链。我们可以把它理解为一个数组。
function fn() {}
console.dir(fn) // 打印内部结构
2、作用域的内部结构
内部结构如下:
scopes 属性在函数声明时产生,在函数被调用时更新,记录当前函数的执行环境。
在函数被调用时,将该函数的AO对象压入到[[ scopes ]]中
梳理作用域链:
function a() {
console.dir(a)
function b() {
console.dir(b)
function c() {
console.dir(c)
}
c()
}
b()
}
a()
调试和打印结果如下:
3、作用域链的作用
在访问变量或者函数的时候,会在作用域链上依次查找。先查找自己的AO是否存在变量和函数,不存在的话,再一层一层向上查找,直到找到GO;
外层的函数无法访问内部函数的变量;而内部函数可以访问外部函数的变量。
第一个例子的aa打印的是 111
第二个例子的aa打印的是 222
4、总结
作用域链的形成是由函数创建时确定的,它由函数创建时所处的执行上下文(Execution Context)和它的词法环境(Lexical Environment)组成。
作用域链的作用包括:
- 变量查找:当在函数内部引用一个变量时,JavaScript 引擎会在当前函数的作用域中查找该变量,如果找不到,则会沿着作用域链向上查找,直到找到全局作用域。
- 闭包实现:通过作用域链,内部函数可以访问外部函数的变量,从而实现闭包(Closure),使得外部函数的作用域在内部函数执行完毕后仍然可以被引用。
作用域链的形成是 JavaScript 语言实现词法作用域(Lexical Scope)的重要基础,它保证了函数内部对外部作用域的可访问性,并且决定了变量在程序中的可见范围和生命周期。
通过作用域链,JavaScript 实现了变量的作用域和闭包特性,使得代码具有更好的封装性和灵活性。