🍎 JavaScript 闭包
相关问题:
- 对闭包的理解?
- 闭包的使用场景?
# 概念解释
- 闭包:一个函数以及其周围状态(词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合称为闭包。
- 闭包的产生:一个嵌套的内部函数调用了嵌套的外部函数的变量,就产生了闭包。闭包存在于嵌套的内部函数中。
- 闭包的理解:
- 闭包是嵌套的内部函数;
- 闭包是包含被引用变量(函数)的对象;
简单地说,闭包是可以使内层函数访问到其外层函数的作用域。
产生闭包的条件:
- 函数嵌套;
- 内部函数引用了外部函数的数据。所以执行外部函数定义就会产生闭包,不必调用内部函数。
🌰 例子:
function init() {
var name = "Mozila";
function displayName() {
alert(name);
}
displayName();
}
init()
2
3
4
5
6
7
8
9
displayName
函数没有自己的局部变量,但是由于闭包的特性,可以访问到函数外部的变量。
常见的闭包:
🌰 例子 / 将函数作为另一个函数的返回值:
function fn1() {
var a = 2
function fn2() {
a++
console.log(a)
}
return fn2
}
var f = fn1()
f()
f()
2
3
4
5
6
7
8
9
10
11
12
🌰 例子 / 将函数作为实参传递给另一个函数调用:
function showDelay(msg, time) {
setTimeout(function() {
console.log(msg)
}, time)
}
showDelay('msg', 1000)
2
3
4
5
6
7
8
# 闭包的生命周期
- 闭包的产生:在嵌套内部函数定义执行完就产生了(不是调用时)例如上面例子中
fn1()
已经定义完,函数提升,内部函数已经创建了。 - 闭包的死亡:在嵌套的内部函数称为垃圾对象时(包含闭包的函数称为垃圾对象))(例如上面的例子,可以直接
f = null
)
# 闭包的使用场景
使用场景特点:
- 创建私有变量;
- 延长变量的生命周期。 使用函数内部的变量在函数执行完后,仍然存活在内存中。
一般函数的词法环境会在函数返回后就被销毁,而闭包会保存对创建时所在的词法环境的引用,即便创建时所在的执行上下文被销毁,但是创建时的词法环境依然存在,以达到延长变量的生命周期的目的。
如例子
var f = fn1()
。当存在闭包是,函数内部声明的局部变量被包含在闭包内,由于外部声明了一个变量把内部的一个闭包制止关联着,所以闭包会一直存在。
相关使用场景的例子:
🌰 例子 / 调整字号按钮:
function makeSizer(size) {
return function() {
document.body.style.fontStyle = size + 'px'
}
}
var size12 = makeSizer(12)
var size14 = makeSizer(14)
var size16 = makeSizer(16)
document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;
2
3
4
5
6
7
8
9
10
11
12
13
🌰 例子 / 柯里化函数:
柯里化的目的在于避免频繁调用具有相同参数的函数的同时,又能轻松重用函数。
// 获取矩形面积的函数
function getArea(width, height) {
return width * height;
}
const area1 = getArea(10, 20)
const area1 = getArea(10, 30)
const area1 = getArea(10, 40)
// 可以使用闭包柯里化这个函数
function getArea(width) {
return height => {
return width * height
}
}
const getTenWidthArea = getArea(10)
const area1 = getTenWidthArea(20) // 相当于 getArea(10,20)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
🌰 例子 / 使用闭包模拟私有方法:
JavaScript 中不支持声明私有变量,可以通过 闭包 实现模拟私有方法。
var makeCounter = (function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1)
},
value: function() {
return privateCounter
}
}
})();
var Counter1 = makeCounter
var Counter2 = makeCounter
console.log(Counter1.value())
Counter1.increment()
console.log(Counter1.value())
Counter1.decrement()
console.log(Counter1.value())
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
上述通过使用闭包来定义公共函数,并令其可以访问私有函数和变量,这种方式也叫模块方式。
两个计数器
Counter1
和Counter2
是维护它们各自的独立性的,每次调用其中一个计数器时,通过改变这个变量的值,会改变这个闭包的词法环境,不会影响另一个闭包中的变量。
🌰 其他例子:例如延迟调用、回调等闭包的应用,核心思想还是差 u 你姑姐爱你私有变量和延长变量的生命周期。
# 闭包的缺点和解决
缺点:函数在执行之后,函数内部的局部变量没有及时释放,占用内存的时间变长,导致内存泄漏。
解决:能不使用闭包就不使用闭包;及时释放使用过的闭包。
# 闭包的注意事项
如果不是某些特定的任务需要使用闭包,在其他函数中创建函数是不明智的,因为闭包在处理速度和内存消耗方面对脚本的性能具有负面影响。
🌰 例子 / 创建新的对象或者类时,方法通常应该关联于对象的原型,而不是定义到对象的构造其中。
function MyObject(name, message) {
this.name = name.toString()
this.message = message.toString()
this.getName = function() {
return this.name
}
this.getMessage = function() {
return this.message
}
}
2
3
4
5
6
7
8
9
10
此处应该修改为 原型,因为此处并没有应用闭包的优点,应该避免使用闭包:
function MyObject(name, message) {
this.name = name.toString()
this.message = message.toString()
}
MyObject.prototype.getName = function () {
return this.name
}
MyObject.prototype.getMessage = function () {
return this.message
}
2
3
4
5
6
7
8
9
10
11
12