闭包的概念
一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。---摘自于MDN上对闭包的解释,前往MDN-闭包。
通俗点说就是:只要一个函数访问了一个不属于当前函数作用域的变量就称为闭包。
下面看一个MDN上简单的例子:
function init() {
var name = "Mozilla"; // name 是一个被 init 创建的局部变量
function displayName() { // displayName() 是内部函数,一个闭包
alert(name); // 使用了父函数中声明的变量
}
displayName();
}
init();
init()
创建了一个局部变量 name
和一个名为 displayName()
的函数。displayName()
是定义在 init()
里的内部函数,并且仅在 init()
函数体内可用。请注意,displayName()
没有自己的局部变量。然而,因为它可以访问到外部函数的变量,所以 displayName()
可以使用父函数 init()
中声明的变量 name
。
从例子上可以看出,其实就是一种作用域链,在displayName
内部函数中,访问name
属性,但在其内部作用域中没有找到name
属性,因此往作用域链上找,也就是在init
函数里面找到了name
属性。
有意义的闭包
根据上面例子改造:
function init() {
var name = "Mozilla"
return function displayName() {
console.log(name)
}
}
let fn = init()
fn()
此案例中,在init
函数中,返回displayName
函数,且在displayName
函数内引用了外部函数init
的中变量name
,此时就形成了一个闭包。
使用场景
柯里化函数
柯里化的目的在于避免频繁调用具有相同参数函数的同时,又能够轻松的重用。
// 假设我们有一个求长方形面积的函数
function getArea(width, height) {
return width * height
}
// 柯里化之前
// 如果我们碰到的长方形的宽老是10
const area1 = getArea(10, 20)
const area2 = getArea(10, 30)
const area3 = getArea(10, 40)
// 我们可以使用闭包柯里化这个计算面积的函数
function getArea(width) {
return height => {
return width * height
}
}
const getTenWidthArea = getArea(10)
// 之后碰到宽度为10的长方形就可以这样计算面积
const area1 = getTenWidthArea(20)
// 而且如果遇到宽度偶尔变化也可以轻松复用
const getTwentyWidthArea = getArea(20)
闭包总结
- 形成: 函数中嵌套函数,且内部函数引用外部函数的变量
- 作用: 延长变量生命周期
- 优点: 希望一个变量长期存在内存中、模块化代码避免全局变量的污染、私有属性
- 缺点: 无法回收闭包中引用变量,容易造成内存泄漏