站在前人肩膀上能够看得更远,也不必定,至少比你本身看得远。javascript
1、带着问题去想html
问题:JS为何是单线程? 为何须要异步?单线程又是如何实现异步的?java
第一个问题:javaScript最初被设计用在浏览器中,思考一下,若是浏览器中的JS是多线程,会什么样?node
虚拟场景:git
现有2个进程,process1 & process2 ,因为是多进程的JS,它们对同一个dom同时进行操做,es6
process1 要删除该dom,process2编辑了该dom元素,同时执行2个命令,试想,浏览器究竟要如何执行?github
结论:JS为何被设计成单线程有木有理解了呢?web
第二个问题: JS为何须要异步?ajax
虚拟场景:promise
试想,若是JS不存在异步,只能自上而下执行,若是上一行解析时间很长,下面要执行的代码就会被阻塞。
对于用户而言,阻塞意味着卡死,浏览器无响应,等等。。 致使用户体验不好,估计研发要和(产品、UI设计、视觉设计等一票人)干一仗了。
结论:证实JS中是必定要存在异步执行操做。
第三个问题:试想单线程要如何实现异步操做?
结论:经过事件循环(Event Loop)理解event loop机制,一样就理解了JS的执行机制
2、概念
再讲以前,咱们先熟悉了解一下,3个概念?
事件循环:将任务栈中的事件放到执行栈中
事件循环过程被称为"tick"; (每次Tick会查看任务队列中是否有须要执行的任务)
每次Tick的过程就是查看是否有待处理的事件,若是有则取出相关事件及回调函数放 入执行栈中由主线程执行。
任务队列(事件队列):存储异步操做添加的相关事件回调
异步操做会将相关回调添加到任务队列中。不一样的异步操做添加到任务队列的时机也不一样。
异步操做是由浏览器内核的 webcore 来执行的,webcore 包含3种 webAPI,分别是 DOM Binding、network、timer模块
{
1.setTimeout 会由浏览器内核的 timer 模块来进行延时处理,当时间到达的时候,才会将回调函数添加到任务队列中。
2.onclick 由浏览器内核的 DOM Binding 模块来处理,当事件触发的时候,回调函数会当即添加到任务队列中。
3.ajax(XMLHttpRequest) 则会由浏览器内核的 network 模块来处理,在网络请求完成返回以后,才将回调添加到任务队列中。
}
主线程:
1. 主线程中的执行栈放的同步代码,先执行
2. 主线程惟一(只有一个主线程)主线程将执行栈里的代码执行完毕。事件循环才开始执行
3. 主线程中执行栈为空,才会进行事件循环来观察要执行的事件回调。当事件循环检测到任务队列中有事件就取出相关回调放入执行栈中由主线程执行
此图来源网络,再次引用做为学习用途,感谢原图做者绘图!
具体只一个任务队列来讲明:
ok
3、Event Loop 运行机制
先看一个例子:输出的循序是什么?
console.log(1);
setTimeout(() => {
console.log(2)
}, 1000)
console.log(3);
分析:setTimeout中的函数并无当即执行,二是延迟了一段时间,知足必定条件后,才执行,一般咱们所谓的异步操做。
注:setTimeout函数,一般理解为1秒后,执行里面的函数,这种理解并非很准确,
而是,1秒后,setTimeout中函数会推入到event queue,而事件队列中的任务,只有在 主线程空闲时才会执行。
只有同时知足:1秒后且主线程空闲时,才会真的在1秒后执行该函数
实际场景,会遇到,超过1秒后,才执行该函数(由于主线程执行的内容不少,致使真正的延迟时间远远大于1秒)
特殊状况:setTimeout(fn,0)
只要主线程执行栈内的同步任务所有执行完成,栈为空就立刻执行;
即使主线程为空,0毫秒实际上也是达不到的。根据HTML的标准,最低是4毫秒
故此,知道JS中将任务分为同步任务和异步任务。
看图:
JS执行的机制是:
1.先判断JS是同步任务仍是异步任务,同步任务进入主进程,异步任务进入event table
2.异步任务在event table中注册函数,当知足条件后,被推入event queue
3.同步任务进入主线程后一直执行,当主线程空闲时,才会去event queue中查看是否有可执行的异步任务,若是有就推入主线程中(执行栈)
上述步骤循环执行,即event loop
注:event table就是注册站,调用栈让event table注册一个函数,并移到event queue。
event queue:理解为缓冲区域便可,事件队列中函数等着被调用并移到调用栈(执行栈)。
JS引擎会有一个monitoring process 持续不断地检查调用栈是否为空
回过头,分析上述例子:
console.log(1); // 同步任务,放入主线程
setTimeout() 是异步任务,被放入event table, 0秒以后被推入event queue里
console.log(3 是同步任务,放到主线程里
当 1、 3在控制条被打印后,主线程去event queue(事件队列)里查看是否有可执行的函数,执行setTimeout里的函数
继续往下看:
setTimeout(function(){
console.log('定时器开始啦')
});
new Promise(function(resolve){
console.log('立刻执行for循环啦');
for(var i = 0; i < 10; i++){
i == 9 && resolve();
}
}).then(function(){
console.log('执行then函数啦')
});
console.log('代码执行结束');
按照上述JS执行机制分析,最终输出结果
1.setTimeout 是异步任务,被放到event table
2.new Promise 是同步任务,被放到主进程里,直接执行打印 console.log('立刻执行for循环啦')
3..then里的函数是 异步任务,被放到event table
4.console.log('代码执行结束')是同步代码,被放到主进程里,直接执行
结论:立刻执行for循环啦 — 代码执行结束 — 定时器开始啦 — 执行then函数啦(对么)
按照异步和同步的划分方式,并不许确
划分方式:任务队列分两种类型(宏任务、微任务)
注:ES6 的 promise[ECMAScript标准]产生的任务队列为microtask queue
按照此分类,JS执行的机制是:
此时,再次分析上述例子:
结论:立刻执行for循环啦 — 代码执行结束 — 执行then函数啦 — 定时器开始啦
最后,
总结:
永远记得js就是单线程执行
(扩展:h5中 webworker可实现多线程)主要解决计算,并不能操做dom
具体可参考:http://www.ruanyifeng.com/blog/2018/07/web-worker.html
1.了解事件循环的机制,了解了任务队列、js主线程、异步操做之间的相互协做;
2.了解两种任务队列:macrotask queue & microtask queue
microtask queue对应ECMAScript的promise属性(es6)DOM3的MutationObserver
3.经过JS的事件循环机制,能够清楚js代码的执行流,更好的控制代码
不够完善、不够详细、后续不断调整补充
看到一句总结关于事件队列的优先级的问题!
在JS中ES6 中新增的任务队列(promise)是在事件循环之上的,事件循环每次 tick 后会查看 ES6 的任务队列中是否有任务要执行,也就是 ES6 的任务队列比事件循环中的任务(事件)队列优先级更高。而 Promise 就使用了 ES6 的任务队列特性,也即在执行完任务栈后首先执行的是任务队列中的promise任务。其余异步操做加入队列的时间是没有相应优先级一说。
参考资料: