理解堆栈溢出和内存泄漏的原理,如何防止
堆栈溢出
JS中的数据存储分为栈和堆,栈遵循先进后出的原则,所以程序从栈底开始计算,程序内部函数的调用以及返回会不停的执行进栈和出栈的操作,一旦调用即进栈过多就会导致栈满。一般出现在递归中。 一个递归爆炸的例子:
function isEven(n){ if(n===0)return true if(n===1)return false return isEven(Math.abs(n)-2) }复制代码
当n为10时,可以正常输出true,运行的也很快。
当n为10000000时,会抛出错误Uncaught RangeError: Maximum call stack size exceeded
,即最大调用超过堆栈大小。
解决方法使用闭包和Trampoline来解决:
function isEven(n) { function isEvenInner(n) { if (n === 0) return true if (n === 1) return false return isEven(Math.abs(n) - 2) } function trampoline(func,arg) { var value=fun(arg) while (typeof value==='function')value=value() return value } return trampoline.bind(null,isEvenInner)(n) }复制代码
内存泄露
申请的内存执行完之后没有及时的清理和销毁,占用空闲内存,既不能使用也不能回收。 几种会导致内存泄露的情况:
意外的全局变量
被遗忘的计时器或回调函数
脱离DOM的引用(分离的DOM节点)
<div id="root"> <div class="child">我是子元素</div> <button>移除</button> </div> <script> let btn = document.querySelector('button') let child = document.querySelector('.child') let root = document.querySelector('#root') btn.addEventListener('click', function() { root.removeChild(child)//虽然该.child节点确实从dom中被移除了,但全局变量child仍然对该节点有引用,导致该节点的内存一直无法释放 }) </script>复制代码
解决方法
<div id="root"> <div class="child">我是子元素</div> <button>移除</button> </div> <script> let btn = document.querySelector('button') btn.addEventListener('click', function () { let child = document.querySelector('.child') let root = document.querySelector('#root') //当移除节点并退出回调函数的执行上下文后会自动清除对该节点的引用 root.removeChild(child) }) </script>复制代码
闭包使用不当导致的内存泄露
控制台打印
如何避免内存泄露?
减少不必要的全局变量,使用
严格模式
来避免创建意外的全局变量。使用完数据之后,及时解除引用(闭包中的变量 DOM引用 定时器清除)。
组织好逻辑,避免死循环等造成浏览器卡顿,崩溃等问题。
作者:两支耳朵都听到啦
链接:https://juejin.cn/post/7029212003642114062