进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
顾名思义就是只有一个进程就是单线程,有超过1个进程就是多进程
线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。线程是不能单独存在的,它是由进程来启动和管理的。
一个进程可以包含一个线程的时候就是单线程,包含几个线程的时候就是多线程。
每个渲染进程都有一个主线程,并且主线程非常繁忙,既要处理 DOM,又要计算样式,还要处理布局,同时还需要处理 JavaScript 任务以及各种输入事件。要让这么多不同类型的任务在主线程中有条不紊地执行,这就需要消息队列和事件循环系统来统筹调度这些任务。
我们知道JS是单线程的,一般的代码都是按顺序执行的,也就是已知的安排好的任务都是按顺序执行的,等这个任务执行完成之后就会推出线程。
一般情况肯定没有我们想的那么好,全都是已知的任务,有可能中间插入其他任务需要执行,这个时候应该怎么办呢?
我们可以加一个循环,等待事件进入,然后再执行
刚刚一直都在讨论主线程上的任务执行,那如果有其他线程的任务发给主线程,这个时候主线程怎么处理呢?
消息队列是一种数据结构,可以存放要执行的任务。数据结构里的队列就是“先进先出”的特性,一般添加任务就是添加到队列尾部,然后从队列的头部取出任务来操作。
添加了一个消息队列后,其他的线程发送过来的事件就添加到消息队列的尾部,然后主线程会循环的从消息队列的头部取出任务再执行任务。
刚刚我们是处理其他线程发送给主线程的任务,现在是处理其他进程发送过来的任务,一字之差,其实步骤差不多的。整个渲染进程会有一个IO线程来接收其他进程发送过来的任务,然后再把接收到的其他进程任务添加到消息队列的尾部,渲染主线程就循环的取出任务,执行任务。
具体还是可以看MDN:requestAnimationFrame
具体还是可以看MDN:requestIdleCallback
1、整体的代码属于一个大的宏任务
2、遇到promise.then等微任务的时候会放进微任务队列尾部
3、遇到setTimeout,setInterval 等宏任务的时候会放入宏任务的队列尾部
4、最大的宏任务执行完后,查看有没有微任务,有的话执行微任务,没有的话执行下一个宏任务
5、执行setTimeout,setInterval 等宏任务的时候,如果里面有promise.then等代码的时候又要放入微任务队列尾部,执行完setTimeout,setInterval 后查看有没有要执行的微任务,没有的话执行下一个宏任务
6、 …
raf 在 render 中,requestIdleCallback 在 render 之后。raf在渲染之前执行的,requestIdleCallback 是在浏览器空闲时间才会执行,所以requestIdleCallback是最后执行的。
可以用这个代码体验一下执行顺序
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
<style>
#animation{
background-color: aqua;
width: 100px;
height: 100px;
border:1px solid rebeccapurple;
}
style>
head>
<body>
<div id='animation'>animationdiv>
<script>
window.requestIdleCallback(myNonEssentialWork);
const tasks = [
() => {
console.log("第一个任务");
},
() => {
console.log("第二个任务");
},
() => {
console.log("第三个任务");
},
];
function myNonEssentialWork (deadline) {
// 如果帧内有富余的时间,或者超时
while ((deadline.timeRemaining() > 0 || deadline.didTimeout) && tasks.length > 0) {
work();
}
if (tasks.length > 0) window.requestIdleCallback(myNonEssentialWork);
}
function work () {
tasks.shift()();
// console.log('执行任务');
}
var start = null;
var element = document.getElementById('animation');
element.style.position = 'absolute';
function step(timestamp) {
console.log('222')
if (!start) start = timestamp;
var progress = timestamp - start;
element.style.left = Math.min(progress / 10, 200) + 'px';
// if (progress < 2) {
// window.requestAnimationFrame(step);
// }
}
window.requestAnimationFrame(step);
setTimeout(function() {
console.log('setTimeout');
})
new Promise(function(resolve) {
console.log('promise');
resolve();
}).then(function() {
console.log('then');
})
console.log('console');
script>
body>
html>
Chrome浏览器的执行顺序是
可以看到出现两种情况,requestAnimationFrame打印出来的222会在setTimeout之前或者之后,是因为raf是由系统来决定回调函数的执行时机的,会请求浏览器在下一次重新渲染之前执行回调函数,这个下一次重新渲染的时机我们不能固定,所以打印出来的顺序是不固定的。
setTimeout(function() {
console.log('setTimeout1');
})
new Promise(function(resolve) {
console.log('promise');
resolve();
}).then(function() {
console.log('then');
})
setTimeout(function() {
console.log('setTimeout2');
})
async function aaa() {
console.log('async1')
return 'async2'
}
async function test () {
let a = await aaa();
console.log('await',a)
}
test()
console.log('console');
这个代码打印出来的顺序你知道吗?
1、这一整块代码就是一个宏任务,先一行一行代码看,看到一个setTimeout,先把它放到宏任务队列的尾部标记为setTimeout1
2、然后遇到new Promise 这个时候就是立刻执行了,所以先输出“promise”
3、然后遇到了then,把它放入微任务队列的队尾
4、再往下,又遇到了setTimeout,再把它放到宏任务队列的尾部标记为setTimeout2,这个时候宏队列是这样的:
【setTimeout2】【setTimeout1】
5、再往下遇到两个函数,目前没有调用所以不用管
6、遇到了test的函数调用,就直接执行test内部,遇到await也是直接执行aaa(),所以输出“async1”,await这行后面的代码都是放入微任务队列尾部,这个时候微任务队列长这样:
【await async2】【then】
7、遇到最后一行代码了,这个时候直接输出“console”,至此最大的宏任务执行完毕。
8、这个时候查看有没有微任务,我们看到微任务队列【await async2】【then】,然后从队头取出微任务,所以依次输出then,await async2
9、微任务队列为空后,说明执行完毕,然后查看有没有下一个宏任务
10、我们从宏任务队列的头部取出“setTimeout1”,输出“setTimeout1”,这个宏任务里面并没有微任务,所以这个setTimeout1宏任务结束
11、开始新的setTimeout2宏任务,**输出“setTimeout2”**后,这个宏任务里面并没有微任务,所以这个setTimeout2宏任务结束。
12、至此没有宏任务也没有微任务了,所以就结束了。依次输出的结果是
看看你有没有掌握,下面几个输出的结果是什么呢?
PS:process.nextTick要在node环境才能运行出来
console.log('1');
process.nextTick(function() {
console.log('6');
})
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
console.log('8')
})
setTimeout(function() {
console.log('2');
process.nextTick(function() {
console.log('3');
})
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5')
})
})
(async () => {
console.log('1');
await new Promise((resolve, reject) => {
console.log('2');
setTimeout(() => {
console.log('3')
}, 0)
console.log('4')
})
console.log('5')
})();
欢迎大家关注我的公众号: 石马上coding,一起成长
参考:
1、李兵老师的浏览器工作原理与实践(这是一个付费专栏,以下链接不属于这个专栏)
2、https://juejin.im/post/6844903512845860872
3、https://www.jianshu.com/p/2771cb695c81
4、https://html.spec.whatwg.org/multipage/webappapis.html#event-loops
5、https://javascript.info/event-loop