堆栈溢出和内存泄漏

279 阅读3分钟

堆栈溢出的原理

堆栈溢出的产生是由于过多的函数调用,导致调用栈无法容纳这些调用的返回地址,一般在递归中产生。
是指内存空间已经被申请完,没有足够的内存提供了。

堆栈溢出的场景

递归

function isEven(n) {
    if (n === 0) {
        return true;
    }
    if (n === 1) {
        return false;
    }
    return isEven(Math.abs(n) - 2);
}

当传入的n值过大就会造成堆栈溢出。
会发生什么现象呢?

  1. 此时打开浏览器执行此js代码,会看到浏览器报错:Uncaught RangeError: Maxium call stack size exceeded
  2. 后面的代码没有继续执行
  3. 浏览器正常使用,没有卡顿,且打开任务管理器可看到浏览器占用CPU没有发生明显变化。

内存泄漏的原理

内存泄漏就是指程序中已经动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果的现象。
不论多大的内存都叫内存泄漏,并不一定是导致浏览器崩溃、卡顿才能叫内存泄漏。一般是堆区内存泄漏,栈区不会泄漏。

堆栈溢出的处理
  1. 异步调用(必须是宏任务)
// 原来写法(会发生堆栈溢出)
function test() {
    test();
}
test();

// 改写(不会发生堆栈溢出)
function test() {
    setTimeout(() => {
        test();
    })
}
test();

js的执行的规则是同步任务放到执行栈中,异步任务会先放到任务队列里,等同步任务执行后,再把异步任务push到执行栈里,依次执行。
同步任务的执行顺序是先进后出,异步任务的执行顺序是先进先出,微任务先执行,全部拉入执行栈,宏任务后执行,一次拉入一个进入执行栈。
2. 尾递归,复杂度由O(n)变为O(1);

内存泄漏的情形
  1. 闭包
function onclick() {
    let obj = document.getElementById("button");
    obj.onclick = function() {
        //
    }
}

以上例子中,函数内部定义了一个变量,这个变量的事件引用了内部函数,并且这个事件回调函数外暴了,形成闭包。
2. 意外的全局变量引起的内存泄漏

function() {
    menu = "1111";  //menu是一个全局变量,全局变量常驻内存
}
  1. 没有清理的dom元素的引用

dom元素不存在,该引用还存在。
4. 定时器没有被及时销毁

var contentByName = getcontentByName();
setInterval(function(){
    let content = document.getElementById('key_id');
    if (content) {
        content.txt = contentByName;
    }
}, 1000);
  1. 监听事件造成的泄漏

内存泄漏会发生什么呢

for(let i = 0; i < i + 1; i++) {
    console.log(i);
}
  1. 此时打开浏览器执行此js代码,会出现浏览器卡死现象,只能强制关闭浏览器。
  2. 打开任务管理器,发现CPU占用100%,尤其是运行此代码的浏览器,几乎占满了CPU。

如何防止堆栈溢出和内存泄漏

内存泄漏的避免
  • 谨慎使用闭包
  • 在业务不需要用到的内部函数,可以重构在函数外,实现解除闭包。
  • 闭包内,局部变量使用后或不再需要,及时清除掉。
  • 减少不必要的全局变量,如果用了,最好在声明周期钩子中或在函数调用之前,及时清除掉。
  • 减少生命周期较长的兑现,对不用的对象及时的释放。
  • 对注册的事件,再不用的时候,及时的解耦,释放资源。

堆栈溢出和内存泄漏的区别

堆栈溢出仅是js语法环境的“栈”溢出,而内存泄漏是会涉及到电脑硬件(变量过多未回收或其他原因导致浏览器占用cpu过高,浏览器卡死)