JS作用域链及闭包

62 阅读3分钟

今天让我们来了解一下js的作用域链及闭包

作用域链:理解变量的查找机制

在JS中,作用域链是一个至关重要的概念。当一个函数被执行时,JS引擎会先进行预编译(详情可参考(你真的了解JS的预编译吗?)并且为它创建一个执行上下文,其中包括一个变量环境。变量环境中包含着函数内部的所有变量声明以及一个特殊的属性——outer,这个属性指向该函数的外层作用域。

作用域链的工作原理在于,当函数尝试访问一个变量时,它首先会在自己的局部作用域内搜索。如果没有找到相应的变量,它会沿着outer属性指向的链路向上层作用域查找,直到找到变量或者到达全局作用域为止。这一系列的作用域连接起来就形成了作用域链。

词法作用域:函数声明的位置决定其环境

词法作用域,又称为静态作用域,指的是一个函数的执行环境由它在源代码中的位置决定。换句话说,函数访问变量的能力取决于它在代码中的定义位置,而不是调用位置。这意味着即使函数在不同的地方被调用,只要它的定义位置相同,那么它所能访问的变量环境也是相同的。

function bar (){
    console.log(a);
}
function foo(){
    var a = 100
    bar()
}
var a = 200
foo()

image.png

为什么输出的答案是200而不是100呢?因为a定义在全局作用域中,函数bar()也定义在全局作用域中。即使他是在foo()内被调用的,bar()的词法作用域也还是在全局,所以访问定义在全局的a,故输出a=200。

闭包:变量与函数的持久联系

根据js词法作用域的规则,内部函数总是能访问外部函数中的变量,当通过调用一个外部函数返回的一个内部函数后,即使外部函数执行已经结束了,但是内部函数引用了外部函数中的变量也依旧需要被保存在内存中,我们把这些变量的集合叫做闭包。

function foo(){
    var name = 'Ro9nin'
    function bar(){
        console.log(count,age);
    }
    var count = 1
    var age = 18
    return bar 
}

var age = 20
const damn = foo()
damn()

image.png

image.png

因为bar()在foo()内部声明,只是return,并未在内调用。所以当foo()被调用完之后其执行上下文就被销毁了,但是根据作用域规则,内部函数一定能访问外部函数的变量,这就导致foo()运行完后不能被完全销毁,留下了需要访问的count和age。所以能输出1和18。

闭包的作用

  1. 实现共享变量:在模块化开发中,闭包可以提供一种封装私有变量的方式,同时允许多个函数之间共享数据,而不暴露给全局作用域。

  2. 做缓存:闭包可以用来缓存计算结果,避免重复计算。

  3. 封装模块:利用闭包可以创建模块,使得内部状态和方法对外部世界不可见,从而避免全局变量污染。

闭包的缺点

内存泄漏:由于闭包会保留对外围函数变量的引用,这可能导致垃圾回收器无法回收这些不再使用的变量,从而造成内存泄漏。因此,开发者需要谨慎管理闭包的使用,避免不必要的长期引用。

总之,理解作用域链、词法作用域和闭包是掌握JS的关键。它们不仅帮助我们编写更加高效和安全的代码,还让我们能够更好地理解JS引擎如何处理变量和函数,以及它们之间的关系。