定义
什么是闭包?
闭包就是在一个函数定义的作用域外,使用该函数作用域内的局部变量,它通常是在嵌套函数中发生的。它的原理是基于词法作用域链和垃圾回收机制,通过维持函数作用域的引用,让函数作用域可以在当前作用域外被访问到。
作用
为什么闭包会产生呢?或者说闭包的作用是什么?
让我们先来看看下面这个例子:
var age = 18;
function person() {
age++;
console.log(age);// cat函数内输出age,该作用域没有,则向外层寻找,结果找到了,输出[19];
}
person(); // 19
person(); // 20
由上述例子可知,如果我们再次调用时,结果会一直增加,也就是变量age的值一直递增;并且当其他函数调用该变量age时,将会很容易修改,这也就是全局变量容易污染的原因。为了解决变量污染问题,我们可以把变量修改到函数内,让它成为局部变量。
function person() {
var age = 18;
function student() {
age++;
console.log(age);
}
return student()
}
person(); // 19
person(); // 19
可以看出,此时每次调用函数person,进入该作用域时,变量age都会重新赋值18。同时,变量age已在函数内部,不易修改和外泄,相对比较安全。
所以说,闭包有以下几个作用:1、隐藏变量,避免全局污染; 2、可以读取函数内部的变量。
缺点
既然闭包能够做到这些,那它会不会有什么缺点?
缺点1:变量难以被垃圾回收机制回收,造成内存过度占用
function person(propertyName) {
return function(object) {
let student = object[propertyName]
console.log(student)
}
}
let personName = person('name')
// 调用函数
let studentName = personName({name: 'btqf'})
// 解除函数的引用,释放内存
personName = null
我们现在可以知道,闭包是基于作用域链和垃圾回收机制去读取某函数内部的变量。就拿上述的person()来说,person()的活动对象并不能在它执行完毕后销毁,因为匿名函数的作用域链中依然有对它的引用。当person()执行完毕后,其执行上下文的作用域链会被销毁,但它的活动对象仍会保留在内存中,直到匿名函数被销毁后才会被销毁。为了释放内存,可以把personName设置为null从而解除对函数的引用。
因为闭包会保留它们包含函数的作用域,所以比其他函数更占用内存,过度使用闭包可能导致内存过度占用缺点2:不恰当的使用闭包可能会造成内存泄漏
function assignHandler() {
let element = document.getElementById('someElement')
element.onclick = () => console.log(element.id)
}
以上代码中,匿名函数引用着assignHandler()的活动对象,阻止了element的
引用计数
(详情可学习垃圾回收机制) 归零。只要该匿名函数在,element的引用计数就至少等于1,即内存不会回收。应用场景
说了这么多,它主要能用来干什么?
通常在定时器、事件监听器、ajax请求、跨窗口通信、webworkers或者其他的同步/异步任务中,只要使用了回调函数,实际上就是使用了闭包。比较经典的题目大概有防抖节流函数
.
以下为防抖函数实现:function debounce(func, wait) {
let timer = null;
return function () {
if (timer) {
clearTimeout(timer);
timer = null;
}
let self = this;
let args = arguments;
timer = setTimeout(function () {
func.apply(self, args);
timer = null;
}, wait);
};
}
为什么要使用闭包呢?我们可以看看不使用闭包去实现防抖函数:
let timer = null;
function dalay() {
if (timer) {
clearTimeout(timer);
timer = null;
}
timer = setTimeout(() => {
// do sth...
}, wait)
}
在上述的实现中,由于需要声明全局代码
timer
,这会污染作用域;同时,当页面需要实现多个防抖函数时,我们将需要写很多重复代码。
为了解决这两个问题,我们可以采用闭包,既能保护全局作用域不受污染,又能做到函数重用。