当控制台遇上异步数据
同步和异步
首先我们来回顾一下JS的同步和异步的执行逻辑
JS利用事件循环规定了同步和异步的执行逻辑
执行栈和事件队列
执行栈:当主线程在执行过程中遇到同步代码,会按照顺序添加到执行栈。
function a(){ b(); console.log('a'); } function b(){ console.log('b'); } a()上面的实例代码中,执行栈的变化如下
- 首先要执行函数a,所以函数a入栈
- 函数a中首先要执行函数b,所以函数b入栈
- 执行函数b,console.log('b')入栈
- console.log('b')执行完毕,出栈
- 函数b执行完成,出栈
- console.log('a')入栈
- console.log('a')执行完,出栈
- 函数a出栈
事件队列:当主线程在执行过程中遇到异步代码,主线程不会等待异步代码执行,而是会将异步代码挂起,当异步代码(如,网络请求)得到结果之后,会将它的回调放入事件队列,等待主线程清空执行栈之后,主线程会去事件队列中查找是否存在任务,如果存在,那么主线程将从事件队列队头取出任务,将它的回调放入执行栈,主线程会开始执行它的同步代码
注意实际上,不同的异步任务将被放入不同的异步任务队列(宏任务队列和微任务队列)
function a(){ b(); console.log('a'); } function b(){ console.log('b'); setTimeout(function() { console.log('c'); }, 2000) } a()宏任务和微任务
- 什么是宏任务和微任务
- 异步任务被分为2种:宏任务和微任务,实际上它们俩都是异步任务。
- 为什么要把异步任务划分为2种
- 因为异步任务实际上是存在优先级的,如果所有异步任务都是一种,那么主线程将按顺序从队头取出任务放到执行栈执行。
- 有哪些宏任务
- script(整体代码)
- setTimeout()
- setInterval()
- postMessage
- I/O
- UI交互事件
- 有哪些微任务
- Promise().then中的回调
- MutationObserver
一个(浏览器)事件循环的过程(执行一个宏任务,并清空所有微任务)
第一步,主线程去宏任务队列中取出一个宏任务放入执行栈,直到清空宏任务队列
(在这个过程中,如果主线程遇到异步代码,那么会等待它们得到结果后按照分类放入不同的异步队列)
第二步,当这个宏任务执行完毕,出栈,主线程会去查看微任务队列是否存在任务,如果存在微任务,那么主线程将清空微任务队列
当想使用console.log打印异步数据
一个例子
let obj = { a: 1, } console.log(obj) obj.a++在上面的例子中,我们直觉的认为,打印出的obj中的a是1,但是实际上
导致问题的原因
- 上面例子的执行逻辑
- 当主线程执行到console.log(obj),浏览器会立即使用JSON.stringify,将对象序列化为一个字符串来展示
- 主线程会继续执行,当执行到obj.a++,所以这时对象obj中的a属性的值变为2
- 当我们去点开obj这个打印内容,这时浏览器就会去对象obj的地址读取当前a属性的值,所以这时就变成了2