堆栈溢出

什么是堆栈溢出?我们知道JS中的数据存储分为栈和堆,程序代码运行都需要一定的计算存储空间,就是栈了,栈遵循先进后出的原则,所以程序从栈底开始运行计算,程序内部函数的调用以及返回会不停的执行进栈和出栈的操作(递归),栈内被所占的资源也在不断的对应变化,但是一旦你的调用即进栈操作过多,返回即出栈不够,这时候就会导致栈满了,再进栈的就会溢出来。

用代码举例;

function isEven(n) {
    if (n === 0) {
        return true;
    }
    if (n === 1) {
        return false;
    }
    return isEven(Math.abs(n) - 2);
}
//1.console.log(factorial(10))    true    运行比较快
//2.console.log(factorial(10000000))    Uncaught RangeError: Maximum call stack size exceeded
//错误是 最大调用超过堆栈大小

递归层多了就报错,这是为什么呢?原因就是,程序在执行代码过程中,需要一定的计算空间即栈,一般大小为1M左右,当你每次调用程序内的函数等其它时,这些就会占用一定的空检,当占用过多时,就会超过该程序所分配的栈的空间,就会报错了。那么,如何解决这个问题?就拿上面的递归例子来说,解决办法如下(前文我们提到了闭包,这里就用闭包来解决):

function isEven (num) {
    if (num === 0) {
        return true;
    }
 
    if (num === 1) {
        return false;
    }
 
    return function() {
        return isEven(Math.abs(num) - 2);
    }
}
//Outputs: true
console.log(isEven(4)()());

//此时每次调用时,返回一个匿名函数,匿名函数执行相关的参数和局部变量将会释放,不会额外增加堆栈大小。
//个人理解,新出来一层,然后外层的就销毁了,因为最外层的isEven已经销毁了

内存泄漏

什么是内存泄漏?内存泄漏是指程序被分配的栈内有一块内存既不能使用,也不能被回收。 (此处可以联系强引用弱引用知识点、Map和Object的区别知识点)

导致内存泄露的情况:

//1.*********************************************
//【函数内未使用声明变量关键字的变量[意外的全局变量]】
//全局变量的生命周期最长,直到页面关闭前,它都存活着,所以全局变量上的内存一直都不会被回收。
function func() {
    a = 1;
}
//2.*********************************************
//【未销毁的定时器,要记得clearTimeout()】
//setTimeout 和 setInterval 是由浏览器专门线程来维护它的生命周期。
//所以当在某个页面使用了定时器,当该页面销毁时,没有手动去释放清理这些定时器的话,那么这些定时器还是存活着的。
setInterval(function () {
    console.log(1)
}, 1000);
//3.*********************************************
//【DOM以外的节点引用】
//DOM 元素的生命周期正常是取决于是否挂载在 DOM 树上,当从 DOM 树上移除时,也就可以被销毁回收了
//但如果某个 DOM 元素,在 js 中也持有它的引用时,那么它的生命周期就由 js 和是否在 DOM 树上两者决定了
//记得移除时,两个地方都需要去清理才能正常回收它。
var elements = {
    button: document.getElementById('button'),
};
function doStuff() {
    button.click();
}
function removeButton() {
    document.body.removeChild(document.getElementById('button')); 
    // 这时,我们仍然有一个引用指向全局中的elements。button这个节点仍在内存中,不会被回收。
}
//4.*********************************************
//【闭包的循环引用】
function my(name) {
    function sayName() {
        console.log(name)
    }
    return sayName
}
//在函数my()内部创建的sayName()函数是不会被回收机制回收
//如果闭包不被调用,由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。过度使用闭包可能会导致内存占用过多。
var sayHi= my("tom")
sayHi() //tom
//5.*********************************************
//【网络回调】
//某些场景中,在某个页面发起网络请求,并注册一个回调,且回调函数内持有该页面某些内容。
//那么,当该页面销毁时,应该注销网络的回调,否则,因为网络持有页面部分内容,也会导致页面部分内容无法被回收。

垃圾回收

  • 垃圾回收机制,清除孤儿 语言当中一般分两种,一种是自动清理,一种是手动清理(GC),js中只有自动清理
  • 垃圾回收机制就是将引用对中的地址的对象设置为null,并且将所有引用该地址的对象都设置为null,并且移除事件侦听
  • 不会即时清除,垃圾回收车会根据内存的情况在适当的时候进行清除堆中的对象 内存到达一定程度了才会进行回收
var obj={
        a:1,
        b:2
    };
var obj1=obj;
obj=null;
obj1=null;
    // 必须将所有引用的对象全部设为null 堆里面才会变成孤儿,要不然无法回收