闭包是什么
- 闭包是指有权访问另一个函数作用域中的变量的函数`
在我看来就是内嵌函数,在函数中嵌套函数
由于在JS中,变量的作用域属于函数作用域,在函数执行后作用域就会被清理、内存也随之回收,但是由于闭包是建立在一个函数内部的子函数,由于其可访问上级作用域的原因,即使上级函数执行完,作用域也不会随之销毁,这时的子函数——也就是闭包,便拥有了访问上级作用域中的变量的权限,即使上级函数执行完后作用域内的值也不会被销毁。
闭包的作用
内部函数调用外部变量
function Father() {
var counter = 0
function Son() {
counter += 1
console.log(counter);
}
return Son
}
var father = Father()() // 1
创建变量或者函数
var counter = (function () {
var private = 0
function changeBy(val) {
private += val
}
return {
zj: function() {
changeBy(1)
},
js: function() {
changeBy(-1)
},
value: function() {
return private
}
}
})()
counter.zj()
let a = counter.value()
console.log(a); // 1
counter.js()
let b = counter.value()
console.log(b); // 0
将函数作为参数
var a = 'whl'
function f1() {
var a = 'foo'
function fo() {
console.log(a);
}
return fo
}
function f2(f) {
var a = 'qwe'
f()
}
f2(f1()) // foo
图解闭包
function first(first) {
const firstName = first
let dept = 1
debugger // 第一个 debugger
return function second(second) {
const secondName = firstName + '/' + second
dept = 2
debugger // 第二个 debugger
return function third(third) {
const thirdName = secondName + '/' + third
dept = 3
debugger // 第三个 debugger
}
}
}
const x = first('白')('小')('唯')
const y = first('白')('小')
debugger // 第四个 debugger
first
函数内部return 回来一个second
函数,second
函数里面又return 回来一个名为 thrid
函数,并且我们分别在合适的时机debugger 了一下,下面让我们在浏览器中跑一下,看看会出现什么情况。
-
第一次debugger
在first 函数入栈时,我们发现局部变量有dept=1,first:'白’,firstName:'白'
,局部变量以及函数参数都在,没有任何特殊的事情发生 -
第二次debugger
second
函数进入了调用栈,我们scope作用域
中多了一个名为 first 的闭包
,里面存储的正是first
函数里面的变量对象
,因此通过这里我们可以知道,内部的second
函数是可以访问到first
函数里的属性的。 -
第三次debugger
third
函数进入了调用栈,并且跟第二次 debugger 一样,作用域不仅将外部second
函数的变量对象
包含了起来,还包括了first
函数的变量对象
,因此它可以访问并且修改dept
,并且成功置为 3。 -
第四次debugger
我们可以看到成功获取了third
函数,并且有了一个惊人的发现,在first
和second
的执行环境被弹出的情况下,我们的third
函数的 [[scopes]]属性仍然包含着他们的变量对象,这就是我们的作用域链。
闭包的优缺点
- 优点:
全局变量可以重用,局部变量不会被污染,仅函数内部可用
- 缺点:
比普通函数占用更多的内存,内存泄漏导致项目运行变得卡顿
经典例题
var data = []
for(var i = 0; i < 3 ; i++) {
data[i] = function() {
console.log(i);
}
}
data[0]() // 3
data[1]() // 3
data[2]() // 3
这里的 i 是全局下的 i,共用一个作用域,当函数被执行的时候这时的 i=3,导致输出的结构都是3。
如何修改让它输出的是0、1、2呢
- Method1:
自执行函数和闭包
var data = []
for (var i = 0; i < 3; i++) {
(function (j) {
data[j] = function () {
console.log(j);
}
})(i)
}
data[0]() // 0
data[1]() // 1
data[2]() // 2
- Method2:
使用let
var data = []
for (let i = 0; i < 3; i++) {
data[i] = function () {
console.log(i);
}
}
data[0]() // 0
data[1]() // 1
data[2]() // 2
let 具有块级作用域,形成的3个私有作用域都是互不干扰的。
function fun(n,o){
console.log(o);
return {
fun:function(m){
return fun(m,n);
}
};
}
var a = fun(0);a.fun(1);a.fun(2);a.fun(3);
var b = fun(0).fun(1).fun(2).fun(3);
var c = fun(0).fun(1);c.fun(2);c.fun(3);
//问:三行a,b,c的输出分别是什么?
分析:var a = fun(0);a.fun(1);a.fun(2);a.fun(3);
- 第一次调用fun(0)时,o为undefined
- 第二次调用fun(1)时,m为1,此时fun闭包了外层函数的n,可以获取到外层函数的n的值,即m=1,n=0,并且调用第一层fun函数fun(1,0),所以o为0
- 第三次调用fun(2)时,m为2,调用a.fun,所以闭包的还是第一次调用时的n的值,为0,所以内部调用第一层f(2,0),所以o为0
- 第四次调用fun(3)时,m为3,仍然是调用a.fun,所以闭包的还是第一次调用时候的n的值,为0,所以o为0
即a的答案是undefined 0 0 0
分析:var b = fun(0).fun(1).fun(2).fun(3);
- 第一次调用第一层fun(0)时,o为undefined
- 第二次调用.fun(1)时候m为1,此时函数闭包了第一次调用函数时候的n值,即m=1,n=0,并在内部调用第一层fun函数fun(1,0),所以o为0
- 第三次调用.fun(2)时候m为2,此时函数闭包了第二次调用函数时候的n值,即m=2,n=1,调用第一层fun函数(2,1)所以o为1
- 第四次调用.fun(3)时m为3,此时函数闭包了第三次调用函数时候的n值,即m=3,n=2,调用第一层fun函数为fun(3,2)所以o为2
即最终答案:undefined 0 1 2
分析: var c = fun(0).fun(1);c.fun(2);c.fun(3);
- 第一次调用一层fun(0),o为undefined
- 第二次调用.fun(1)时m=1,此时fun闭包了外层函数的n值,也就是第一次调用的n,即m=1,n=0,所以o为0
- 第三次.fun(2)此时fun闭包的是第二次调用的,即m=2,n=1,并在内部调用第一层fun函数fun(2,1),所以o为1
- 第四次.fun(3)此时函数闭包的是第二次调用的,即m=3,n=1,
- 并在内部调用第一层函数fun(3,1),所以o为1
即最终答案:undefined 0 1 1