MDN:函数与对其状态即词法环境(lexical environment)的引用共同构成闭包(closure)。也就是说,闭包可以让你从内部函数访问外部函数作用域。在JavaScript,函数在每次创建时生成闭包。
用例子说明:
function foo(){//foo的作用域中声明了局部变量a和函数bar,bar是foo的内部函数,只能在foo内部访问
var a=1
function bar(){//根据变量在作用域中由内而外的查找过程,bar函数能访问其父函数foo中的局部变量a
console.log(a)
}
bar()
}
foo()
注意:词法(lexical)一词表明,词法作用域根据声明变量的位置来确定该变量可被访问的位置。嵌套函数可获取声明于外部作用域的函数。这跟函数在哪里调用无关
function f1(){
var a=2 //在此声明的变量可以被内部函数bar访问
return function bar(){
console.log(a)
}
}
var f=f1()
f()
/* 以上例子尽管bar函数被返回,但仍可访问其词法作用域中的局部变量a */
循环中的闭包
来看一道经典的面试题:
function test(idx){
console.log(idx)
}
for(var i=0;i<2;i++){
setTimeout(() => {
test(i)
}, 1000);
}
该题的输出并不是0,1,而是2,2。循环中传递给延时函数的回调函数是同一个引用,共享同一个词法作用域,域中包含局部变量i,该局部变量被共享,即每次循环后都改变,延时回调之前已经处理完毕,所以连续两次输出最后的i值2。解决这个问题可以有以下两种方法:
- 添加一个闭包
function test(idx){
return function(){
console.log(idx)
}
}
for(var i=0;i<2;i++){
setTimeout(test1(i), 1000);
}
回调函数是有函数test创建并返回的新函数,即每次循环创建了新的词法作用域,局部变量i在不同的作用域中不共享,所以不受影响,输出依次为0,1
- 立即执行函数
function test(idx){
console.log(idx)
}
for(var i=0;i<2;i++){
(function(i){
setTimeout(() => {
test(i)
}, 1000);
})(i)
}
JS中调用函数传递参数都是值传递 ,所以当立即执行函数执行时,首先会把参数i的值复制一份,然后再创建函数作用域来执行函数,即i不是共享的变量
总结
当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数时,就产生了闭包。即产生闭包的条件是:1.函数嵌套;2.内部函数引用了外部函数的数据(变量或函数)
网友评论