JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊,打个比方,我一边往body节点添加子节点,另一边又要删掉body这个节点,这时候浏览器该怎么办?所以就变成了不可控了,这就是为什么是单线程的原因。

首先来看下一段代码:

const p = new Promise((reject, resolve)=>{ console.log(1); const p1 = new Promise((reject1, resolve1)=>{ console.log(2); setTimeout(()=>{ console.log(3); }) resolve1(4); }) p1.then(res=>{ console.log(res) }) resolve(5) }) p.then(res=>{ console.log(res); }) console.log(6);

上面的最终执行的最终结果是: 1 2 6 4 5 3

下面我就将我的理解写下:

开头也说了,js是单线程的,单线程就意味着每次只能执行一个任务,如果一下子要执行很多任务怎么办,所以就有队列任务这个东西,js设计者又把任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)为了解释这段话,我举个栗子吧:

学生在食堂打饭,一个窗口,一次只能打一个人的饭,只有上一个人打完饭菜之后,才能轮到下一个人打饭,队伍有序的进行着(同步),但是,如果到某个人打饭的时候,他还不知道今天要吃什么饭菜,而这时又到他打饭了,他就会在窗口那里看要吃什么饭菜,这时候整个队伍肯定要停下来,等他打完饭菜能才能继续下一个。食堂阿姨肯定会说(食堂阿姨: 我没有说),你现在边上看看要吃什么(异步),看好了在来打饭,别挡住后面的同学打饭,遇到这种人阿姨都这样子处理的话,这时候整个队伍又恢复了正常。如果这时候那些在边上已经看好要打什么饭菜了,直接就跑到窗口说要打什么菜,但是阿姨正好在帮另外一个人打菜,这时候阿姨会怎么做?肯定不管他啊,继续帮后面的同学打菜,你就先在边上待着,等后面的同学全部打完了才帮你打,到这里,例子举完了,是不是感觉很有收获,嘻嘻。

在结合开头的代码,那些是同步代码,那些是异步代码呢?
同步的:

const p = new Promise((reject, resolve)=>{ console.log(1); const p1 = new Promise((reject1, resolve1)=>{ console.log(2); }) }) console.log(6); // 1 2 6 都是同步执行,按照官方说法应该叫主线程的任务

异步的

setTimeout(()=>{ console.log(3); }) p1.then(res=>{ console.log(res) //4 }) p.then(res=>{ console.log(res) //5 }) // 3 4 5 都是异步执行

看到这里想必又有点模糊了,如果看拆分的代码不是应该等于: 1 2 6 3 4 5吗? 但是结果为什么等于1 2 6 4 5 3呢?
这个又涉及到另外一个问题了。其实我们对任务有更精细的定义:
macro-task(宏任务):包括整体代码script,setTimeout,setInterval
micro-task(微任务):Promise,process.nextTick(nodejs的)
事件循环的顺序,进入整体代码(script宏任务)后,开始第一次循环。接着执行所有的微任务。然后再次从宏任务开始,找到其中一个任务队列执行完毕,再执行所有的微任务,这也解释了为什么先输出promise.then而非是setTimeout()因为第一个宏任务是整体代码(script);

最后在说下setTimeout()的坑,看下面的代码

setTimeout(()=>{ console.log("马上执行?"); }, 0)

上面的代码是否真的是0秒执行呢?
答案是不会的,setTimeout(()=>{},0)的含义是,指定某个任务在主线程最早可得的空闲时间执行,意思就是不用再等多少秒了,只要主线程同步任务全部执行完成时就马上执行。意思就是如果主线程一直执行下去,比如写了个死循环什么的,setTimeout(()=>{},0) 这个就一直不会执行。

补充,即便主线程为空,0毫秒实际上也是达不到的。根据HTML的标准,最低是4毫秒。有兴趣的同学可以自行了解。

以上是我对js运行机制的理解,不足之处欢迎大家在评论区指正,感谢!