在阅读本文前先给大家抛出几个问题:
- 事件循环是什么?
- 宏任务和微任务是什么?
- 哪些代码触发宏任务,哪些代码触发微任务?
前言
JS是一门单线程语言,单线程就意味着,所有的任务需要排队,前一个任务结束,才会执行下一个任务。
这样所导致的问题是:如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉。为了解决这个问题,JS中出现了同步和异步。
他们的本质区别是:一条流水线上各个流程的执行顺序不同。
Event Loop
- Event Loop 实际就是 JavaScript 异步执行机制的一种实现方式;
- 程序按照主线程-微任务-宏任务的顺序不断重复执行, 并始终维护各执行队列直至全部队列清空的操作就是 Event Loop;
宏任务&微任务
JS 中的任务分为同步与异步
同步任务:即主线程上的任务,按照顺序由上⾄下依次执⾏,当前⼀个任务执⾏完毕后,才能执⾏下⼀个任务。
异步任务:不进⼊主线程,⽽是进⼊任务队列的任务,执行完毕之后会产生一个回调函数,并且通知主线程。当主线程上的任务执行完后,就会调取最早通知自己的回调函数,使其进入主线程中执行。
其中异步任务又分为两种:
- 宏任务(Macro-take)
- 微任务(Micro-take)

在JavaScript中,微任务的优先级高于宏任务。也就是说,当主线程空闲时,如果有微任务存在,它们会被优先执行,直到微任务队列为空。然后事件循环才会从宏任务队列中取出下一个任务执行。
总之,宏任务和微任务是用于描述任务队列中的不同类型任务的概念,了解它们的原理和执行顺序可以帮助我们更好地理解JavaScript代码的执行过程,并优化代码的性能。
执行顺序
同步任务 > 微任务 > 宏任务
注意:
Promise
本身与new Promise()
并不是微任务,而是同步任务
首先遇到 new Promise() 会同步执行代码,立即执行参数传入的 executor 函数,executor 函数内部从上到下同步执行代码,遇到 resolve() 或 reject() 时,将值传递给 then() 参数中的回调,并立即将该回调注册到微任务队列。(⚠️注意:resolve() / reject() 的执行也是同步的,它不会等异步完成后执行,除非它在异步回调内,参照示例代码理解)
对于 then() 方法的链式调用而言:尽管调用在形式上是连续的,但是注册回调并不是连续的,下一个 then() 回调注册需要等待上一个 then() 方法执行完毕后才进行注册。
遇到 await,将后续代码放入微任务队列等待执行,交还执行权(跳出该函数体)
任务归属
宏任务:
- 整体 script 代码(script 代码是异步代码, 其内部可能包含同步代码, 但整体上是异步宏任务)
- setTimeout
- setInterval
- setImmediate(Node.js)
微任务:
- 原生 Promise 的 then() , catch() 方法
- await 暂停处的后续语句,将后续代码放入微任务队列等待执行,交还执行权(跳出该函数体)
- MutationObserver(H5)
- process.nextTick(Node.js)
EventLoop流转示意图

