JS为什么是单线程的?
浏览器js的作用是操作DOM,这决定了它只能是单线程的,不然会带来复杂的同步问题。假设js同时有两个线程,一个线程在某个dom上添加内容,另一个线程删除了这个dom节点,这时浏览器该以哪个线程为准?
任务队列
单线程就意味着所有的任务需要排队,但是I/O操作时cpu是闲着的。所以js就设计成了一门异步的语言。
任务分为两种:一种是同步任务(synchronous),另一种是异步任务(asynchronous)
- 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)
- 主线程外,还维护着一个**任务队列**。只要异步任务有了运行结果,就在任务队列之中放置一个事件
- 一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,看看里面有哪些事件。那些对应的异步任务就会结束等待状态,进入执行栈,开始执行
- 主线程不断重复上面的第三步
例如下列代码:
let fn = () => {
console.log('fn');
}
setTimeout(fn, 1000)
console.log('main');
//main
//fn
- setTimeout先进入主线程,然后异步fn函数被加入到I/O中
- setTimeout出栈,主线程继续执行,console进入主线程,打印`main`
- 1秒之后将fn加入到任务队列
- 任务队列检查主线程是否有任务在执行,发现主线程没有任务,将fn函数加入到主线程,打印`fn`
主线程从任务队列中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Lop(事件循环)
宏任务与微任务
除了广义的同步任务和异步任务,js单线程中的任务可以细分为宏任务(macrotask)和微任务(microtask)
- 宏任务:script(整体代码)、`setTimeout`、`setInterval`、`setImmediate`、`I/O`、`UI rendering`
- 微任务:`Promise`、`process`、`nextTick`、`Object`、`observe`、`MutationObserver`
- 宏任务进入主线程,执行过程中会收集微任务加入微任务队列
- 宏任务执行完成后,立马执行微任务中的任务。微任务执行过程中将再次收集宏任务,并加入到宏任务队列
- 反复执行1,2操作
例如下列代码
setTimeout(() => {
console.log('setTimeout');
})
Promise.resolve().then(() => {
console.log('promise');
})
console.log('main');
//main
//promise
//setTimeout
- 首先setTimeout进入宏任务,然后将`log('setTimeout')`放到I/O中,setTimeout出队
- 然后Promise中的`log('promise')`加入到微任务队列中
- `log('main')`加入到宏任务队列,打印`main`,`log('main')`出队
- 宏任务执行完毕,执行微任务,打印`promise`
- 微任务执行完毕,没有需要执行的新的宏任务。**此处就是事件循环的一环事件(每执行完一轮宏任务和一轮微任务)**
- 将`log('setTimeout')`加入到宏任务,打印`setTimeout`
最后
在我的博客网站上有更多我的一些学习成果
本文含有隐藏内容,请 开通VIP 后查看