浏览器:事件循环
- 功能检测,根据每个浏览器独有的特性来进行判断,如 ie 下独有的 ActiveXObject。
浏览器:事件循环
- Event Loop
Js引擎在执行代码时候会产生执行栈,当调用异步 API,例如 setTimeout,setInterval,Promise 等回调触发时,就会进入异步任务队列,当同步代码执行完成后,就会去异步队列取出回调函数并执行,这样就形成
了一个事件循环。在 Javascript 中有两种任务类型,分别是宏任务和微任务。
事件循环唯一任务:将队列和调用堆栈连接起来。
事件队列是一个存储执行任务的队列。
执行栈类似于函数调用栈的运行容器,当其为空时,JS检查事件队列,将第一个任务压入栈中执行。若调用栈和微任务队列为空,事件循环检查宏任务队列是否还有任务,从中弹出进入调用栈执行完再弹出。
setTimeout的时间不是指马上执行,而是最快可以多久后执行,因为它会等待调用栈为空时执行。
浏览器的渲染必须要调用栈为空时才会执行,正常的 forEach 会阻塞渲染。
在一个回调出栈,另一个回调进栈的间隙(此时栈空),渲染得以顺利进行。
单线程JS实现异步
浏览器的内核多线程实现。
script是宏任务
若存在两个script代码块,首先执行第一个 script 中的同步代码,若这个过程中创建了微任务并进入了微任务队列,第一个 script 同步代码执行完之后,首先清空微任务队列,再去开启第二个 script 的执行。
js单线程问题
所有任务都在一个线程上完成,一旦遇到大量任务或遇到一个耗时的任务,网页就可能出现卡死,也无法响应用户的行为。
setTimeout如果在主线程上运行就会阻塞其他活动。
所以,需要做的就是,离开这个线程,同时运行这个任务。
JavaScript的单线程,与它的用途有关。
作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。
比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。
Event Loop
是一个程序结构,用户等待和发送信息的事件。
microtask在事件循环的macrotask执行完之后执行。
macro(宏任务)有多个,微任务(micro)队列只有一个。
先执行宏任务,再执行微任务,执行宏任务的过程中遇到微任务,依次加入微任务队列。
异步实现
- 宏观:浏览器多线程(从宏观来看是多线程实现了异步)
- 微观:Event Loop,事件循环(Event Loop 翻译是事件循环,是实现异步的一种机制)
常见的macrotask有:(一般由浏览器发起)DOM渲染后触发
- script整体代码
- setImmediate:node 的方法
- setTimeout 和 setInterval
- requestAnimationFrame
- I/O
- UI rendering
常见的microtask有:(一般由JS自身创建)DOM渲染前触发
- process.nextTick (Node环境中)
- Promise callback(例如 promise.then)
- Object.observe (基本上已经废弃)
- MutationObserver
运行过程
线程都有自己的运行数据存储空间,堆的空间比较大,所以存储一些对象;
栈的空间比较小,所以存储一些基础数据类型、对象的引用,函数的调用;
函数调用就入栈,执行完函数体的代码自动从栈中弹出——调用栈。
当栈中的函数出栈时,栈为空的话,我们会调用一些异步函数,这个异步函数会找它们的异步处理模块,异步处理模块包括定时器、promise、Ajax等,异步处理模块会找它们各自对应的线程,线程向任务队列中添加事件,再从
任务队列中取出事件,去执行对应的回调。
3个注意点:
- 整个script代码块属于宏任务
- 当宏任务执行完,会去执行所有微任务
- 微任务执行完再去执行下一个宏任务,等调用栈为空时执行一个微任务;调用栈不为空时,任务队列的微任务
一直等待;微任务执行完又去取任务队列的宏任务,依次执行宏任务,执行宏任务时检查当前是否存在微任务,若有微任务就去执行完所有微任务,然后再去执行后续的宏任务。
注意点:
- 一个 Event Loop 有一个或多个 task queue(任务队列)
- 每个 Event Loop 有一个 microtask queue(微任务队列)
微任务(一个 promise),再执行下一个宏任务(setTimeout)
记住一点,要把本次宏任务下所产生的微任务全部执行完才会执行下一个宏任务,记住是产生的,没有产生的不会执行!
//async函数是对一些异步操作的处理方式,一旦调用会立即执行,其中可包含微任务和宏任务 // await语句后面的代码回放进微任务队列执行
async function async1() {
console.log('async1 start')
//await是等待,需要把第一轮微任务执行完,再执行下面的内容
//await后面的内容执行完,又执行宏任务
await async2()
console.log('async1 end')
}
async function async2() {
return Promise.resolve().then(_ => {
console.log('async2 promise')
})
}
console.log('start')
setTimeout(function () {
console.log('setTimeout')
}, 0)
async1()
new Promise(function (resolve) {
console.log('promise1')
resolve()
}).then(function () {
console.log('promise2')
//async1 start
//promise1
//async2 promise
//promise2
//async1 end
//setTimeout
所谓一轮事件循环就是第一轮宏任务和微任务结束。当微任务队列清空后,一个事件循环结束。
所以正确的一次 Event loop 顺序是:
- 执行同步代码,这属于宏任务
- 执行栈为空,查询是否有微任务需要执行
- 执行所有微任务
- 必要的话渲染 UI
- 然后开始下一轮 Event loop,执行宏任务中的异步代码
通过上述的 Event loop 顺序可知,如果宏任务中的异步代码有大量的计算并且需要操作 DOM 的话,为了更快的界面响应,我们可以把操作 DOM 放入微任务中。
- Node事件循环
microtask 在事件循环的各个阶段之间执行(一个阶段执行完毕,就会去执行microtask队列任务)。和浏览器不同,浏览器时每次取一个宏任务执行,执行完后就跑去检查微任务队列.
但nodejs是来都来了,一次全部执行完该阶段的任务好了,那么process.nextTic和微任务啥时候执行?
process.nextTick会优先于微任务.
事件循环顺序

执行的几个阶段
- timers 阶段:执行 timers 的回调,也是执行 setTimeout 和 setInterval 的回调
- pending IO callbacks:系统操作的回调,如定时器和setImmediate回调。
- idle,prepare:内部使用
- poll:等待新的 I/O 事件进来,其他所有宏任务都属于poll阶段,此阶段,系统会做两件事:
- 执行到点的定时器
- 执行 poll 队列中的事件
- 且当 poll 中没有定时器的情况下,会发生以下两件事
如果 poll 队列不为空,会遍历回调队列并同步执行,直到队列为空或者系统限制
如果 poll 队列为空,会有两件事发生
如果有 setImmediate 需要执行,poll 阶段会停止并且进入到 check 阶段执行 setImmediate 如果没有 setImmediate 需要执行,会等待回调被加入到队列中并立即执行回调
- 如果有别的定时器需要被执行,会回到 timer 阶段执行回调。
- check:执行 setImmediate 回调
全站分卷索引 · 154 卷
- 1代码随想录知识星球精华(最强八股文)第五版(前端篇))
- 2html语义化
- 3src 和 href 的区别
- 4link 与 @import 的区别
- 5iframe
- 6CSS
- 7伪元素和伪类
- 8block,inline,inline-block
- 9重排、重绘和合成
- 10盒子模型
- 11margin 合并
- 12margin和padding 的值为百分比时
- 13浮动
- 14定位
- 15CSS选择器
- 16CSS选择器优先级
- 17选择器权重Specificity
- 18CSS属性继承
- 19不可/可继承属性
- 20display: none与 visibility: hidden的区别
- 21background-size
- 22client、offset&scroll
- 23CSS3新特性
- 24flex布局
- 25常见布局
- 26三栏布局
- 27垂直居中
- 28水平垂直居中
- 29CSS画三角形
- 30使chrome支持12px以下文字
- 31响应式布局
- 32媒体查询
- 33过渡与动画
- 34JavaScript 动画 和 CSS动画
- 35SCSS
- 36CSS性能优化
- 37有哪些数据类型
- 38判断数据类型
- 39类型转换
- 40parseInt&parseFloat
- 41浮点数求和步骤
- 42十进制小数转二进制
- 430.1+0.2 为什么不等于 0.3
- 44undefined 和 undeclared 的区别
- 45JavaScript NaN 属性
- 46isFinite()&isNaN()
- 47数组
- 48对原数组有影响
- 49对原数组无影响
- 50数组去重
- 51数组拍平
- 52ES6新增的数组方法
- 53判断是否存在某个值
- 54arguments为啥不是数组
- 55对象
- 56遍历对象的方法
- 57判断对象是否具有属性
- 58for in 和 for of 的区别
- 59继承方式
- 60instanceof
- 61深浅拷贝
- 62freeze属性
- 63函数
- 64函数柯里化
- 65函数的 length
- 66箭头函数和普通函数区别
- 67构造函数
- 68执行上下文
- 69作用域
- 70作用域和作用域链
- 71闭包和内存泄漏
- 72this
- 73关于 this 的指向问题
- 74改变this指向的方法(bind / call / apply)
- 75字符串
- 76DOM和事件
- 77哪些事件支持冒泡
- 78阻止冒泡
- 79HTMLCollection、NodeList
- 80dom.onclick 和 dom.addEventListener的区别
- 81e.target和e.currentTarget
- 82页面生命周期
- 83异步
- 84内存管理GC
- 85其它
- 86JavaScript 中日期时间格式转换
- 87input和object实现双向绑定
- 88JSON.stringify()
- 89escape、encodeURI&encodeURIComponent
- 90移动端滚动穿透和溢出
- 91ES6
- 92Map字典
- 93... 与rest
- 94proxy
- 95模块化
- 96Ajax、Fetch和Axios
- 97Webpack
- 98浏览器:事件循环
- 99浏览器内核
- 100浏览器同源策略
- 101输入URL回车后发生了什么
- 102HTTP
- 103HTTP 2.0
- 104浏览器存储
- 105鉴权
- 106Webstorage
- 107浏览器执行机制
- 108AST 抽象语法树
- 109性能优化
- 110CSS
- 111网络相关
- 112使用HTTP2.0
- 113图片
- 114CDN图片
- 115其他文件优化
- 116降低APP首页开屏渲染时间
- 117页面白屏
- 118HTTPS中间人攻击
- 119Web Worker
- 120Service Worker
- 121微前端
- 122Vue
- 123v-show 和 v-if 的区别
- 124双向数据绑定的原理
- 125Vue中的插槽使用
- 126Vue中的key
- 127Vue过滤器
- 128Vue中Scoped原理
- 129Vue中$nextTick的使用
- 130Vue中实现组件通信的方式
- 131Vue-router
- 132Vue路由传参
- 133前端路由
- 134Vuex
- 135虚拟DOM
- 136DIFF 算法
- 137Vue3和Vue2有哪些区别
- 138说一说你对React的理解
- 139React事件机制
- 140说一说你对react refs的理解
- 141react中key的作用
- 142组件开发
- 143有状态组件和无状态组件
- 144函数式组件和类组件的区别
- 145PureComponent&Component
- 146常见 Hooks
- 147状态管理
- 148redux toolkit
- 149React18新特性
- 150client中的createRoot取代以前的ReactDOM.render
- 151Suspense
- 152新的Hooks
- 153VDom一定更快吗
- 154Fiber