闭包
闭包 是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数。——《JavaScript高级程序设计》
首先明确JS中函数的作用域确定的基本原则:
- 外部函数作用域不能看到内部函数的作用域;
- 内部函数作用域可以看到外部函数的作用域;
那么如何能访问或修改到一个函数内部的变量呢?
函数的return就是这个传送门,可以将一个内部函数送出外部函数。 即使无法直接访问到外部函数内部的变量,也可以通过return出的内部函数去访问或修改外部函数的变量。
用一个简单的例子来解释。
function outerFn(){
var data = 10
var innerFn = function(){
data += 1
console.log(data)
}
return innerFn
}
var result = outerFn()
result()
result()
// 11
// 12
现有一个名为outerFn的函数,它的内部包含一个已经声明的变量data和一个内部函数innerFn。一般情况下,是无法访问或修改data的,但是innerFn可以访问到data,我们通过调用return出的innerFn,就可以间接的去修改和访问到data的数据了。
这样的做法,被总结称为 闭包。
为什么要这么做?
- 为一个封闭的函数作用域开一个传送门,让外界可以去访问和修改它;
- 将部分函数内部的局部变量暴露,按需求进行共享和长期保存。
需要注意的地方
由于内部函数在被访问后一直处于被引用状态,不能够被垃圾回收。在调用完毕后,如上例,将 result = null ,解除innerFn的引用,方便内存释放,是比较好的方式。
立即执行函数
当我们理解了 闭包 的概念后,就会出现一种需求,我们并不需要多次调用闭包函数,甚至并不需要闭包的返回值,仅仅是需要它执行一次。那上述函数最简单的写法是否如下:
function outerFn(){/* code */}() //SyntaxError: Unexpected token (
很可惜,结果却是报错。
原因
当JavaScript在读取代码时,看到 function 关键字后,就已经认为这是一个函数声明了,而在函数声明之后,是不可以直接加()来执行的。我们要做的就是让js知道这不是一个函数声明即可。
解决方法
很简单,可以用圆括号将整个函数包起来再(),更可以使用简单的判断符号来处理。这样的做法,就称为 立即执行函数 。
(function outerFn(){/* code */})()
!function outerFn(){/* code */}()
~function outerFn(){/* code */}()
true && function outerFn(){/* code */}()
可以用来做什么?
一般情况下,只对匿名函数使用这种 立即执行函数 。它的核心是 闭包 ,实现的目的有以下几个:
1. 不必为函数命名,避免污染全局变量;
2. 内部形成单独的块级作用域,封装一些私有变量;
3. 内部变量执行完即销毁,不会占用更多的内存。