JavaScript是一门单线程语言,原由是设计之初js只用来操做dom,对表单进行简单的校验。在这种执行环境简单的状况下,天然就选择了单线程来处理程序。可是单线程若是遇到执行时间较长的程序片断,会拖延甚至阻塞程序的执行,对于用户来讲,页面呈现"卡死状态",这是最糟糕的体验。node
为了解决上述问题,JavaScript将程序的执行分为同步和异步。面试
在JavaScript中写异步代码也叫作异步编程,进行异步编程的方式有:编程
回调函数promise
事件监听浏览器
发布订阅markdown
promise多线程
❝异步即未来, 异步任务就是未来执行的任务dom
❞
js引擎是单线程的,那么异步任务是如何维护的呢?异步
js引擎负责解析并编译js代码。制定做用域标准,分配内存,建立执行上下文调用栈...。编译好的代码放到运行环境中去运行,而运行环境会维护异步任务。async
对浏览器而言,浏览器是多线程的,它能够分配线程去倒计时定时器,发送请求,事件监听等。当定时器中的事件倒计时完毕,将其扔到也是由浏览器维护的消息队列中,当遇到其余异步时,浏览器会分配进程去处理,处理完毕也是扔到消息队列中。等待js引擎去执行。
以上所说的异步任务均是宏任务。
关于异步还有一些有趣的事情。
「有趣的异步控制台」
console也并不是js标准,没有具体的约束和规则去指定console的行为,console的行为是由运行环境决定的。
摘自你不知道的JavaScript(中卷) p141
❝不一样的浏览器和 JavaScript 环境能够按照本身的意愿来实现,有时候这会引发混淆。 尤为要提出的是,在某些条件下,某些浏览器的 console.log(..) 并不会把传入的内容立 即输出。出现这种状况的主要缘由是,在许多程序(不仅是 JavaScript)中,I/O 是很是低 速的阻塞部分。因此,(从页面 /UI 的角度来讲)浏览器在后台异步处理控制台 I/O 可以提 高性能,这时用户甚至可能根本意识不到其发生。
❞
console一般是进行程序调试写的比较多,若是产生了一些迷惑性行为,能够这样作
❝最好的选择是在 JavaScript 调试器中使用断点, 而不要依赖控制台输出。次优的方案是把对象序列化到一个字符串中,以强制执行一次“快照”,好比经过 JSON.stringify(..)。
❞
当检测到js主线程中的调用栈为空时(主线程会维护一个巨大的匿名函数,这个匿名函数用来执行js代码)。 浏览器早已提供好了用做事件触发的线程,事件触发线程从消息队列中按照队列排序取出一个任务放到执行栈中压栈执行。
执行过程当中遇到的问题:
若是遇到宏任务: 将其给浏览器进行处理,处理完毕放入消息队列中排队。
若是遇到微任务:将其放到微任务队列中,依次执行,不用去排队。也就是微任务是能够"插队"的。等到微任务队列中的全部微任务所有执行完毕。才会开启下一轮事件循环。
宏任务有: script
、setTimeout
、setInterval
、setImmediate
(浏览器暂时不支持,只有IE10支持)、I/O
、UI Rendering
。
微任务有: Process.nextTick(node)
、Promise
、MutationObserver
模拟执行一个宏任务
setTimeout(() => {
console.log('setTimeout');
}, 0);
复制代码
模拟执行一个微任务
queueMicrotask(() => {
console.log('queueMicrotask');
});
复制代码
上面两种方式不会建立额外的对象,不会形成浪费(好比经过promise
建立微任务.)
async function async1 () {
console.log('async1 start');
await async2();
console.log('async1 end')
}
async function async2 () {
console.log('async2')
}
console.log('script start');
setTimeout(function () {
console.log('setTimeout')
}, 0);
async1();
new Promise(function (resolve) {
console.log('promise1');
resolve()
}).then(function () {
console.log('promise2')
});
console.log('script end')
复制代码
js编译完成,进入浏览器执行环境开始执行。
遇到async1,async2函数分配内存
打印script start
遇到setTimeout,计时完成放到消息队列中,等待主线程空闲时执行。
调用async1函数,打印async1 start
遇到awiait,同步代码,执行后面的async2函数,打印async2
(async await是generator的语法糖。能够用generator来实现async await的效果。generator也是微任务队列) 将后面的片断放到微任务队列中。
建立Promise实例对象,打印promise1
, 将then函数扔到微任务队列中。
打印script end
主线程空闲, 事件触发线程拿到微任务队列中的第一个任务,放到主线程中的调用栈中执行,打印async1 end
而后执行微任务队列中的第二个微任务,打印promise2
,微任务队列清空,去宏任务队列中拿到第一个任务,放到主线程中执行,打印 setTimeout
❝在面试或者在写代码中,只要明白了这套规则,就了解了js的执行机制。对于一些执行机制以及执行顺序的问题也就迎刃而解了。
❞