<script> // js闭包简单来说就是 : 子函数可以使用父函数的局部变量,还有父函数的参数。 var add = (function () { var counter = 0; return function () { return counter += 1; } })(); add(); add(); alert(add()); </script>
练习题如下:
javascript 代码
效果预览
1
2
3
4
5
6
for
(
var
i
=
0
;
i
<
3
;
i
++
)
{
setTimeout
(
function
(
)
{
console
.
log
(
i
)
;
}
,
0
)
;
console
.
log
(
i
)
;
}
结果是:0 1 2 3 3 3
很多公司面试都爱出这道题,此题考察的知识点还是蛮多的。
为了防止初学者栽在此问题上,此文稍微分析一下。
都考察了那些知识点呢?
异步、作用域、闭包,你没听错,是闭包。
我们来简化此题:
setTimeout(function() {先打印2,后打印1。
console.log(1);
}, 0);
console.log(2);
因为是setTimeout是异步的。
正确的理解setTimeout的方式(注册事件):
有两个参数,第一个参数是函数,第二参数是时间值。
调用setTimeout时,把函数参数,放到事件队列中。等主程序运行完,再调用。
没啥不好理解的。就像我们给按钮绑定事件一样:
btn.onclick = function() {这么写完,会弹出1吗。不会!!只是绑定事件而已!
alert(1);
};
必须等我们去触发事件,比如去点击这个按钮,才会弹出1。
setTimeout也是这样的!只是绑定事件,等主程序运行完毕后,再去调用。
setTimeout的时间值是怎么回事呢?
比如:
setTimeout(fn, 2000)我们可以理解为2000之后,再放入事件队列中,如果此时队列为空,那么就直接调用fn。如果前面还有其他的事件,那就等待。
因此setTimeout是一个约会从来都不准时的童鞋。
继续看:
setTimeout(function() {程序会不会报错?
console.log(i);
}, 0);
var i = 1;
不会!而且还会准确得打印1。
为什么?
因为真正去执行console.log(i)这句代码时,var i = 1已经执行完毕了!
所以我们进行dom操作。可以先绑定事件,然后再去写其他逻辑。
window.onload = function() {这么写,完全是可以的。因为异步!
fn();
}
var fn = function() {
alert('hello')
};
es5中是没有块级作用域的
for (var i = 0; i < 3; i++) {}也就说i可以在for循环体外访问到。所以是没有块级作用域。
console.log(i);
但此问题在es6里终结了,因为es6,发明了let。
这回我们再来看看原题。
原题使用了for循环。循环的本质是干嘛的?
是为了方便我们程序员,少写重复代码。
让我们倒退50年,原题等价于:
javascript 代码
效果预览
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var
i
=
0
;
setTimeout
(
function
(
)
{
console
.
log
(
i
)
;
}
,
0
)
;
console
.
log
(
i
)
;
i
++
;
setTimeout
(
function
(
)
{
console
.
log
(
i
)
;
}
,
0
)
;
console
.
log
(
i
)
;
i
++
;
setTimeout
(
function
(
)
{
console
.
log
(
i
)
;
}
,
0
)
;
console
.
log
(
i
)
;
i
++
;
因为setTimeout是注册事件。根据前面的讨论,可以都放在后面。
原题又等价于如下的写法:
javascript 代码
效果预览
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var
i
=
0
;
console
.
log
(
i
)
;
i
++
;
console
.
log
(
i
)
;
i
++
;
console
.
log
(
i
)
;
i
++
;
setTimeout
(
function
(
)
{
console
.
log
(
i
)
;
}
,
0
)
;
setTimeout
(
function
(
)
{
console
.
log
(
i
)
;
}
,
0
)
;
setTimeout
(
function
(
)
{
console
.
log
(
i
)
;
}
,
0
)
;
这回你明白了为啥结果是0 1 2 3 3 3了吧。
那个,老姚你说它是闭包,又是怎么回事?
为了很好的说明白这个事情,我们把它放到一个函数中:
var fn = function() {上面的函数跟我们常见另一个例子(div绑定事件)有什么区别:
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i);
}, 0);
console.log(i);
}
};
fn();
var fn = function() {点击每个div都会弹出3。道理是一样的。因为alert(i)中的i是fn作用越中的,因而这是闭包。
var divs = document.querySelectorAll('div');
for (var i = 0; i < 3; i++) {
divs[i].onclick = function() {
alert(i);
};
}
};
fn();
《javascript忍者秘籍》书里把一个函数能调用全局变量,也称闭包。
因为作者认为全局环境也可以想象成一个大的顶级函数。
怎么保证能弹出0,1, 2呢。
解决之道:以毒攻毒!
再创建个闭包!!
var fn = function() {或者如下的写法:
var divs = document.querySelectorAll('div');
for (var i = 0; i < 3; i++) {
divs[i].onclick = (function(i) {
return function() {
alert(i);
};
})(i);
}
};
fn();
var fn = function() {因此原题如果也想setTimeout也弹出0,1,2的话,改成如下:
var divs = document.querySelectorAll('div');
for (var i = 0; i < 3; i++) {
(function(i) {
divs[i].onclick = function() {
alert(i);
};
})(i);
}
};
fn();
javascript 代码
效果预览
1
2
3
4
5
6
7
8
for
(
var
i
=
0
;
i
<
3
;
i
++
)
{
setTimeout
((
function
(
i
)
{
return
function
(
)
{
console
.
log
(
i
)
;
}
;
})
(
i
)
,
0
)
;
console
.
log
(
i
)
;
}
本文完。
原文链接 : http://www.qdfuns.com/notes/17398/e8a1ce8f863e8b5abb530069b388a158/page/.html