异步模式
首先Javascript是单线程模式,即多任务需要排队执行。容易阻塞照成页面假死。为了解决这个问题所以有了异步。
单线程:指浏览器中执行avascript代码的线程只有一个。但浏览器的渲染进程(GUI)提供了多个线程。如JS引擎线程,事件触发线程,定时触发器线程,异步Http请求线程
js引擎线程向下执行期间,如(setTimeout放入定时器出发线程,Http请求放入异步Http请求线程 ...),使得后面的代码继续执行,如此便实现了非阻塞。
异步模式:通过回调函数(callback)事件监听 发布/订阅 Promises对象等方式实现。其本质都是通过回调函数来实现异步代码的存放与执行。每一个异步都存在一个或则多个回调函数,这里异步执行完成之后马上出发回调函数,而下一个任务也不会等待回掉函数执行完成再执行。
1.回调
一个函数执行完毕后,得到想要的特定数据后在去执行的函数。
异步回调
function fnA(callback){
setTimeout(()=>{
callback('张三')
},3000)
}
function fnB(name){
console.log(name)
}
function fnC(){
console.log('来了老弟')
}
fnA(fnB)
fnC()
执行结果:先来了老弟,后张三
同步回调
function fnA(callback) {
for (let i = 0; i < 999999999; i++) {
if(i===999999991){
callback('张三')
}
}
}
function fnB(name) {
console.log(name)
}
function fnC() {
console.log('来了老弟')
}
fnA(fnB)
fnC()
执行结果:先张三后来了老弟。
回调与同步或者异步并无太大关系,因为我们既可以同步回调,也可以异步回调。
2.事件监听
采用事件驱动模式。任务的执行不取决代码的顺序,而取决于某一个事件是否发生。监听函数有:on,bind,listen,addEventListener,observe
3.观察者模式(发布/订阅)
4.promise对象(promise 模式)
promise存在三种状态,等待(pendding),失败(fulfilled),成功(rejected),且状态变化不可逆。当状态非pendding时,通过successCallback成功回调或者failCallback失败回调返回对应的结果。
EventLoop 事件环和消息队列
EventLoop是一种循环机制 ,不断去轮询一些队列 ,从中找到 需要执行的任务并按顺序执行的一个执行模型。
消息队列是用来存放宏任务的队列, 比如定时器时间到了, 定时间内传入的方法引用会存到该队列, ajax回调之后的执行方法也会存到该队列。
function fnA(){
//操作
console.log('fnA')
}
fnA()
setTimeout(()=>{
//操作
console.log('定时器')
},0)
//某ajax请求操作
function fnB(){
for(let i=0;i<999999999;i++){
if(i===999999991){
console.log('fnB')
}
}
}
fnB()
以上面代码为列,执行结果为 fnA,fnB,定时器
这里js引擎栈会维持一个执行栈(call stack),js同步代码压入执行栈中生成匿名函数anonymous。同步代码执行完成之后,函数依次出栈。直至执行栈清空。
当js引擎遇到异步代码会交给相应的线程(web apis)去执行,直到(如:计时器时间到回调,电点击事件出发回调,或者网络请求完成回调等等),然后由事件循环(event loop)将这些回调依次放入消息队列(queue)。
这里事件循环(event loop)负责监听执行栈与消息队列,当执行栈为空时,从消息队列中依次取出异步回调放入执行栈。
当执行栈(call stack)即js引擎线程空置的时候,事件触发线程按照先入先出的原则,将消息队列中的异步回调放入执行栈(call stack)中执行。
所以看似fnB执行耗时长于settimeout,但是fnB任然先执行完成。
宏任务和微任务
宏任务可以理解为每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)。
浏览器为了让 JS 内部宏任务 与 DOM 操作能够有序的执行,会在一个宏任务执行结束后,在下一个宏任务执行开始前,对页面进行重新渲染。
宏任务包含:script(整体代码)、setTimeout、setInterval、I/O、UI交互事件、MessageChannel 等
微任务可以理解是在当前任务执行结束后需要立即执行的任务。也就是说,在当前任务后,在渲染之前,执行清空微任务。
所以它的响应速度相比宏任务会更快,因为无需等待 UI 渲染。
微任务包含:Promise.then、MutaionObserver、process.nextTick(Node.js 环境)等
这里同等级别下微任务优先于宏任务。