什么是闭包
1.必须有函数嵌套函数;
2.内部函数必须使用到了外部函数的变量。
3.必须返回使用了外部变量的内部嵌套函数
怎么理解闭包
通(hu)俗(bian)来(luan)讲(zao),为了便于理解,接下来我们做个代入。
- 你 = 外层函数myself
- 女朋友 = 内层函数girlFriends
- 钱 = 外层变量money
- 老板 = 内存
下面这个人就是你,你有一个女朋友(任何你喜欢的女人都可以),你努力工作赚钱,然后你的女朋友花你的钱,女人花男人钱,这个就是“闭包”。
function myself() {
var money = 100;
function girlFriends() { //不要在意后面的“s”
return console.log('花钱', money);
}
return girlFriends();
}
myself();//花钱 100
上面的myself
函数执行的时候,girlFriends
(女朋友)可以随便使用money
,虽然心疼,但是也还算合理对吧,用就用了。
可惜天不随人愿,函数的生命都是很短暂地,函数调用完成之后就没有了,内部的money
也会释放掉,人死了就什么都没有了,但是有女朋友的人会有一点微妙变化。
function myself() {
var money = 100;
function girlFriends() {
return console.log('花钱', money);
}
return girlFriends; //返回函数,而不是返回函数调用结果
}
myself()(); //增加一个(),保证返回的函数可以执行调用
//花钱 100
看看上面的代码,明明myself
已经执行结束了,返回了一个函数,调用这个函数,居然还能访问到money
,说明你死了,但是你的女朋友还是能花你的钱o(╥﹏╥)o
为什么函数执行完毕后,嵌套的函数还能访问到外层函数的变量
翻译一下上面的话,为什么你死了,你的女朋友还是能花你的钱。
这需要先讲讲钱是怎么挣的,你拼命工作,好不容易从老板(内存)那里挣了点儿钱。
但是这个老板很鸡贼,他给你钱后你必须时时刻刻盯着,如果没有人盯着这个钱,老板就会把钱拿走(垃圾回收)。
如果这个时候你有了女朋友(内部函数),并且返回了女朋友(内部函数),女朋友就可以帮你盯着钱,那么即便你死了,还是有人盯着钱,老板不能收走。所以只要有人在盯着这个钱,老板就不能收走。
接下来我们改造一下自己
function myself() {
var money = 100;
return function () {return money++;}; //这里省略了一下girlFriend的名称,直接返回一个匿名函数
}
var earnMoney = myself();
earnMoney();
earnMoney();
earnMoney();
console.log('earnMoney', earnMoney());
//earnMoney 103
定义一个变量earnMoney
,把返回值赋值给这个变量,然后执行三次调用,结果输出了103。
- 这里为什么会累加我们之前的数据?
因为我们的myself
函数只执行了一次,那么money
只初始化了一次,而且一直有函数在盯着存放money
的内存,所以没有被回收,然后里面内嵌的函数执行了三次,那么money++
运行了三次,所以返回103。也就是说现在我们已经从内存当中申请了一块区域用来存放“103”了,这块区域会一直存在,如何才能释放这块区域,只要让
earnMoney = null;
就可以了。
上面这段话翻译一下,找一个“房子”,用来存放我们返回的“女朋友”,然后呢女朋友可以帮我们盯着钱,这时候钱还是100,这个女朋友很会理财,每执行一次,她就会让我们的钱增加1块,执行一次就增加一块,虽然“我们自己”已经挂了,但是女朋友还在,她执行了三次,我们的钱增加了3块。而且存钱的这个地方,别人访问不到,只有女朋友能访问。
现在老板(内存)必须要把钱收回来了,那只能销毁“房子”来销毁女朋友,进而没有人盯着钱了,那么好不容易挣的钱就被收走了。
闭包有什么好处?
function myself() {
var money = 100;
return function () {return money++;};
}
var earnMoney = myself();
var earnMoneyDouble = myself();
earnMoney();
earnMoney();
earnMoney();
earnMoneyDouble();
earnMoneyDouble();
earnMoneyDouble();
console.log('earnMoney', earnMoney()); //earnMoney 103
console.log('earnMoneyDouble', earnMoneyDouble()); //earnMoneyDouble 103
上面我们把返回函数赋值给了两个变量,分别执行两个变量,他们的计算互不影响,可以独立运行。这是因为计算的时候是在两块独立的内存上进行的。
接下来转换成我们熟悉的语言,现在找了两个房子,每一个房子都是调用myself(我们自己)之后得来的,说明我们很努力,挣了两份钱,分别放到两套房子里,这两份钱都让女朋友帮忙盯着,当女朋友去到第一个房子里执行理财的时候,第一个房子里面的钱就增加1块,这时第二套房子里的钱跟第一套。
所以“女人花男人的钱(闭包)“的好处就是,永远有钱花,而且钱只属于自己,不会被拿走,除非“房子”被铲掉。
接下来,该说一点男人们梦寐以求的事情了
- 如果有好几个女朋友,那么该怎样对待她们呢?
答案就是,一碗水端平,每个都要疼,都得给钱花(为了让你们听懂,我才这样说的)
回到正题,请看代码
function myself() {
var manyGirlFriends = [];
for (var i = 0; i < 10; i++) {
manyGirlFriends[i] = function () {
return i;
};
}
return manyGirlFriends;
}
var girlsHouse = myself();
girlsHouse[5]();
console.log('girlsHouse[1]()', girlsHouse[1]()); // girlsHouse[1]() 10
console.log('girlsHouse[2]()', girlsHouse[2]()); // girlsHouse[2]() 10
console.log('girlsHouse[3]()', girlsHouse[3]()); // girlsHouse[3]() 10
console.log('girlsHouse[4]()', girlsHouse[4]()); // girlsHouse[4]() 10
console.log('girlsHouse[5]()', girlsHouse[5]()); // girlsHouse[5]() 10
上面代码里面,我们循环创建10个闭包,然后想让每个闭包返回当前的i,但是当myself
返回的时候,for
循环已经结束,所有闭包都会共享一个i
,那么结果就是全都会返回10。
接下来是大家期待的翻译时间
你有10个女朋友,你希望第一个女朋友分一块钱,第二个分两块钱,依次类推,但是这些女朋友不会听你的,她们是好姐妹,有钱一起花,每个人都能支配你赚来的钱。
- 那么有没有什么方法能达到那种互不影响的效果呢?
还真有!立即执行函数
function myself() {
var manyGirlFriends = [];
for (var i = 0; i < 10; i++) {
(function (i) {
manyGirlFriends[i] = function () {
return i;
}
})(i);
}
return manyGirlFriends;
}
var girlsHouse = myself();
girlsHouse[5]();
console.log('girlsHouse[1]()', girlsHouse[1]()); //girlsHouse[1]() 1
console.log('girlsHouse[2]()', girlsHouse[2]()); //girlsHouse[2]() 2
console.log('girlsHouse[3]()', girlsHouse[3]()); //girlsHouse[3]() 3
console.log('girlsHouse[4]()', girlsHouse[4]()); //girlsHouse[4]() 4
或者还可以使用ES6的 let
,把for循环当中var i = 0
替换为 let i = 0
就可以了。
关于立即执行函数和let作用域问题,我们可以单独抽出来讨论一下,请关注我之后的博客
本片文章借鉴了《JavaScript权威指南》一书,还有很多技术界的前辈们,如果涉及侵权的地方,请联系我删除,另外画画和举例子是为了有直观的感受,并无恶意,请各位不要对号入座。还有本文是自己对于闭包和JS垃圾回收的一点浅显理解,有不准确的地方,请大佬多多指点,小弟拜上。