Event table
是一种数据结构,其中存储了一些延迟事件的列表。
1 2 3
| setTimeout(function() { console.log(“This will print after 1 second”); }, 1000);
|
以上代码事件发生后(即 1 秒结束后),事件表会将事件发送到事件队列。在上述情况下,1000ms 后,事件表会将要执行的函数发送到EventQueue。
Event Queue
是一种使用 FIFO(先进先出)策略存储事件列表的数据结构。
示例代码分析
Demo1
1 2 3 4 5 6 7 8 9 10 11 12 13
| console.log('demo: script start');
setTimeout( () => { console.log('demo: setTimeout'); }, 0);
Promise.resolve().then( () => { console.log('demo: promise1'); }).then( () => { console.log('demo: promise2'); });
console.log('demo: script end');
|
Demo2
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| setTimeout(()=>{ console.log('demo: setTimeout1'); }, 0);
let p = new Promise<void>( (resolve, reject) => { console.log('demo: Promise1'); resolve(); })
p.then( ()=> { console.log('demo: Promise2'); })
console.log('demo: Promise3');
|
Demo3
1 2 3 4 5 6 7 8 9 10 11 12 13
| Promise.resolve().then( ()=>{ console.log('demo: Promise1') setTimeout(()=>{ console.log('demo: setTimeout2') },0) });
setTimeout( ()=>{ console.log('demo: setTimeout1') Promise.resolve().then( ()=>{ console.log('demo: Promise2') }) },0)
|
测试
测试1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| console.log('test: 1');
setTimeout( () => { console.log('test: 2'); let async1 = async () => { console.log('test: 3'); } async1() new Promise<void>( (resolve) => { console.log('test: 4'); resolve(); }).then( () => { console.log('test: 5') }) })
let async2 = async () => { console.log('test: 6'); }
async2()
new Promise<void>( (resolve) => { console.log('test: 7'); resolve(); }).then( () => { console.log('test: 8') })
setTimeout( () => { console.log('test: 9'); let async3 = async () => { console.log('test: 10'); } async3() new Promise<void>( (resolve) => { console.log('test: 11'); resolve(); }).then( () => { console.log('test: 12') }) })
|
以上代码输出什么呢?
测试2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| let async1 = async () => { console.log('test: async1 1'); await async2(); console.log('test: async1 2'); }
let async2 = (() => { console.log('test: async2 3'); })
console.log('test: script start 4 '); setTimeout(() => { console.log('test: setTimeout 5 '); }) async1(); 正确: 4 1 3 6 9 2 7 8 5
岳帅: 4 1 3 2 6 9 7 8 5
:: 4 1 3 6 9 2 7 8 1 5 new Promise<void>( (resolve) => { console.log('test: promise 6'); resolve(); }).then(() => { console.log('test: promise 7'); return new Promise<void>( (resolve) => { resolve(); }) }).then((res) => { console.log('test: promise 8'); })
console.log('test: script end 9');
|
以上代码输出什么呢?
宏任务、微任务与线程
JS的同步任务,异步任务,跟线程没有什么关系, 哪个线程执行的代码,就在哪个线程返回执行, 宏任务也一样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| test3() { console.log(`demo: 开始执行异步方法 执行时当前线程: ${process.tid},当前进程:${process.pid}`);
taskpool.execute(asyncRunAction)
console.log(`demo: 结束执行异步方法 执行时当前线程: ${process.tid},当前进程:${process.pid}`); }
@Concurrent export async function asyncRunAction(): Promise<void> { console.log(`demo: 1 执行时当前线程: ${process.tid},当前进程:${process.pid}`); return await new Promise<void>( (resolve, reject) => { setTimeout(()=>{ console.log(`demo: setTimeout1 执行时当前线程: ${process.tid},当前进程:${process.pid}`); }, 0);
setInterval(()=> { console.log(`demo: setInterval 执行时当前线程: ${process.tid},当前进程:${process.pid}`); }, 1000)
let p = new Promise<void>( (resolve, reject) => { console.log(`demo: Promise1 执行时当前线程: ${process.tid},当前进程:${process.pid}`); resolve(); })
p.then( ()=> { console.log(`demo: Promise2 执行时当前线程: ${process.tid},当前进程:${process.pid}`); })
console.log(`demo: Promise3 执行时当前线程: ${process.tid},当前进程:${process.pid}`); resolve(); })
} demo: 开始执行异步方法 执行时当前线程: 6233,当前进程:6233 demo: 结束执行异步方法 执行时当前线程: 6233,当前进程:6233 demo: 1 执行时当前线程: 6259,当前进程:6233 demo: Promise1 执行时当前线程: 6259,当前进程:6233 demo: Promise3 执行时当前线程: 6259,当前进程:6233 demo: Promise2 执行时当前线程: 6259,当前进程:6233 demo: setTimeout1 执行时当前线程: 6259,当前进程:6233 demo: setInterval 执行时当前线程: 6259,当前进程:6233 demo: setInterval 执行时当前线程: 6259,当前进程:6233 demo: setInterval 执行时当前线程: 6259,当前进程:6233 demo: setInterval 执行时当前线程: 6259,当前进程:6233 demo: setInterval 执行时当前线程: 6259,当前进程:6233 ...
|
宏任务与微任务是怎么实现的?
看到上面会不会以为宏任务和微任务 是JS 引擎实现的 ? 其实并不是。
我们双端完整的写过动态化框架,所以知道其实不是这样的。
微任务由JS代码进行调度实现
1 2 3
| 微任务: 1.原生 Promise 的 then() , catch() 方法 2.await 暂停处的后续语句
|
宏任务的实现和调度由外部注入实现
1 2 3
| 宏任务: 1.setTimeout 2.setInterval
|
看看 H5的 Window API,就能发现我们熟知的 setTimeout
、setInterval
、console对象
https://www.runoob.com/jsref/obj-window.html


