简介
闭包是啥,用一句通俗的话来说就是能够读取函数内部局部变量的函数。闭包在一个函数内部定义一个新的函数,在执行的时候,会将这个函数返回,也可能是返回一个对象。闭包为啥会出现呢,它其实是js作用域链的特性而产生的。我们在函数内部是可以直接访问函数外部的全局变量的,但是我们在函数的外部是无法直接获取到函数内部的局部变量的,如果我们想在函数外部获取函数内部的局部变量,那么就要用到闭包。
函数内部可以直接访问函数函数外部的全局变量
var num = 66
function fn1() {
console.log(num)
}
fn1() // 66
函数外部不可以访问函数内部的局部变量
function fn2() {
var kk = 88 // 这里一定要用var来定义,如果不用的话,kk会变量提升变成全局变量
}
console.log(kk) // Uncaught ReferenceError: kk is not defined
闭包的作用
1、读取函数内部的局部变量
function fn() {
var a = 5
return function() {
return a
}
}
console.log(fn()()) // 5
2、让局部变量的值始终保存在内存中
function num() {
var num = 100
add = function() {
num++
}
function getNum() {
return num
}
return getNum
}
var result = num()
console.log(result()) // 100
add()
console.log(result()) // 101
为啥说可以让局部变量始终保存在内存中呢?我们分析一下,首先getNum函数被赋予一个全局变量,这就导致getNum函数在内存中不会被垃圾回收机制清理掉,又因为getNum函数是依赖于num函数的,所以num函数也不会被垃圾回收机制清理掉,既然num函数不会被清理掉,那么num局部变量也就一直存在于内存中了。所以就会出现,你调用了两次num函数一次add,一次打印100,一次打印101。
这里有个要注意的点,add变量没有用var定义,所以他是全局变量,所以函数外面可以直接访问
闭包的缺点
看上面的例子,函数num始终在栈中得不到释放,就会长时间占用内存,就会造成内存泄露,内存泄露多了会造成内存溢出
使用场景
1、setTimeout传参
原生的setTimeout传递的第一个函数是不能带参数的,通过闭包可以实现带参
function fn1(name) {
function fn2() {
console.log(name)
}
return fn2
}
var fun = fn1('张三')
setTimeout(fun,1000) // 一秒钟之后打印张三
2、回调
定义行为,然后把它关联到某个用户事件上(点击或者按键)。代码通常会作为一个回调(事件触发时调用的函数)绑定到事件。
<body>
<a href="#" id="size-12">12</a>
<a href="#" id="size-20">20</a>
<a href="#" id="size-30">30</a>
<script type="text/javascript">
function changeSize(size) {
return function() {
document.body.style.fontSize = size + 'px';
}
}
document.getElementById('size-12').onclick = changeSize(12);
document.getElementById('size-20').onclick = changeSize(20);
document.getElementById('size-30').onclick = changeSize(30);
</script>
</body>
3、函数防抖
在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。实现的关键就在于setTimeOut这个函数,由于还需要一个变量来保存计时,考虑维护全局纯净,可以借助闭包来实现。
/*
* fn [function] 需要防抖的函数
* delay [number] 毫秒,防抖期限值
*/
function debounce(fn,delay) {
let timer = null
return function() {
if(timer) {
// 说明在一个计时过程中,又触发了相同的事件,所以需要清除定时器,重新定时
clearTimeout(timer)
timer = setTimeout(fn,delay)
}else {
timer = setTimeout(fn,delay)
}
}
}
4、循环赋值
for(i=0;i<10;i++) {
(function(j){
console.log(j*10)
})(i)
}
// 0 10 20 30 40 50 60 70 80 90