JavaScript深入之异步
我们先来看一段代码
Promise.resolve()
.then(function () {
console.log("promise1");
})
.then(function () {
console.log("promise2");
});
console.log("3");
setTimeout(function () {
console.log("setTimeout");
}, 0);
console.log("5");
结果是
这是为什么呢?接下来我们往下面去看。
1.单线程与多线程
单线程语言:JavaScript 的设计就是为了处理浏览器网页的交互(DOM操作的处理、UI动画等),决定了它是一门单线程语言。因为如果有多个线程它们一起操作DOM那网页将会非常混乱。
console.log("1");
console.log("2");
console.log("3");
那如果一个任务的处理耗时(或者是等待)很久的话,如:网络请求、定时器、等待鼠标点击等,后面的任务也就会被阻塞,也就是说会阻塞所有的用户交互(按钮、滚动条等),会带来极不友好的体验。
console.log("1");
console.log("2");
setTimeout(()=>{
console.log("hunian")
},1000)
console.log("3");
console.log("4");
“hunian”在4后打印的 其实,JavaScript 单线程指的是浏览器中负责解释和执行 JavaScript 代码的只有一个线程,即为JS引擎线程,但是浏览器的渲染进程是提供多个线程的。 JS引擎线程 事件触发线程 定时触发器线程 异步http请求线程 GUI渲染线程 当遇到计时器、DOM事件监听或者是网络请求的任务时,JS引擎会将它们直接交给 webapi,也就是浏览器提供的相应线程(如定时器线程为setTimeout计时、异步http请求线程处理网络请求)去处理,而JS引擎线程继续后面的其他任务,这样便实现了 异步非阻塞。所以,所以一切javascript版的"多线程"都是用单线程模拟出来的。 我们总结一下这张导图
1.同步和异步任务分别进入不同的执行场所,同步的进入主线程,异步的进入Event Table并注册函数。 2.当指定的事情完成时,Event Table会将这个函数移入Event Queue。 3.主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。 4.上述过程会不断重复,也就是常说的Event Loop(事件循环)。
2.同步与异步
上面说到了异步,JavaScript 中有同步代码与异步代码。 下面是同步例子
console.log("1");
console.log("2");
console.log("3");
console.log("4");
setTimeout(()=>{
console.log("0");
},1000)
console.log("1");
上面的 setTimeout 函数便不会立刻返回结果,而是发起了一个异步,setTimeout 便是异步的发起函数或者是注册函数,() => {…} 便是异步的回调函数。这里,JS引擎线程只会关心异步的发起函数是谁、回调函数是什么?并将异步交给 webapi 去处理,然后继续执行其他任务。 异步函数一般是网络请求,定时器,DOM时间监听。 我们再通过一个例子来看看这个执行过程
console.log("1");
setTimeout(() => {
console.log("2");
}, 1000);
console.log("3");
console.log("4");
1.主代码块(script)依次加入执行栈,依次执行,主代码块为: console.log(‘1’) setTimeout() console.log(‘3’) console.log(‘4’) 2.console.log() 为同步代码,JS引擎线程处理,打印 “1”,出栈; 3.遇到异步函数 setTimeout,交给定时器触发线程(异步触发函数为:setTimeout,回调函数为:() => { … }),JS引擎线程继续,出栈; 4.console.log() 为同步代码,JS引擎线程处理,打印 “3”,出栈; 5.console.log() 为同步代码,JS引擎线程处理,打印 “4”,出栈; 6.如果执行栈为空,也就是JS引擎线程空闲,这时从消息队列中取出(如果有的话)一条任务(callback)加入执行栈,并执行;这里就是输出“2” 7.重复第6步。 可以看出,setTimeout异步函数对应的回调函数( () => {} )会在执行栈为空,主代码块执行完了后才会执行。
3.宏任务与微任务
以上机制在ES5的情况下够用了,但是ES6会有一些问题。 Promise同样是用来处理异步的: 我们再来看最初的代码
Promise.resolve()
.then(function () {
console.log("promise1");
})
.then(function () {
console.log("promise2");
});
console.log("3");
setTimeout(function () {
console.log("setTimeout");
}, 0);
console.log("5");
这里有一个新概念:宏任务 和 微任务。 宏任务:setTimeout、setInterva,Ajax和DOM等(可以看到,事件队列中的每一个事件都是一个 宏任务,现在称之为宏任务队列) 微任务:Promise、async和await等 同步任务毫无疑问先执行所以先打印出“3”,“5”,按照代码段顺序会将异步任务放到任务队列中,并且秉持先微再宏的原则,入主线程执行。则微任务promise1和promise2打印出来。然后再打印宏任务setTimeout。 手画下执行过程:
|