进程和线程的区别
经常说js是单线程的执行的,那什么是单线程呢 ?
首先我们来看看什么是进程。本质上来说,两个名词都是CPU工作时间的描述。
进程描述了CPU在运行指令及加载和保存上下文所需要的时间,放在应用来说就表示了一个程序,线程是进程中更小的单位,描述了执行一段指令需要的时间。
把这些概念拿到浏览器上来说,当我们打开一个tab页面时,其实就创建了一个进程,一个进程中有多个线程,比如渲染引擎线程、JS引擎线程、HTTP请求线程等。当我们发送一个请求,就是创建了一个线程,请求完成后,这个线程就会销毁。
上段中说的渲染引擎线程和JS引擎线程,在我们平时开发中,肯定也发现了,JS的运行时会阻碍UI渲染,这说明两个线程是互斥的。这其中的原因JS可以修改DOM,如果JS执行时,UI线程还在工作,就可能导致不安全的UI渲染。JS单线程的另一个好处就是可以节省内存,节约上下文切换的时间,没有锁的问题。
执行栈
什么是执行栈?
可以把执行栈认为一个存储执行函数的栈结构,遵循先进后出的规则。
function foo(a, b) {
return 2 * a + b;
}
function boo(a, b, c) {
return foo(a, b) + c
}
console.log(boo(1, 2, 3));
这段代码执行时,首先把console.log(boo(1, 2, 3))
放入执行栈中,然后继续把boo(1, 2, 3)
放入执行栈中,然后执行到boo函数内部,发现调用了foo函数,就把foo(a, b)
放入执行栈中,foo()函数内部无其他调用,执行返回值 4
, foo(a, b)
出栈。执行boo(1, 2, 3)
,返回值7
,然后其出栈。最后执行console.log
输出结果7,console.log
出栈,执行完成。
浏览器中的Event Loop
异步代码的执行顺序?
当遇到异步代码的时候,会被挂起并在需要执行的时候加入到Task(有多种Task)队列中,一旦执行队列为空,Event Loop就会从Task队列中拿需要执行的代码并发入到执行栈中执行。所以从本质上来说JS中的异步行为还是同步行为。
不同的任务源会被分配到不同的Task队列中,任务源可以分为微任务(microtask)和宏任务(macrotask)。在es6规范中,microtask称为job
,macrotask称为task
。
console.log('script start');
async function async1() {
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2 end');
}
async1();
setTimeout(() => {
console.log('timeout end')
}, 0);
new Promise(resolve => {
console.log('promise start');
resolve();
}).then(res => {
console.log('then1');
}).then(res => {
console.log('then2');
})
console.log('script end');
输出:
- script start
- async2 end
- promise start
- sctipt end
- async1 end
- then1
- then2
- timeout end
首先分析下async
和await
执行顺序,当我们调用async1
的时候,会马上输出async2 end
,并且async2
函数会返回一个Promise
对象,接下来遇到await
的时候,会让出线程执行函数async1
外的函数,所以我们可以把await
看作是让出线程的一个标示。
Event Loop执行顺序
- 首先执行同步代码,这属于宏任务。
- 执行完同步代码,执行栈为空,查询是否有异步代码需要执行。
- 执行所有的微任务
- 当执行完所有的宏任务,如果有必要会渲染也页面
- 然后开始下一轮的Event Loop,执行任务只的异步代码,也就是setTimeout中的回调函数
微任务包括:process.nextTick
、promise.then
、MutationObserver
。
宏任务包括:script
、setTimeout
、setInterval
、setImmediate
,I/O
,UI rendering
Event Loop 与 UI渲染关系
说说下面的代码,会看到几个颜色
// style 部分
<style>
.wrap {
width: 500px;
height: 500px;
background-color: bisque;
}
</style>
// html部分
<div class="wrap"></div>
// js部分
document.querySelector('.wrap').style.backgroundColor = 'red';
setTimeout(() => {
console.log('out setTimeout')
document.querySelector('.wrap').style.backgroundColor = 'yellow';
}, 10000)
new Promise((resolve, reject) => {
resolve(10000);
}).then(res => {
for(let i = 0; i < 10000; i++) {}
document.querySelector('.wrap').style.backgroundColor = 'pink';
setTimeout(() => {
document.querySelector('.wrap').style.backgroundColor = 'green';
}, 5000);
return '第一个'
}).then(res => {
document.querySelector('.wrap').style.backgroundColor = 'blue';
});
会展示的颜色有三个:blue、green、yellow
解析:ui渲染需要等所有微任务执行完成再执行,微任务执行完成是设置的颜色是blue
, 接下来就是两个宏任务,第一个setTimeout在10s后才注册回调,所以执行时间比第二个setTimeout完执行,所以先是green
再是yellow