前言
js中闭包无处不在,你只需要识别并拥抱他,闭包是基于词法作用域书写代码时所产生的自然效果,你甚至不需要为了利用他们而有意的创建闭包。
实质问题
当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行的。
// 闭包是基于词法作用域书写代码时所产生的自然效果
// 当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行
function foo() {
var a = 2
function bar() {
console.log(a);
}
bar()
}
foo()
基于词法作用域的查找规则,函数bar可以访问外部作用域中的变量a(这个作用域中是一个RHS查询)
技术上来讲,也许是闭包,但是并不是。我认为最准确的来解释bar(),对a的引用的方法是词法作用域的查找规则,而这些规则只是闭包的一部分。
从学术角度来说,上面的代码片段中,函数bar()具有一个涵盖foo函数作用域的闭包。也可以认为bar()封闭在了foo()作用域中,即bar()嵌套在了foo()内部。
但是通过这种方式定义的闭包不能直接观察,也无法明白这个代码片段中闭包是如何工作的。我们可以很容易的理解词法作用域,而闭包则是隐藏在代码之后的神秘阴影里面,并不那么容易理解。
下面看一下清晰的闭包:
function foo() {
var a = 2
function bar() {
console.log(a);
}
return bar
}
var baz = foo()
baz()//2 实际上只是通过不同标识符引用调用了每部的函数bar()
// bar()依然有对该作用域的引用,而这个引用就叫做闭包
bar在自己定义的词法作用域以外的地方执行的。
在foo执行后通常会由于js引擎的垃圾回收机制来释放不再使用的内存空间,即foo整个内部作用域都被销毁。
但是闭包正阻止内存回收,也就是内部作用域依旧存在,那谁在使用这个内部作用域呢?
其实就是bar本身在使用
拜bar的位置所赐,它拥有涵盖foo()内部作用域的闭包,使得该作用域能够一直存活,以供bar在之后任何时间进行引用。
bar()依然有对该作用域的引用,而这个引用就叫做闭包。
当然,以何种方式对函数类型的值进行传递,当函数在别处被调用时都可以观察到闭包:
直接传递函数
function foo() {
var a = 2
function baz() {
console.log(a);
}
bar(baz)
}
function bar(baz) {
baz()//这里就是闭包
}
foo()
//一个函数的调用,是在其他作用域中
把函数诶不baz传递给bar,当调用这个内部函数时(fn),它涵盖的foo()内部的作用域的闭包就可以观察到了,因为它能够访问a。
间接传递函数:
var fn
function foo() {
var a = 2
function baz() {
console.log(a);
}
fn = baz
}
function bar() {
fn()
}
foo()
bar()
无论通过何种手段将内部函数传递到所在的词法作用域以外,他都会持有对原始定义作用域的应用,无论在何处执行这个函数都会使用闭包。
真正明白
前面的代码有些死板,为了解释闭包而刻意进行修饰
function wait(message) {
setTimeout(function timer() {
console.log(message);
}, 1000)
}
wait('hello')
timer具有涵盖wait(…)作用域的闭包,因为还保持对变量message的引用。
在引擎内部,内置的工具函数setTimeout(…)持有对一个参数的引用,引擎会强调这个函数,在例子中就是内部的timer,而词法作用域在这个过程保持完整,这就是闭包
富有争议的闭包
var a = 2
(function IIFE() {
console.log(a);
})()
循环和闭包
for (var i = 1; i <= 5; i++) {
setTimeout(function () {
console.log(i);
}, 1000)
}
//一下子打印出来五个6
for (var i = 1; i <= 5; i++) {
(function () {
setTimeout(function () {
console.log(i);
}, i * 1000)
})()
}
//五个6
for (var i = 1; i <= 5; i++) {
(function () {
var j = 1
setTimeout(function () {
console.log(i);
}, i * 1000)
})()
}
//五个6
for (var i = 1; i <= 5; i++) {
(function (j) {
setTimeout(function () {
console.log(j);
}, 1000)
})()
}
//五个undefined