当前位置: 首页 > Web前端 > HTML

前端开发核心知识进阶第1章第2节《老手》也会在闭包上翻车

时间:2023-03-28 17:07:47 HTML

闭包是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='click'varbtn=document.querySelector('#btn')btn.addEventListener('click',function(){//...})ele.innerHTML=''这里按钮已经在DOM中被移除了,但是事件处理程序是仍然存在,节点变量无法回收,所以需要添加removeEventListener函数来防止内存泄漏。除了开发者主动保证回收之外,浏览器在大多数场景下依赖于标记清除和引用计数回收一个闭包函数的算法示例fcn(){letvalue=1functionfdn(){console.log(value);}returnfdn}varfdn=fcn()fdn()在谷歌浏览器的V8引擎中执行,在fdn函数中设置断点,你会发现有一个闭包变量值最后,我们举一个实际的例子varfn=nullconstfun=()=>{vara=1functionffn(){console.log(a);控制台日志(b);}fn=ffn}letbar=()=>{varb=2fn()}fun()bar()的结果是什么?结果为1且ReferenceError:bisnotdefined,fn被赋值为ffn,变量b不在其作用域链中,如果b=2在fun或全局声明,则输出2