闭包:函数和对其周围状态(词法环境)的引用捆绑在一起构成闭包,闭包可以让你从内部函数访问外部函数作用域。
看了这位大佬的文章了解了很多,指路====》转载自闭包
首先一道题:
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(new Date, i);
}, 1000);
}
console.log(new Date, i);
打印出来是5 5 5 5 5 5
,没有问题。
- 而代码实际运行情况呢?怎么描述? 应该是
5 - > 5, 5, 5, 5, 5
- 为什么?—— 循环执行过程中,几乎同时设置了5个定时器,而且都是在一秒后触发,而且循环完的输出几乎是立即执行的。
如果期望输出为:5 -> 0,1,2,3,4
,怎么解决?
方法一:闭包
for(var i=0;i<5;i++){
(function (j){
setTimeout(function(){
console.log(new Date, j)
}, 1000)
})(i)
}
console.log(new Date, i)
方法二:setTimeOut第三个参数
for(var i=0;i<5;i++){
setTimeout(function(j){
console.log(new Date, j)
}, 1000, i)
}
console.log(new Date, i)
方法三:函数传参(利用js基本类型的参数传参是按值传参)
var output = function (i) {
setTimeout(function(){
console.log(new Date, i)
}, 1000)
}
for(var i=0;i<5;i++){
output(i)
}
console.log(new Date, i)
方法四: es6 let(会报错)
可以倒是可以但是会报错。。。因为最后的那个i的作用域并没有i定义
for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(new Date, i);
}, 1000);
}
console.log(new Date, i);
如果期望输出改为0 -> 1 -> 2 -> 3 -> 4 -> 5
,怎么处理?
可以在之前的基础上进行修改,如下:
for (var i = 0; i < 5; i++) {
(function (j){
setTimeout(function() {
console.log(new Date, j);
}, 1000 * j);
})(i)
}
setTimeout(function(){
console.log(new Date, i)
},1000 * i)
这种思路是可以的,但是全部都是异步操作,不是很好。
可以使用Promise:
const task = []
for (var i=0;i<5;i++) {
((j) => {
task.push(new Promise((resolve) => {
setTimeout(() => {
console.log(new Date,j)
resolve()
}, 1000 * j)
}))
})(i)
}
Promise.all(task).then(() => {
setTimeout(() => { // 这个promise.all里面一定要个setTimeOut,否则最后4,5会一起出来,就不能实现打印4隔一秒再打印5
console.log(new Date, i)
}, 1000)
})
闭包的实用:
闭包允许将函数与其所操作的某些数据(环境)关联起来。类似于面向对象编程。
可以使用闭包模拟私有方法:
闭包的缺点:
- 闭包使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用。否则会造成内存泄漏,造成网页的性能问题。(解决:在退出函数之前,将不使用的局部变量全部删除)
- 闭包会在父函数外部,改变父函数内部变量的值,如果把父函数当成对象使用,会把闭包当成他的公有方法,把内部变量当成私有变量。
闭包的优点:
- 闭包加强了封装性,保护变量安全
- 私有成员的产生
- 延长作用域链