参考:http://www.360doc.com/content/19/0529/15/64276718_838982036.shtml
什么是闭包
闭包是能够读取函数内部定义的变量的函数
函数内部定义的变量,在函数外无法调用到,并且在函数执行完以后,定义变量的内存也会被回收,闭包函数因为是函数内部的子函数,可以访问到上级函数定义的变量,即使上级函数执行完作用域也不会销毁
补充:函数的作用域
js变量分为全局变量和局部变量,在函数内部可以直接调用全局变量,但是函数为不能调用函数内的变量,变量的作用域最多只能是定义时的函数作用域
闭包解决了什么问题
- 可以读取函数内部的值
- 让函数内定义的变量一直保存在内存中,不会在函数被调用后清除
function addCounter() {
let counter = 0
const myFunction = function () {
counter = counter + 1
return counter
}
return myFunction
}
const increment = addCounter()
const c1 = increment()
const c2 = increment()
const c3 = increment()
console.log('increment:', c1, c2, c3);
// increment: 1 2 3
在这段代码中increment实际上就是闭包函数myFunction, 它一共运行了三次,第一次的值是1,第二次的值是2,第三次的值是3。这证明了,函数addCounter中的局部变量counter一直保存在内存中,并没有在addCounter调用后被自动清除。
闭包与setTimeout的应用:如何取到正确的值
for (var i = 0; i < 10; i++) {
setTimeout(function () {
console.log(i) // 输出结果10个10
}, 1000)
}
上述代码执行流程:for循环为同步任务,正常执行,执行第一次for循环,函数体内只有setTimeout,setTimeout为异步任务中的宏任务,将其置于异步任务队列中等待主线程执行完再执行,此时for循环体中没有其他代码,第一次for循环已经结束,开始第二次for循环,i自增1,以次类推,当for循环完毕,此时主线程上的同步任务已经执行完,开始执行任务队列中的微任务,此例并没有微任务,所以开始第二次事件循环,执行任务队列中的宏任务也就是setTimeout,这时由于for循环已经循环完10次,i的值此时为10,所以setTimeout中打印出来的i值也为10,并且打印10次
正确的取到每次循环的值,如下:
for (var i = 0; i < 10; i++) {
((j) => {
setTimeout(function () {
console.log(j) //1-10
}, 1000)
})
(i)
}
上述代码执行流程与上一个例子类似,只不过for循环中是一个function,将循环变量 i 作为形参传给function,function函数体内的实参并不是作为循环变量的i,而是当前 i 的拷贝,所以每次执行function,都会将当前的 i 复制后传递到函数体,以此实现了打印出正确的数字
闭包模拟私有变量
闭包的缺点
-
由于闭包会是的函数中的变量都被保存到内存中,滥用闭包很容易造成内存消耗过大,导致网页性能问题。解决方法是在退出函数之前,将不再使用的局部变量全部删除。
-
闭包可以使得函数内部的值可以在函数外部进行修改。所有,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。