🤔日拱一卒:死循环、无限递归会导致什么后果 ?

554 阅读5分钟

死循环

死循环会导致主线程长期被占用,主线程用于解析html,渲染css,执行js

如果有死循环js长期占用主线程,就会导致解析html及渲染css无法执行,这些涉及到事件循环的一些知识

会出现的现象是无法点击按钮,因为交互事件也需要排队到主线程上去执行

死循环会导致内存溢出吗?

死循环不会导致内存溢出 变量和对象会占用内存 变量在栈上 对象存在堆上

情况1:没有声明变量 不会导致内存溢出

while(1){
}

情况2:块级作用域 进入循环 生成变量 跳出循环 销毁变量 不会导致内存溢出

while(1){
    let a =1
}

情况3:全局作用域 下面这种情况只会占用一个变量 也不会导致内存溢出

var a
while(1){
     a =1
}

情况4:

function delay(duration){
    return new Promise((resolve)=>{
        setTimeout(resolve,duration)
    })
}


while(1){
    await delay(0)
}

上面这个情况浏览器在事件循环中会把setTimeout放到延时队列中,而浏览器的卡死属于是无法使用交互事件

在事件循环中 优先级以下 微任务队列>交互队列>延时队列

每一次循环都把settimeout加入到延时队列中,浏览器依然有间隙去执行我们的交互队列,也就不会导致浏览器无响应,大家可以复制到控制台自己试一下

情况5:


while(1){
    await 1
}

但如果是上面这种情况,await会把后面的常数识别成一个 promise.resolve(1) 按照事件循环的顺序,微任务队列是最优先的,在死循环的条件下就会一直优先执行微任务队列,而不会去执行交互队列,导致浏览器卡死

无限递归

function m(){
    m()
}
m()

无限递归会持续在调用栈中添加新任务,但调用栈有大小限制。当递归次数超过浏览器或运行环境的调用栈大小限制时,会抛出栈溢出错误

a()
b()
c()

像上面这种正常的情况 a执行完之后就会被销毁 b执行完之后也会被销毁 以此类推 不会占用调用栈的空间

情况一:

function delay(duration){
    return new Promise((resolve)=>{
        setTimeout(resolve,duration)
    })
}
function m(){
   await delay(0)
   m()
}
m()

上面这种情况不会导致栈溢出,如果不加await,栈会很快被撑爆,但是由于await的阻塞,需要等待延时队列的执行,使得栈的空间在执行完之后有时间被销毁

情况二:

function delay(duration){
    return new Promise((resolve)=>{
        setTimeout(resolve,duration)
    })
}
function m(){
   await 1
   m()
}
m()

但如果是这种情况 1会被视为resolve(1),从而await不会等待,也就没有时间销毁栈,会导致爆栈的情况。

总结

  • 死循环的后果

    • 浏览器无响应
      死循环会占用 JavaScript 主线程,使其无法处理其他任务(如页面刷新、用户交互等)。这会导致浏览器页面无响应。

    • 内存占用分析

      • 不会导致内存溢出
        死循环本身不会造成内存溢出,因为 JS 的内存占用主要与对象和变量有关,而死循环并不会频繁创建未销毁的对象或变量。

        • 如果在循环中创建了局部变量,它会在离开作用域时被销毁,不会累积占用内存。
        • 如果使用了 var 声明变量,变量会被提升到函数作用域或全局作用域,但死循环中该变量始终只有一个实例。
      • CPU 长时间占用
        死循环会使 CPU 持续忙碌,因为 JavaScript 是单线程语言,主线程会被死循环完全占用,导致线程阻塞。

  • 无限递归的后果

    • 调用栈溢出
      无限递归会持续在调用栈中添加新任务,但调用栈有大小限制。当递归次数超过浏览器或运行环境的调用栈大小限制时,会抛出栈溢出错误。
    • 浏览器优化
      一些运行环境(如某些浏览器)可能会对尾递归进行优化(即 "尾调用优化")。如果递归调用是函数的最后一条语句,有些引擎会优化调用栈,避免溢出。但并非所有环境都支持尾递归优化(例如,V8 引擎不支持)。
  • 事件循环中的表现

    • 用户交互事件
      死循环和无限递归都会阻塞主线程,导致事件循环无法处理用户交互事件(如点击按钮、滚动页面等)。虽然点击事件可能被捕获并存入事件队列,但事件处理程序不会被执行。
    • 异步操作
      如果死循环中存在异步操作(如 setTimeoutPromise),由于主线程被阻塞,异步任务的回调函数也无法被执行。

如果觉得有趣或有收获,请关注我的更新,给个喜欢和分享。您的支持是我写作的最大动力!

往期好文推荐