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

js高级内存管理与闭包

时间:2023-03-26 20:49:45 JavaScript

javacript中的内存管理javascript不需要我们手动分配内存,当我们创建变量时,它会自动为我们分配内存。在创建基本数据类型时,会在栈内存中开辟空间来存放变量。创建引用数据类型时,会在堆内存中开辟空间保存引用数据类型,并返回堆内存中数据的指针,供变量引用varname="alice"varuser={name:"kiki",age:16}在内存中声明了两种不同类型的变量如下垃圾回收机制内存是有限的,当一些内存不需要使用的时候,我们需要将其释放,以腾出更多的内存空间。javascript中有两种垃圾回收算法。1.引用计数当对象有引用指向它时,计数加1,当引用被移除时,计数减1。当计数为0时,该对象将被垃圾收集器自动销毁。这种回收机制可能存在问题。当两个对象发生循环引用的情况下,这两个对象都不会被销毁,可能会出现内存泄漏。2、标记并清理一个根对象,垃圾收集器会周期性地从这个根开始,从根开始查找所有被引用的对象。销毁未引用对象等算法可以有效解决循环引用问题。下图中,MN从根节点找不到被引用的对象,就会被垃圾回收器销毁。javascript中函数的多次使用,函数非常重要,应用广泛。最常用于以下几种:1.将一个函数作为参数传递,可以直接作为另一个函数的参数,直接调用执行。下面定义了多种计算数的方法,比如加法、减法、乘法,不需要每次都调用不同的函数进行不同的计算,只需要改变传入的参数functioncalcNum(num1,num2,fn){console.log(fn(num1,num2))}functionadd(num1,num2){returnnum1+num2}functionminus(num1,num2){returnnum1-num2}functionmul(num1,num2){returnnum1*num2}calcNum(10,20,add)//302,也可以作为返回值函数返回一个函数,下面的函数称为高阶函数,也属于柯里化函数,可以接收和多次返回并统一处理functionmakeAdder(count){functionadd(num){returncount+num}returnadd}varadd5=makeAdder(5)console.log(add5(6))console.log(add5(10))varadd10=makeAdder(10)varadd100=makeAdder(100)3.作为回调函数,数组中有很多方法需要我们自定义处理数据的回调函数varnums=[10,50,20,100,40]varnewNums=nums.map(function(item){returnitem*10})闭包如如果一个函数可以访问外层的自由变量,那么它就是一个闭包。如下代码所示,bar函数可以访问父scope中的变量name和agefunctionfoo(){varname="foo"varage=18functionbar(){console.log(name)console.log(age)}returnbar}varfn=foo()fn()上面代码的执行结果为foo18根据代码的执行顺序,foo函数执行完毕,其函数上下文已经出栈,但是为什么foo函数中的变量可以保存呢?因为foo函数在创建执行上下文的同时创建了一个AO对象,这个AO对象仍然被bar函数的parentScope指向,所以不会被垃圾回收器销毁。上面代码在内存中的执行过程如下Javascript-->AST在内存中创建一个0x100的空间来保存函数foo,其中父作用域(parentScope)指向GO内存中的GO(GlobalObject)对象,其中包含了String,Number等内置模块,并将定义好的全局变量保存到GO中,这里在GO中添加fn,值为undefined,在GO中添加函数foo,值为0x100Ignition处理ASTV8引擎执行代码,有调用栈ECStack,创建全局执行上下文,VO指向GO创建的函数foo的函数执行上下文,**VO(可变对象)指向foo的AO(活动对象)**,执行函数体中代码创建foo的AO对象,在AO中添加name和age,值为undefined,执行代码前,给foo中的AO对象函数bar开辟内存空间0x200,bar函数的父作用域指向AO将foo添加到AO对象中,value为0x200执行代码foobar函数的返回值赋值给fn,所以fn的值为bar函数的内存地址0x200执行foo函数,将foo的AO对象中的name赋值给foo,并将age赋值为18。bar函数在执行fn函数之前创建函数执行上下文,VO指向bar的AO创建bar的AO。bar函数中没有定义变量。所以AO为空并执行fn函数,输入name和age并执行foo函数被执行,foo函数的执行上下文弹出调用栈。函数执行完毕后,bar函数的执行上下文弹出调用栈。bar的AO对象是在函数执行上下文存在的时候创建的,此时并没有被其他地方引用,所以会被垃圾回收器销毁bar函数被赋值给一个全局变量,不会被销毁,而bar的父作用域指向foo的AO对象,所以foo的AO对象不会被销毁,所以在bar函数中可以访问到foo中的变量icon上面foo的AO对象引用如下,所以它没有被破坏。如果此时只引用了AO对象的部分变量,而其他变量没有被使用,那么未使用的变量会不会被销毁呢?例如,根据ECMAScript规范,变量agefunctionfoo(){varname="foo"varage=18returnfunction(){console.log(name)}}varfn=foo()fn()是不允许的,因为整个AO对象都保存在内存中,但是JS引擎可能会做一些优化。例如,Chome浏览器使用的V8引擎在上述闭包中添加了调试器,用于调试functionfoo(){varname="foo"varage=18returnfunction(){debuggerconsole.log(name)}}varfn=foo()fn()有两种方法可以测试foo的变量age没有被保存1.Sources中ViewClosure保存的变量代码执行到调试器,此时闭包的作用域可以是看过。只有变量name2保存在父作用域foo中。当代码执行到调试器时,此时控制台是关闭的。在包的执行环境中,可以直接打印variables,直接打印name,而直接打印age,省去了undefinedmemoryleaks。如上例,保存到全局的闭包,因为有相互引用,不会被销毁,如果以后不再使用,可能会出现内存泄漏测试函数createFnArray(){vararr=newArray(1024*1024).fill(1)returnfunction(){console.log(arr.length)}}vararrayFns=[]for(vari=0;i<100;i++){setTimeout(()=>{arrayFns.push(createFnArray())},i*100)}setTimeout(()=>{for(vari=0;i<50;i++){setTimeout(()=>{arrayFns.pop()},100*i)}},10000)上面的代码每隔0.1s创建一个内存容量为1024*1024(约4M)的数组保存在全局变量中,一共100个,再过10s,每隔0.1s从数组底部弹出一个元素,一共50个。这个操作在内存上的表现应该是第一次内存占用逐渐增加10秒。在第10秒,内存使用量约为400M。15s后,内存使用减少一半,因为垃圾收集器不会立即回收或销毁垃圾,所以有可能会有一定的时间延迟释放内存。大量的内存会导致内存泄漏。当你不需要使用它的时候,你需要及时释放它。只需要将变量指向null就可以释放内存。varfn=foo()//不用当fn=null以上是对内存和闭包的理解。关于高级js,开发者需要掌握的东西还是很多的。可以看看我写的其他博文,持续更新中~