今天让我们来了解一下js的作用域链及闭包
作用域链:理解变量的查找机制
在JS中,作用域链是一个至关重要的概念。当一个函数被执行时,JS引擎会先进行预编译(详情可参考(你真的了解JS的预编译吗?)并且为它创建一个执行上下文,其中包括一个变量环境。变量环境中包含着函数内部的所有变量声明以及一个特殊的属性——outer
,这个属性指向该函数的外层作用域。
作用域链的工作原理在于,当函数尝试访问一个变量时,它首先会在自己的局部作用域内搜索。如果没有找到相应的变量,它会沿着outer
属性指向的链路向上层作用域查找,直到找到变量或者到达全局作用域为止。这一系列的作用域连接起来就形成了作用域链。
词法作用域:函数声明的位置决定其环境
词法作用域,又称为静态作用域,指的是一个函数的执行环境由它在源代码中的位置决定。换句话说,函数访问变量的能力取决于它在代码中的定义位置,而不是调用位置。这意味着即使函数在不同的地方被调用,只要它的定义位置相同,那么它所能访问的变量环境也是相同的。
function bar (){
console.log(a);
}
function foo(){
var a = 100
bar()
}
var a = 200
foo()
为什么输出的答案是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()
因为bar()在foo()内部声明,只是return,并未在内调用。所以当foo()被调用完之后其执行上下文就被销毁了,但是根据作用域规则,内部函数一定能访问外部函数的变量,这就导致foo()运行完后不能被完全销毁,留下了需要访问的count和age。所以能输出1和18。
闭包的作用
-
实现共享变量:在模块化开发中,闭包可以提供一种封装私有变量的方式,同时允许多个函数之间共享数据,而不暴露给全局作用域。
-
做缓存:闭包可以用来缓存计算结果,避免重复计算。
-
封装模块:利用闭包可以创建模块,使得内部状态和方法对外部世界不可见,从而避免全局变量污染。
闭包的缺点
内存泄漏:由于闭包会保留对外围函数变量的引用,这可能导致垃圾回收器无法回收这些不再使用的变量,从而造成内存泄漏。因此,开发者需要谨慎管理闭包的使用,避免不必要的长期引用。
总之,理解作用域链、词法作用域和闭包是掌握JS的关键。它们不仅帮助我们编写更加高效和安全的代码,还让我们能够更好地理解JS引擎如何处理变量和函数,以及它们之间的关系。