js是运行于浏览器的脚本语言,因其经常涉及操作dom,所以设置为单线程操作。如果是多线程的,也就意味着,同一个时刻,能够执行多个任务。试想,如果一个线程修改dom,另一个线程删除dom,那么浏览器就不知道该先执行哪个操作。所以js执行的时候会按照一个任务一个任务来执行。
试想一下,如果js的任务都是同步的,那么遇到定时器、网络请求等这类型需要延时执行的任务会发生什么?
页面可能会瘫痪,需要暂停下来等待这些需要很长时间才能执行完毕的代码
所以,又引入了异步任务。
浏览器本身是⼀个复杂的系统,它要做的事情⾮常多,例如: 执⾏js代码,请求图⽚资源,解 析css,渲染⻚⾯,响应⿏标的点击等等。在实现层⾯,浏览器内部会⽤不同的功能模块去完成不同的 事情。这些不同的模块就体现为进程。
进⼀步把进程进⾏划分:
1.主进程。⽤来协调控制其他⼦进程。2.GPU进程。⽤于3D绘制等。3.渲染进程。就是我们说的浏览器内核,负责具体⻚⾯的渲染,脚本执⾏,事件处理等。每个tab⻚背 后就有⼀个渲染进程。进程这个单位还是⽐较⼤,它进⼀步拆分多个线程。可以理解为⼀个⻚⾯上的事还是⽐较多,要多找些 ⼩弟来完成。具体来说,⼀个渲染进程包括:
1.主线程。统⼀调度2.GUI渲染线程。负责渲染⻚⾯,布局和绘制。与JS引擎互斥。3.JS引擎线程。负责处理解析和执⾏javascript脚本程序。4.事件触发线程。⽤来控制事件循环(⿏标点击、setTimeout、ajax等)。当事件满⾜触发条件时, 将事件放⼊到JS引擎所在的执⾏队列中。5.setInterval与setTimeout所在的线程。定时任务并不是由JS引擎计时的,是由定时触发线程来计时 的。计时完毕后,通知事件触发线程6.异步http请求线程。浏览器有⼀个单独的线程⽤于处理AJAX请求,当请求完成时,若有回调函数, 通知事件触发线程。7.io线程。⽤来接收其他进程的消息。每个渲染进程都有⼀个主线程,并且主线程⾮常繁忙,既要处理 DOM,⼜要计算样式,还要处理布 局,同时还需要处理 JavaScript 任务以及各种输⼊事件。要让这么多不同类型的任务在主线程中有条不 紊地执⾏,这就需要⼀个系统来统筹调度这些任务,这个统筹调度系统就是消息队列和事件循环。
任务有很多,⼈只有⼀个,且任意时刻只能做⼀件事(不是⼀边⾛路⼀边听课这种事哈),那怎么办, 就是排队呗
这张图非常生动形象,大家可以收藏
1.主线程上要做很多事情,例如:js代码执⾏,⻚⾯布局计算,渲染等2.主线程同⼀时刻只能做⼀件事,事情多了就要排队。所以主线程维护了任务队列。3.某个事件发⽣时,事件触发线程 就把对应的任务添加到主线程的任务队列中。4.主线程上的任务完成之后,就会从任务队列中取出任务来执⾏。任务是以事件及其回调的⽅式存在的。当事件(⽤户的点击,图⽚的成功加载)发⽣时,将其回调添加 到任务队列;主线程上的任务完成之后,就会从任务队列中取出任务来执⾏,此过程不断重复从⽽形成 ⼀个循环,称为eventLoop。
要点回顾:
● 事件循环不是js的语⾔层⾯的内容,是js的宿主环境(浏览器,nodeJS)的讨论内容。在js代码中 讨论事件循环是没有意义的。
● 在更⼴的领域。事件循环是⼀个典型的⽣产者/消费者模型。异步I/O,⽹络请求是事件的⽣产者,源 源不断提供事件,这些事件被传递到对应的观察者那⾥,事件循环则从观察者那⾥取出事件并处 理。
● 在windows下,这个循环基于IOCP创建,⽽在linux下基于多线程创建。
异步任务,⼜可以细分为宏任务和微任务。下⾯列举⽬前学过的宏任务和微任务。
console.log('1')
new Promise((resolve, reject) => {
resolve('2')
}).then((res) => {
console.log(res)
})
setTimeout(() => {
console.log('3')
})
new Promise((resolve, reject) => {
resolve('4')
}).then((res) => {
console.log(res)
})
console.log('5')
效果:
console.log(1)
setTimeout(function() {
console.log(2)
}, 0)
const p = new Promise((resolve, reject) => {
resolve(1000)
})
p.then(data => {
console.log(data)
})
console.log(3)
效果:
console.log(1)
setTimeout(function() {
console.log(2)
new Promise(function(resolve) {
console.log(3)
resolve()
}).then(function() {
console.log(4)
})
})
new Promise(function(resolve) {
console.log(5)
resolve()
}).then(function() {
console.log(6)
})
setTimeout(function() {
console.log(7)
new Promise(function(resolve) {
console.log(8)
resolve()
}).then(function() {
console.log(9)
})
})
console.log(10)
效果:
console.log(1)
setTimeout(function() {
console.log(2)
}, 0)
const p = new Promise((resolve, reject) => {
console.log(3)
resolve(1000) // 标记为成功
console.log(4)
})
p.then(data => {
console.log(data)
})
console.log(5)
效果:
new Promise((resolve, reject) => {
resolve(1)
new Promise((resolve, reject) => {
resolve(2)
}).then(data => {
console.log(data)
})
}).then(data => {
console.log(data)
})
console.log(3)
效果:
setTimeout(() => {
console.log(1)
}, 0)
new Promise((resolve, reject) => {
console.log(2)
resolve('p1')
new Promise((resolve, reject) => {
console.log(3)
setTimeout(() => {
resolve('setTimeout2')
console.log(4)
}, 0)
resolve('p2')
}).then(data => {
console.log(data)
})
setTimeout(() => {
resolve('setTimeout1')
console.log(5)
}, 0)
}).then(data => {
console.log(data)
})
console.log(6)
效果:
事件循环比较简单,它是一个在 “JavaScript 引擎等待任务”,"执行任务"和"进入休眠状态等待更多任务"这几个状态之间转换的无限循环。