最近看了一些javascript相关的知识,打算整理一下
闭包
在javascript中,根据词法作用域的规则,内部函数总是可以访问其外部函数中的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,我们就把这些变量的集合称为闭包
。
看以下示例代码,在bar执行的时候,访问了foo的内部变量a和b,所以产生了闭包
function foo() {
var a = 20
var b = 30
function bar() {
return a + b
}
return bar;
}
var bar = foo();
bar();
在chrome里面断点调试,Call Stack表示当前的函数调用栈,Scope为当前正在被执行的函数的作用域链,Local为当前活动对象。可以看出来产生了一个名为foo的闭包
再来看一段示例代码,可以看到也产生了闭包
function foo() {
var a = 1
let b = 2
const c = 3
var innerBar = {
getA : function() {
console.log(b)
return a
},
setA : function(value) {
a = value
}
}
return innerBar
}
var bar = foo()
bar.setA(100)
console.log(bar.getA())
打印结果为:
2
100
也用chrome断点调试一下,看下具体情况
下面来分析以下调用栈情况,在执行return innerBar
的时候的调用栈如下:
foo函数执行完成之后,其执行上下文从栈顶弹出了,但是由于返回的setA和getA方法使用了foo函数的变量a和b,所以产生了foo函数的“专属背包”。整个调用栈的状态如下:
当执行到bar.setA
方法的时候,会创建setA函数的执行上下文,其中访问了b和a两个变量,javascript引擎会沿着当前执行上下文
-> foo函数闭包
-> 全局执行上下文
的顺序来查找b和a变量。调用栈状态如下:
同理,在调用bar.getA方法的时候,可以在chrome里面打断点,情况如下图所示,由于之前已经调用过bar.setA(100),foo函数闭包里的a变量已经变成了100,查找a变量的时候,沿着Local
-> Closure(foo)
-> Global
作用域链来查找。
看下面的示例代码,长得很像产生了闭包的样子,实际并没有产生闭包╮( ̄▽ ̄)╭
var bar = {
myName : "test",
printName : function() {
console.log(myName)
}
}
function foo() {
let myName = 'blabla'
return bar.printName
}
let myName = 't'
let _printName = foo()
_printName()
bar.printName()
打印结果是:
t
t
可以用chrome断点调试一下,看下执行_printName()
时候的情况,可以看到Scope
里面并没有Closure
出现
闭包什么时候回收
通常,如果引用闭包的函数是一个全局变量,那么闭包会一直存在直到页面关闭。
如果引用闭包的函数是个局部变量,等函数销毁后,在下次 JavaScript 引擎执行垃圾回收时,判断闭包这块内容如果已经不再被使用了,那么 JavaScript 引擎的垃圾回收器就会回收这块内存。
所以在使用闭包的时候,你要尽量注意一个原则:如果该闭包会一直使用,那么它可以作为全局变量而存在;但如果使用频率不高,而且占用内存又比较大的话,那就尽量让它成为一个局部变量