定义
什么是闭包?
闭包就是在一个函数定义的作用域外,使用该函数作用域内的局部变量,它通常是在嵌套函数中发生的。它的原理是基于词法作用域链和垃圾回收机制,通过维持函数作用域的引用,让函数作用域可以在当前作用域外被访问到。
作用
为什么闭包会产生呢?或者说闭包的作用是什么?
让我们先来看看下面这个例子:
var age = 18;function person() {age++;console.log(age);// cat函数内输出age,该作用域没有,则向外层寻找,结果找到了,输出[19];}person(); // 19person(); // 20
由上述例子可知,如果我们再次调用时,结果会一直增加,也就是变量age的值一直递增;并且当其他函数调用该变量age时,将会很容易修改,这也就是全局变量容易污染的原因。为了解决变量污染问题,我们可以把变量修改到函数内,让它成为局部变量。
function person() {var age = 18;function student() {age++;console.log(age);}return student()}person(); // 19person(); // 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,这会污染作用域;同时,当页面需要实现多个防抖函数时,我们将需要写很多重复代码。
为了解决这两个问题,我们可以采用闭包,既能保护全局作用域不受污染,又能做到函数重用。
