js为什么是单线程的?
JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。
为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。
什么是 event-loop
event loop是一种运行机制,在Javascript中用来解决单线程疲于应对多任务的问题。event loop在不同的环境中有不同的实现。如Node、各浏览器。
[图片上传失败...(image-100eb7-1554909685850)]
什么是宏任务和微任务
task的定义
An event loop has one or more task queues. A task queue is an ordered list of tasks, which are algorithms that are responsible for such work as:Events
、Parsing
、Callbacks
、Using a resource
、Reacting to DOM manipulation
也就是说task
是浏览器(或者当前运行环境)去执行Js
、操作dom
的一个个任务。事件触发的时候,触发的回调就会被推入这个队列中。而event loop
会不断循环去获取当前task队列里的第一项拿到栈中执行。
我举个例子:我在页面上绑定了一个点击事件onClick
,并且绑定了一个回调事件,这就是一个简单的发布订阅。当我的鼠标点击,触发这个事件的时候,dispatch Click
这个task就会放到task queue
中,而onClick
回调就会被放到执行栈中执行。onClick
执行完了就完成了一次event loop
。浏览器(或当前运行环境)会不断去队列里取task
来执行。
microtask的定义
Each event loop has a microtask queue. A microtask is a task that is originally to be queued on the microtask queue rather than a task queue.
也就是说它也是一种task
,不过microtask
独立有一个队列。不过它的特点是:
- 在上一个
task
完成之后,下一个task
之前执行, - 并且当前栈必须为空。
- 和
task
一样,microtask
是有顺序的,不会因为后面加入的优先级高而先执行。 - 最后,当这个
microtask
执行完了,当前task
才算执行完成。
microtask的用途是什么?
其中一个是减少渲染。根据HTML Standard,在每个 task 运行完以后,UI 可能会重渲染,那么比如Vue等框架在 microtask
中就完成数据更新,当前 task
结束就可以得到最新的 UI 了。反之如果新建一个 task
来做数据更新,那么渲染就会进行两次。
代码里常用的microtask
和task
有哪些?
task
: setImmediate
> messageChanel
> setTimeout
microtask
: then
> MutationObserver
在Vue.nextTick
源码中有依次降级用到then、MutationObserver、setImediate、setTimeout
MessageChannel的用法
// ie10以上支持
// iframe中有用到
let channel = new MessageChannel();
let port1 = channel.port1;
let port2 = channel.port2;
console.log(port1)
console.log(port2)
// vue.nextTick 以前用这个方式实现的,废弃了
port1.onmessage = function(e){
console.log(e.data)
};
port2.postMessage('你好');
// vue使用nextTick历史:2.5之前用micro task,2.5用macro task,2.5之后用micro task
// vue 在2.5使用macro task(也就是MessageChannel)的原因是 micro task拥有过高的优先级,
// 导致了一些bug(#4521, #6690, #6566)。setImmediate被作者任务是完美的,但只有ie和node支持
// 2.5之后改回来micro task的原因macro task引发了一些无法绕过的bug #7109, #7153, #7546, #7834, #8109
MutationObserver的用法
// ie11以上支持
// mutationObserver是个微任务,提供了监视DOM树更改的能力
// vue 等待dom更新后 在取dom中的数据
setTimeout(()=>{
console.log('timeout');
})
let mutationObserver = new MutationObserver((...args)=>{
console.log(args)
console.log(app.children.length); // 异步的
})
mutationObserver.observe(app, {childList:true})
for(let i = 0 ; i< 10; i++){
app.appendChild(document.createElement('p'))
}
有缘人看到这篇文章可以看一下下面参考里的第一篇文章,通读两遍,做一下里面的几个练习题,有非常详细的思路讲解,再结合HTML规范,我觉得就可以弄清楚Event loop,micro task
就会用了,那些代码我就不照搬了。