闭包是JS中最基础也是最重要的概念之一。闭包绝不是一个单一的概念。涉及作用域、作用域链、执行上下文、内存管理等多个知识点。1.范围首先是范围。在ES6之前,只有函数作用域和全局作用域。在ES6中,let和const声明的变量的块级作用域使得JS的作用域更加丰富。再来说说变量提升和临时死区。下面看一个var声明变量的例子functionf1(){console.log(b);//undefinedvarb=2}f1()var声明的变量会提前声明,但是没有赋值,所以undefined是输出,这里如果把var换成let或者const,会报referenceError。原因是let和const声明的变量会在未声明的区域形成一个“死区”。专业名称是TDZ(TemporalDeadZone)。它开始于函数的开头,结束于变量声明语句。在这个“死区”中调用变量,会报错,在死区外可以正常访问变量。临时死区的一个极端情况是,功能的参数默认值设置也会受其影响。例子如下:functionfan(a1=a2,a2){console.log(a1,a2);}fan(1,2)fan(null,2)fan(undefined,2)调用fan3次,what分别是输出,第一次是1、2,第一次是null、2,第三次报错。第三次,因为传递了undefined,所以默认不传递,给a1赋值a2,但是此时a2没有声明,所以报错。2、执行上下文和调用栈执行上下文是当前代码的执行环境/作用域,与作用域相辅相成,但它们是两个完全不同的概念。2.1代码执行的两个阶段JS代码执行分为两个阶段,一个是代码预编译阶段,一个是代码执行阶段。在预编译阶段,会声明变量,提升变量,但值是undefined。非表达式函数声明得到提升。在执行阶段,代码逻辑会被执行,执行上下文会在这个阶段被完全创建。看一个例子functionfbn(){console.log('a');}varfbn=function(){console.log('b');}fbn()这里会输出b,如果顺序改变varfbn=function(){console.log('b');}functionfbn(){console.log('a');}fbn()这里的输出还是b为什么?因为fbn的变量是在预编译阶段提升的,没有赋值,然后创建函数fbn并声明,接下来就轮到fbn赋值了。fbn的内容赋值给了函数体为console.log('b')的函数,所以输出b的作用域在预编译阶段就确定了,但是作用域链是在创建执行上下文之后生成的,因为函数调用会创建相应的执行上下文。执行上下文包括变量对象、作用域链和this指向2.2调用栈函数b在函数a中被调用,函数c在函数b中被调用,这样就形成了一系列的调用栈,a、b、c依次入栈,c执行出栈,b执行出栈,最后a执行出栈,形成调用栈。通常情况下,当函数执行完出栈时,函数中的局部变量会在下一个垃圾回收(GC)节点被回收,函数的执行上下文会被销毁,这就是为什么定义在函数中的变量外部世界无法访问该功能,也就是说,只有当函数被执行时,相关的函数才能访问函数中的变量。变量将在预编译阶段创建,在执行阶段激活,并在函数执行结束时销毁。3.闭包先说这么多前置概念,相信大家大概了解了闭包的工作原理。前面我们知道,在正常情况下,外界是无法访问函数中的变量的。函数执行后,其变量将被销毁。但是如果a函数中返回了一个b函数,返回的b函数使用了a函数中的变量,那么外界就可以通过b函数访问a函数的变量,这就是闭包原理。3.1内存管理内存空间分为堆空间和栈空间。栈空间存放undefined、string、null、number、boolean等基本数据类型,堆空间存放固定内存大小的object、array、function等引用类型。内存大小不固定3.2内存泄漏内存泄漏是指内存空间明明不再使用,但由于某种原因没有被释放的现象。内存泄漏的危害非常直观,它会导致程序运行缓慢,甚至崩溃。看一个例子varele=document.querySelector('div')ele.index='0'ele.parentNode.removeChild(ele)这里只是移动了节点Except,变量ele仍然存在,这个变量占用的内存不能被释放,所以添加ele=null应该更安全另一个例子varele=document.querySelector('div')ele.innerHTML='
