进程和线程
相信大家经常会听到 JS 是单线程执行的,但是你是否疑惑过什么是线程?
讲到线程,那么肯定也得说一下进程。本质上来说,两个名词都是 CPU 工作时间片的一个描述。
进程描述了 CPU 在运行指令及加载和保存上下文所需的时间,放在应用上来说就代表了一个程序。线程是进程中的更小单位,描述了执行一段指令所需的时间。
(进程相当于一个程序,线程是更小的单位,描述了执行一段指令所需要的时间)
把这些概念拿到浏览器中来说,当你打开一个 Tab 页时,其实就是创建了一个进程,一个进程中可以有多个线程,比如渲染线程、JS 引擎线程、HTTP 请求线程等等。当你发起一个请求时,其实就是创建了一个线程,当请求结束后,该线程可能就会被销毁。
(好处是:单线程无需考虑 你在读取数据的时候其他线程对该数据的修改所造成的错误)
单线程
作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
所以,为了避免复杂性,从一诞生,JavaScript就是单线程
js的事件循环
由于js是单线程的,只有当上一个任务完成之后才会继续完成下一个任务,如果前一个任务耗时很长,后一个任务就不得不一直等着。于是,所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。
同步任务
在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;
异步任务
不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
macro-task(宏任务)和micro-task(微任务)
任务还可以分为宏任务和微任务
宏任务
macrotask 可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行,每一个宏任务会从头到尾将这个任务执行完毕,不会执行其它)包括整体代码script,setTimeout,setInterval
微任务
可以理解是在当前 task 执行结束后立即执行的任务 包括Promise.then()的回调内容,process.nextTick(nodejs)
setTimeout(function() {
console.log('1');
})
new Promise(function(resolve) {
console.log('2');
}).then(function() {
console.log('3');
})
console.log('4');
//打印顺序 2 4 3 1
首先整体代码是一个宏任务,遇到setTimeout,会创建另一个宏任务,接着执行当前的宏任务,Promise 新建后就会立即执行。所以会首先打印2,then方法是一个微任务,遇到then,添加到微任务队列,代码接着执行会打印4。此时宏任务执行完毕,接着就会检查当前微任务队列是否有微任务,如果有,立即执行当前的微任务(也就是then 打印3),当前微任务执行完毕之后,开始执行下一轮的宏任务setTimeout,会打印1。
再来看一下:
console.log('script start')
async function async1() {
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2 end')
}
async1()
setTimeout(function() {
console.log('setTimeout')
}, 0)
new Promise(resolve => {
console.log('Promise')
resolve()
})
.then(function() {
console.log('promise1')
})
.then(function() {
console.log('promise2')
})
console.log('script end')
// script start => async2 end => Promise => script end => async1 end => promise1 => promise2 => setTimeout
首先会打印出 script start
,然后遇到async函数(promsie的语法糖,内容先执行,then放入队列),执行async2 end
,遇到setTimeout
再创建一个宏任务,遇到Promise
执行Promise
then的内容放入事件队列,然后执行script end
,这时当前的同步任务已经执行完,去执行事件队列中的回调,这时打印出async1 end
这是第一个假如task队列的回调,然后执行promise1,promise2
这是Promise的回调,这时当前的宏任务里面所有的同步任务和异步任务都已经执行完成,那么下面会去执行下个宏任务,这时打印的是setTimeout
,按照上面的代码结构,这时所有的任务都已经执行完成了。