所以鸿蒙的语言框架也注入并实现了 Window的这些常规函数,而不是 JS本身就带的能力。
动态化当时写的给 JS引擎注入的函数的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
| func addGlobalCall() { let onKTRequestJSCallEventBlock : @convention(block) (String) -> Void = { [weak self] (jsonString:String) -> Void in self?.eventQueue.async { if let jsCallNative = self?.jsCallNative { jsCallNative(jsonString) } } } let onKTResponseJSCallEventBlock : @convention(block) (String) -> Void = { [weak self] (jsonString:String) -> Void in
self?.eventQueue.async { if let jsResponseNative = self?.jsResponseNative { jsResponseNative(jsonString) } } } let setTimeout : @convention(block) (JSValue?, NSNumber?) -> String = { [weak self] callback, milliseconds -> String in guard let self = self else { return "" } var delay: Double = 0 if let milliseconds = milliseconds { delay = milliseconds.doubleValue * 0.001 } var timeId = UUID().uuidString DispatchQueue.main.async { let timer = Timer.scheduledTimer(withTimeInterval: delay, repeats: false) { [weak self] (kTimer) in guard let self = self else { kTimer.invalidate() self?.timerStore.removeValue(forKey: timeId) return }
kTimer.invalidate() self.timerStore.removeValue(forKey: timeId) self.eventQueue.async { callback?.call(withArguments: []) } } self.timerStore[timeId] = timer } return timeId } let clearTimeout : @convention(block) (JSValue?) -> Void = { [weak self] timerId -> Void in guard let self = self else { return } var delay: Double = 0 if timerId?.isString ?? false, let timerId = timerId?.toString() as? String { DispatchQueue.main.async { if let timer = self.timerStore[timerId] { timer.invalidate() self.timerStore.removeValue(forKey: timerId) } } } } let setInterval : @convention(block) (JSValue?, NSNumber?) -> String = { [weak self] callback, milliseconds -> String in guard let self = self else { return "" } var delay: Double = 0 if let milliseconds = milliseconds { delay = milliseconds.doubleValue * 0.001 } var timeId = UUID().uuidString DispatchQueue.main.async { let timer = Timer.scheduledTimer(withTimeInterval: delay, repeats: true) { [weak self] (kTimer) in guard let self = self else { kTimer.invalidate() self?.timerStore.removeValue(forKey: timeId) return } self.eventQueue.async { callback?.call(withArguments: []) } } self.timerStore[timeId] = timer } return timeId } let clearInterval : @convention(block) (JSValue?) -> Void = { [weak self] timerId -> Void in guard let self = self else { return } var delay: Double = 0 if timerId?.isString ?? false, let timerId = timerId?.toString() as? String { DispatchQueue.main.async { if let timer = self.timerStore[timerId] { timer.invalidate() self.timerStore.removeValue(forKey: timerId) } } } } let btoa : @convention(block) (String?) -> String = { [weak self] resString -> String in guard let self = self else { return ""} var encodeString = "" if let resString = resString { encodeString = resString.kt.encodingBase64() ?? "" } return encodeString } let atob : @convention(block) (String?) -> String = { [weak self] resString -> String in guard let self = self else { return ""} var decodeString = "" if let resString = resString { decodeString = resString.kt.decodeBase64() ?? "" } return decodeString } let global = JSValue(newObjectIn: self)! global.setValue(setTimeout, forProperty: "setTimeout") global.setValue(clearTimeout, forProperty: "clearTimeout") global.setValue(setInterval, forProperty: "setInterval") global.setValue(clearInterval, forProperty: "clearInterval") global.setValue(btoa, forProperty: "btoa") global.setValue(atob, forProperty: "atob") global.setValue(onKTRequestJSCallEventBlock, forProperty: onKTRequestJSCallEvent) global.setValue(onKTResponseJSCallEventBlock, forProperty: onKTResponseJSCallEvent) globalObject.setValue(global, forProperty: "global") let onRequestJSCallEventBlock : @convention(block) (String) -> Void = { [weak self] (jsonString:String) -> Void in self?.eventQueue.async { if let jsCallNative = self?.jsCallNative { jsCallNative(jsonString) } } } let onResponseJSCallEventBlock : @convention(block) (String) -> Void = { [weak self] (jsonString:String) -> Void in
self?.eventQueue.async { if let jsResponseNative = self?.jsResponseNative { jsResponseNative(jsonString) } } } self.setObject(onRequestJSCallEventBlock, forKeyedSubscript: onKTRequestJSCallEvent as NSString) self.setObject(onResponseJSCallEventBlock, forKeyedSubscript: onKTResponseJSCallEvent as NSString) }
|
引用
JavaScript 中的 Event Loop 事件循环、微任务与宏任务
搞清事件循环、宏任务、微任务
JS 基础篇 - 宏任务与微任务 & EventLoop事件循环