死循环
死循环会导致主线程长期被占用,主线程用于解析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 引擎不支持)。
- 调用栈溢出:
-
事件循环中的表现
- 用户交互事件:
死循环和无限递归都会阻塞主线程,导致事件循环无法处理用户交互事件(如点击按钮、滚动页面等)。虽然点击事件可能被捕获并存入事件队列,但事件处理程序不会被执行。 - 异步操作:
如果死循环中存在异步操作(如setTimeout或Promise),由于主线程被阻塞,异步任务的回调函数也无法被执行。
- 用户交互事件: