在我最初接触前端的时候,老师曾今和我说过在JS中,立即执行函数即是闭包
。
但这真的是对的吗?
带着疑问我们对IIFE和闭包进行一点探索,试图找到真相。
立即执行函数表达式
立即执行函数表达式 ( IIFE: Immediately Invoked Function Expression )
(function IIFE(){
var a = 3
console.log(a) // 3
})()
函数被包含在一对( )
内部,因此成为了一个表达式
,通过在末尾加上另外一个( )
可以立即执行这个函数,即函数在声明时立刻被执行了,这就是立即执行函数(IIFE)。
我们观察一下这个例子中的变量,可以发现内部声明了一个变量a
,a
只在函数内部可访问,这里涉及到一个函数作用域
的知识。
什么是函数作用域?
每声明一个函数,就会产生一个函数作用域
,你可以把它当作一个气泡
,每个作用域气泡中声明的变量,都会附属于
所处的作用域
气泡:
function foo(){
var a = 1
function bar(){
console.log(a, b)
}// <- bar 的函数声明(Function Expression)
var b = 2
return bar
}// <- foo 的函数声明(Function Expression)
foo()() // 1 2
foo
函数中声明了a
、b
变量和bar
函数,bar
由于附属于foo
气泡内,所以当bar
执行时可以访问foo
气泡内的变量a
、b
。
而上面IIFE的例子中:函数作用域
仅仅在立即执行的时候有意义,且变量a
只在函数内部可访问,执行完之后,变量a不再具有意义
。
闭包
上面这个例子中,bar
执行时所在的是外部词法作用域
,在外部词法作用域中并未声明a
、b
变量,但是却可以访问foo
内部的变量a
、b
。
function foo(){
var a = 1
function bar(){
console.log(a, b)
}
var b = 2
return bar
}
foo()()// <- 函数 bar 在这一行执行了
// 执行 即是函数调用,即: Function Involked
这个就是闭包的第一个特性
,即:
函数在当前词法
作用域之外
执行时,也可以记住并访问其声明时
所在的词法作用域,即保有其声明时所在词法作用域的引用。
也可以这么说:
当函数能记住并访问声明时所在的词法作用域时,就产生了
闭包
。
我们回过头来看IIFE的例子:
(function IIFE(){
var a = 3
console.log(a)
})()
被认为是经典的闭包例子的IIFE,并没有在其所声明的词法作用域之外被执行,并且变量a
的查询并非因为闭包而被发现,而是通过普通的RHS
查询被找到的,从这一点看,IIFE已经不能算是一个经典的闭包了
。
那么闭包还有什么别的特性吗?
我们再回到上面这个例子,我们稍微改写一点点。
function foo(){
var a = 1
function bar(){
console.log(a, b)
}
var b = 2
return bar
}
var baz = foo()
baz() // <- bar 实际被执行
如果你了解垃圾回收器
的话,我们执行到var baz = foo()
这一行时,会期待函数foo
已经被回收了,即foo
内部的整个作用域会被销毁
,因为我们知道它们不再有意义了(* 垃圾回收器
: 引擎通过垃圾回收器
来释放不再使用的内存空间)。
但是很显然,baz
的执行告诉我们并不是,正因为其还保有对foo
内部声明的变量a
、b
的引用,所以变量a
、b
并不会被释放,这就是闭包的第二个特性
:
阻止垃圾回收器对闭包内部引用的回收
只要baz还在,引用就一直在。
根据这一特性,我们再反观刚刚的IIFE:
(function IIFE(){
var a = 3
console.log(a)
})()
执行完之后,a
不再具有意义,因此会随着IIFE执行被销毁
。
结论
了解完什么是闭包
之后我们做个总结
,闭包
产生时,会继续保有声明词法作用域的引用
,并且这些引用不会被垃圾回收机制回收
,而IIFE
在执行后并不会在外部作用域再次使用,且整个内部作用域都会被销毁。
因此IIFE
并不算是一个经典的闭包
案例,只能算是一个特殊的函数表达式执行
。
- 本文链接:https://meglody.github.io/diary/Javascript%E5%87%BD%E6%95%B0%E4%BD%9C%E7%94%A8%E5%9F%9F%E4%B8%8E%E9%97%AD%E5%8C%85/
- 版权声明:本博客所有文章除特别声明外,均默认采用 许可协议。