1. 定义
引用MDN的说法: 闭包是函数和声明该函数的词法环境的组合。这个环境保护了这个闭包创建时所有能够访问的局部变量。
我的理解是:在一个函数(外部函数或者父函数)内部定义了一个函数(即内部函数),且这个内部函数访问了外部函数的变量。这个内部函数就形成了一个闭包。
2. 作用
闭包可以在函数外部访问函数内部的局部变量。可以用于定义私有方法。
3. 例子
3.1 例1
function outer () {
let name = '张三'
function innerOne () {
return name
}
function innerTwo (newName) {
name = newName
}
return {
getName: innerOne,
setName: innerTwo
}
}
let outerFunc1 = outer()
let outerFunc2 = outer()
outerFunc1.getName()// 张三
outerFunc1.setName('李四')
outerFunc1.getName()// 李四
outerFunc2.getName()// 张三
3.2 例2 (循环中定义闭包)
js:
function loopOuter () {
let array = [{
name: '张三',
id: 'person1'
}, {
name: '李四',
id: 'person2'
}, {
name: '王老五',
id: 'person3'
}]
for (var i=0; i < array.length; i++) {
var item = array[i]
document.getElementById(item.id).onclick = function () {
document.getElementById('showName').innerHTML = item.name
}
}
}
loopOuter()
html:
<button id="person1">第一个人</button>// 点击后发现,标签p处显示王老五
<button id="person2">第二个人</button>// 点击后发现,标签p处显示王老五
<button id="person3">第三个人</button>// 点击后发现,标签p处显示王老五
<p id="showName">显示名字的地方</p>
3.3 例3 (基于例2)
点击事件的函数替换成showName(item)
然后再添加下面这个函数声明
function showName(name) {
return function () {
document.getElementById('showName').innerHTML = name
}
}
4. 词法作用域(共享同一个词法作用域还是独立的词法作用域)
例1中闭包getName跟setName闭包共享词法作用域
例2中点击事件的函数都是一个闭包,且这几个函数都共享了同一个词法作用域。当年调用点击事件的时候,item变量的值已经变成了数组的最后一个值。
例2中假如把item的声明变成let,则点击之后三个名字都不一样,这是因为let声明的变量是块作用域。
例2中把点击事件的函数再加一个内部函数也可以点击之后三个名字不一样。见例3。
例3中点击事件的函数都是一个闭包,且这几个函数的词法作用域都是不同的。
5. 闭包副作用
引用MDN的说法: 闭包在处理速度和内存消耗方面对脚本性能具有负面影响。使用不当会引起内存泄漏。程序卡死。
所以能够避免使用闭包的就不使用。比如在创建新的对象或者类时,方法通常应该关联于对象的原型,而不是定义到对象的构造器中。原因是这将导致每次构造器被调用时,方法都会被重新赋值一次。