1.什么是闭包
一个函数如果可以访问它被创建是所处的上下文环境,则这个函数被称为闭包。--JavaScript语言精粹
闭包是指有权访问一个函数作用域中的变量的函数。 -- JavaScript高级程序设计
我们再举个闭包的例子:
function user() {
var age = 12
function userAge() {
age++
return age
}
return userAge
}
const addAge = user()
console.log(addAge()) // 13
console.log(addAge()) // 14
console.log(addAge()) // 15
从上面的代码中我们可以很明显地看到每调用一次 addAge
函数输出的内容就会+1,这就是闭包的效果。
2.闭包产生的原理
产生
从上面的例子很容易看出来,输出是age
每次都+1的结果。虽然addAge
函数在使用的时候并不是在user
函数之内,但是它仍然能够使用user
函数的变量,比如age
,于是乎,闭包就产生了。因为addAge
是有权访问user
作用域的函数,它有着对该作用域的引用,这个引用就是我们说的闭包。
基本原理
addAge
分明没有在user
之内使用那它为什么还能访问user
的变量呢?想要理解这点,我们就需要用到我们之前说的作用域和作用域链的知识了。
由我们之前了解的知识我们知道,JavaScript使用作用域是词法作用域,所以函数声明完成之后它的作用域也确定了,addAge
这个函数是user
赋值给它的,但user
已经执行了,所以addAge
的值应该是userAge
才对(user
函数return
的值),如果是userAge
,它自然能够无压力访问age
这个变量。
深入原理
就是在执行上下文的内容中我们知道,函数在调用完成之后就会弹栈,再之后就会被垃圾回收机制回收。如此一来该函数里面的变量应当会被销毁,无法再被访问到,但是为什么闭包还是能访问到呢?
如果想不通就说明我们在某个地方陷入了误区,首先,我们知道现在的情况是父函数虽然弹栈了,但是不在父函数之内运行的子函数仍然能够访问父函数里面的变量,那么这就说明该变量肯定还存在内存之中没有被销毁。那变量在什么情况下不会被销毁呢?从垃圾回收机制之中我们知道,只要该变量还在被使用,垃圾回收机制就不会销毁它,这就说明age
在一直被人调用着,调用的人就是addAge
,所以闭包就这么产生了。
3.闭包的用途场景
既然我们知道了闭包是如何出现了,我们接下来就可以思考它能用来干嘛了。从上面的例子中我们看到,函数user
的变量age
是定义在函数里面的,如果我们想在外面的函数中读取到这个变量是不可能的,除非函数将这个变量return
出去。但是现在我们发现这个问题可以通过闭包轻松解决了,不过需要我们注意的是,闭包尽量不要像上面那样改动这个值(除非真的需求需要),不然到后面出了问题都不好去找。
异步事件(典型例子:setTimeout/setInterval)
* 这里的i必须要用var来定义,不能用let。熟知es6语法的你们应该都懂的
// 不用闭包的情况下
function fun1(){
for(var i = 0; i < 10; i++){
setTimeout(function(){
console.log(i)
},1000)
}
console.log(i) // 10
}
fun1() // 10个10
// 使用闭包解决
function fun2(){
var j = 1;
for(var i = 0; i < 10;i ++){
(function(j){
setTimeout(function(){
console.log(j)
},1000)
})(i)
}
console.log(i) // 10
}
fun2() // 0-9分别输出
作为变量
function fn1(){
var name="hello";
return function(){
return name;
}
}
var fn2 = fn1();
console.log(fn2()) // hello
模块化 (2的升级版)
// IIFE(立即执行函数)等价于fn2 = fn1()
var fn2 = (
function fn1() {
var name = "hello";
var age = 3;
var realName = "my name"
function myName() {
console.log( name );
}
function myAge() {
console.log(age);
}
return {
realName:realName,
myName:myName,
myAge:myAge
}
})()
console.log(fn2.realName) // my name
console.log(fn2.myName()) // hello
console.log(fn2.myAge()) // 3
4.闭包的缺点
但是学过前端的都应该听过一句话:不要滥用闭包。这是因为虽然闭包有时候用于帮忙解决某些问题会方便很多,但是它也有一个让人感到窒息的缺点:使用闭包会占用大量内存。因为闭包如果不是开发人员手动清除,那么垃圾回收机制就不会回收它,那使用的闭包越多,所占用的内存就越大,会造成网页的性能问题,在IE中甚至会导致内存泄露。一个开发人员如果在开发时不考虑性能问题简直就是耍流氓。