异步执行原理
js是单线程的,浏览器是多线程的
单线程的js
js是单线程的,同一时间内只能干一件事情,比如发起一个http请求后,等待结果还是往下继续执行代码?定时器中定义的事件,js是等这个时间,还是继续执行settimeout()之后的代码的。js是单线程的,要么等要么执行等待操作的后续代码。答案都是继续执行后续操作,而不原地等待。 那js怎么知道哪些代码是直接执行,哪些是等待不直接执行呢?这里涉及到一个同步任务和异步任务,同步任务直接执行,异步任务交由其他人操作,自己不等待做别的。
同步
一个函数执行后,调用者立刻就能得到函数结果,那么这个函数/任务就是同步的
异步
一个函数执行后,调用者不能立刻获取函数结果,而是需要将来通过其他手段获得,那么这个函数/任务就是异步的
js为什么是单线程
因为js主要作用是通过在浏览器上和用户操作交互,以及操作DOM。
- 而浏览器ui渲染和js执行是互斥的,js可以操作DOM,如果js运行同时,UI也在渲染,那么会造成不安全的UI渲染。
- 且单线程节省内存,切换上下文成本小,(减少了线程之间的通信)
多线程的浏览器
浏览器是多进程多线程的。(进程和线程关系参考文章:。)其内部结构如下:
浏览器包含浏览器进程、浏览器在CPU上的进程、浏览器上添加的其他插件的进程等。最重要且和js最相关的是renderer内核进程。在这个进程中有5个线程,分别是js引擎线程、GUI渲染线程、事件触发线程、异步HTTP请求线程以及定时器触发线程。
这几个线程是协助js完成异步任务的基础。
浏览器的事件循环
js的任务分为同步任务和异步任务:
同步任务在主线程同步执行,执行完一个任务,再执行下一个
异步任务不在主线程上执行,而是放在任务队列,当主线程上的任务全部执行完毕,从任务队列中按顺序获取回调函数,在主线程上执行。
执行顺序如下:
浏览器有一个事件循环机制,当主线程执行完毕的时候会不停的检测任务队列中是否有新任务,有的话,会将新任务的回调放入主线程执行。
任务队列中的任务种类也有区分,总的来说分为两类:宏任务和微任务。
- 宏任务:定时器、script 整体代码、I/O操作等
- 微任务:promise等
任务队列中的任务根据种类的不同,执行优先级也不同,同一个宏任务下的微任务先执行,宏任务后执行
- JavaScript 引擎首先从宏任务队列中取出第一个任务;
- 执行完毕后,再将微任务中的所有任务取出,按照顺序分别全部执行(这里包括不仅指开始执行时队列里的微任务),如果在这一步过程中产生新的微任务,也需要执行,也就是说在执行微任务过程中产生的新的微任务并不会推迟到下一个循环中执行,而是在当前的循环中继续执行。
- 然后再从宏任务队列中取下一个,执行完毕后,再次将 microtask queue 中的全部取出,循环往复,直到两个 queue 中的任务都取完。
举例:
宏任务和微任务的本质区别:
- 宏任务是交给其他线程做的,比如网络请求交给异步HTTP线程,定时器交给触发定时器线程做,主线程执行他们的回调
- 微任务,不需要其他线程做,直接执行回调