时间函数 setTimeout
let a = 0;
setTimeout(function () {
console.log(++a);
}, 1000);
这个例子用到了时间函数setTimeout,在等待 1 秒钟后对变量 a 进行加 1 的操作。这是一个闭包,因为 setTimeout 中的匿名函数对外部变量进行访问,然后该函数又被 setTimeout 方法引用。满足了形成闭包的两个条件。所以即使外部上下文结束了,1 秒后仍然能对变量 a 进行加 1 操作。
在 DOM 的事件操作中
<body>
<input id="count" type="button" value="计数">
<script>
(function(){
var count = document.getElementById("count");
var num = 0;
count.onclick = function(){
console.log(++num);
}
})()
</script>
</body>
在这个例子中,onclick 指向的函数中访问了外部变量 num,同时该函数又被 onclick 事件引用了,满足 2 个条件,是闭包。所以当外部上下文结束后,你继续点击按钮,在触发的事件处理方法中仍然能访问到变量 num。
img 数据上报
const report = function (src) {
let img = new Image();
img.src = src;
}
report('http://192.168.*.**/****/*****');
在这个例子中,我们可以通过查询后台的记录得知,因为一些低版本的浏览器的实现存在 bug,在这些浏览器下使用 report 函数进行数据上报时会丢失 30% 左右的数据,也就是说,report 函数并不是每一次都成功发起了 HTTP 请求。丢失数据的原因是 img 是 report 函数中的局部变量,当 report 函数在调用结束后,img 局部变量随即被销毁,而此时或许还没来得及发出 HTTP 请求,所以此次请求就会丢失掉。
而这个例子我们可以用闭包重构一下
const report = (function () {
let imgs = [];
return function (src) {
let img = new Image();
imgs.push(img);
img.src = src;
}
})();
这样就可以保证每次请求的成功。
闭包引起的奇怪问题
for (var i = 1; i <= 3; i++) {
setTimeout(function () {
console.log(i);
}, 1000);
}
我们预期的结果是过 1 秒后分别输出 i 变量的值为 1,2,3。但是,执行的结果是:4,4,4。
既然是闭包引起的问题,那么解决的方法就是去掉闭包。我们知道形成闭包有两个条件,只要不满足其一,那就不再是闭包。
- 条件一,内部函数被外部引用,这个我们没办法去掉。
- 条件二,内部函数访问外部变量。这个条件我们有办法去掉,比如:
for (var i = 1; i <= 3; i++) {
(function (index) {
setTimeout(function () {
console.log(index);
}, 1000);
})(i)
}
用一个立即执行函数重新包裹一下,这样 setTimeout 中就可以不用访问 for 循环声明的变量 i 了。而是采用调用函数传参的方式把变量 i 的值传给了 setTimeout,这样它们就不再形成闭包。也就是说 setTimeout 中访问的已经不是外部的变量 i,所以即使 i 的值增长到 4,跟它内部也没关系,最后达到了我们想要的效果。
当然这个来说也是相对比较麻烦的,因此这里提供一种更加简便的方法:
for (let i = 1; i <= 3; i++) {
setTimeout(function () {
console.log(i);
}, 1000);
}
使用 ES 6 中的 let 关键字。它声明的变量有块作用域,如果将它放在循环中,那么每次循环都会有一个新的变量 i,这样即使有闭包也没问题,因为每个闭包保存的都是不同的 i 变量。
闭包与面向对象
过程与数据的结合是形容面向对象中的"对象"时经常使用的表达。对象以属性的形式包含了数据,以方法的形式包含了过程。而闭包则是在过程中以环境的形式包含了数据。通常用面向对象思想能实现的功能,用闭包也能够实现,反之亦然。
下面我们使用闭包来实现一个完整的面向对象的系统。
const Test = function(){
let value = 0;
return {
call : function(){
value++;
console.log(value);
}
}
}
const test = new Test();
test.call(); // 1
test.call(); // 2
test.call(); // 3
如果换成面向对象的写法:
const test = {
value: 0,
call: function () {
this.value++;
console.log(this.value);
}
}
test.call(); // 1
test.call(); // 2
test.call(); // 3
或者
const Test = function () {
this.value = 0;
}
Test.prototype.call = function () {
this.value++;
console.log(this.value);
}
const test = new Test();
test.call(); // 1
test.call(); // 2
test.call(); // 3
本文章取自本人JS语言导师谢老师的学习总结,同时也感谢谢老师对我的谆谆教诲,感谢他带我走上前端这条道路,并让我为之不断向前