开局
首先我们看一段代码:
var arr=[]
for (var i = 0; i <10;i++) {
arr[i]=function(){
console.log(i);
}
}
arr.forEach(function(item){
item()
})
我们本意想让他输出0到9,但我们运行完会发现他会打印十遍10;那要怎么办才能让他打印0到9,看完这篇文章你可能就会有办法了。
作用域链
在了解闭包之前我们先来了解一下作用域链的变量的查找规则。
- 函数在执行前预编译,会创建执行上下文对象
- 变量环境中,有一个内定的outer属性用于指明该函数的外层作用域是谁
- outer的指向是根据词法作用域来定的
拿一段代码来分析一下:
function bar(){
console.log(a);
}
function foo(){
var a=100
bar()
}
var a =200
foo()
我们来按照以上步骤来画出图来观察一下:
通过这个指向我们能清楚的看到每个函数中的变量的查找规则,所以我们得出上述代码输出的结果应该为:200
总结
js引擎在查找变量时会先在函数中查找,找不到就会根据outer的指向去到外层作用域中查找,层层往上,这种查找的关系链就称为,作用域链。
闭包
闭包概念:闭包可以被看做是一种特殊的函数,它能够让函数保存一些信息,以便在之后的调用中使用。
下面我们拿一个代码示例来讲解一下:
function foo(){
var name="大仙"
function bar(){
console.log(count,age)
}
var count=1
var age=18
return bar
}
var age=20
const baz=foo()
baz()
这段代码我们在函数foo体内有定义了一个函数bar,通过var baz=foo()
这一行,调用了foo
函数,并将返回的bar
函数的引用赋值给了变量baz
。这就会导致函数foo的上下文执行完已经出栈了但bar还没调用;我们通过画图来展示:
这里foo函数已经调用完已经要出栈了但是foo体内有个bar函数因为通过预编译使编译器知道在bar函数体内需要调用count和age两个变量以至于foo函数去栈完重新生成一个包里面装有count和age两个变量再次放入栈中。如图:
后面放入含有count和age两个属性的集合就成为闭包。将以上内容总结就是以下内容:
根据js词法作用域的规则,内部函数总是能访问外部函数中的变量, 当通过调用一个外部函数返回的一个内部函数后,即使外部函数执行已经结束了, 但是内部函数引用了外部函数中的变量也依旧需要被保存在内存中,我们把这些变量的集合叫做闭包。
闭包的作用
1. 实现共有变量(适用于企业的模块开发)
在企业级的模块化开发中,不同模块之间可能需要共享一些数据或状态,但又不希望这些数据成为全局变量,以免引起命名冲突或意外修改。闭包可以用来创建一个私有的作用域,同时通过暴露特定的方法或属性来安全地共享数据。这种方式使得模块间可以有选择性地交互,而不会干扰到其他模块或全局命名空间。例如,一个模块可以返回一个对象,该对象上挂载了访问或修改共享数据的方法,而实际的数据则被闭包保护起来。
2. 做缓存
闭包非常适合用来实现函数的 memoization(记忆化)技术,这是一种优化技术,用于加速具有重复计算的函数。通过闭包保存之前计算过的结果,当下次遇到相同的输入时,直接从缓存中返回结果,避免重复计算。这对于递归算法、复杂计算或者网络请求等耗时操作特别有效。例如,斐波那契数列的计算就可以利用闭包来存储已经计算过的值,大大提升效率。
3. 封装模块,防止全局变量污染
在JavaScript中,全局变量容易引发命名冲突,特别是在大型项目或多开发者协作的环境中。闭包通过创建独立的作用域,使得内部定义的变量和函数对外部不可见,从而减少了全局命名空间的污染。这有助于提高代码的可维护性和减少错误。例如,可以使用立即执行函数表达式(IIFE,Immediately Invoked Function Expression)来封装整个模块,所有模块内部的变量和函数都将成为这个闭包的一部分,外界只能通过明确指定的接口(如返回的对象上的方法)来访问模块功能,而无法直接触及内部细节。
总结来说,闭包在企业级开发中不仅是管理状态和变量的有效手段,也是提高代码质量和维护性的关键工具,它帮助开发者构建更加健壮、高效且易于维护的应用程序。
闭包缺点
因为闭包需要产生这种变量的集合会容易使栈满了,导致爆栈,而这就是闭包的一个缺点内存泄漏。
结尾
讲完以上内容估计大家对文章开头的那个问题怎么解决应该有想法了吧!对,就是需要在循环内创建一个闭包来捕获每个迭代时i
的当前值。这可以通过立即执行的函数表达式(IIFE)或者使用额外的函数来实现。下面就是使用额外的函数来完成的代码:
var arr=[]
for (var i = 0; i <10;i++) {
(function(){
var j=i
arr[j]=function(){
console.log(j);
}
})()
}
arr.forEach(function(item){
item()
})
在这个新的作用域中,通过声明 var j = i;
,实际上是为每次循环创建了一个新的变量 j
,并将当前迭代的 i
的值赋给 j
。防止闭包引用循环变量的最终值。
这些就是我对闭包的一些大概了解欢迎各位大佬指教补充!!!