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:EventsParsingCallbacksUsing a resourceReacting 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独立有一个队列。不过它的特点是:

  1. 在上一个task完成之后,下一个task之前执行,
  2. 并且当前栈必须为空。
  3. task一样,microtask是有顺序的,不会因为后面加入的优先级高而先执行。
  4. 最后,当这个microtask执行完了,当前task才算执行完成。

microtask的用途是什么?

其中一个是减少渲染。根据HTML Standard,在每个 task 运行完以后,UI 可能会重渲染,那么比如Vue等框架在 microtask 中就完成数据更新,当前 task 结束就可以得到最新的 UI 了。反之如果新建一个 task 来做数据更新,那么渲染就会进行两次。

代码里常用的microtasktask有哪些?

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就会用了,那些代码我就不照搬了。

参考