导读
首先,要知道JavaScript是一门单线程的语言,要搞明白其为何会设计成单线程,要先来说一下JavaScript的最初应用场景。
其次,在单线程语言中如何处理多线程的任务。
再次,同步和异步的流程以及如何理解异步。
然后,在解决复杂的逻辑业务时,出现了什么样的问题。
再然后,怎样解决这一问题。
=> Promise/.then(),async/await,
最后谈谈微任务及宏任务
JavaScript最初的应用场景
最初,JavaScript的设计是作为浏览器脚本语言实现用户的交互,而为了避免多线程给我们带来很多不必要的麻烦,比如说:一个线程在删除一个节点,而另一个线程在修改这个节点,这时我们应该以哪一条线程为主呢?
因此,设计者将其设计成单线程的语言。
在H5中规定了JS可以拥有多个子线程,但是子线程仍然是依托于主线程的,且不能够操作节点。
为什么要使用多线程
-
单线程的缺点:
单线程有一个“致命”的缺点,就是会造成阻塞;
因为是单线程,所以在程序运行时是按照先进先出的原则来进行任务处理的,也正是因如此,在主线程遇到了耗时操作后,其后的任务就进入到了等待的状态。如果此时CPU是被占用的,也没什么,但是如果这个耗时任务是一个不占CPU的操作,举个栗子:向服务端请求数据。这个时候CPU出入空闲状态但是程序并没有执行结束,这就形成了阻塞。
-
启发:
在进行耗时操作且影响代码正常运行时,我们可以先不管这个操作,将其挂起。先处理后面的任务,等到主线程清空时,再来执行这个任务。于是出现了同步任务和异步任务。
同步和异步及程序运行流程
关于这一块内容,笔者建议从整体来看,将这一块中的内容结合起来读,可能会更好理解
-
我们可以将同步任务理解为在主线程中执行的任务,异步任务理解为在子线程执行。
-
同步任务和异步任务的执行过程:
-
同步任务正常运行,没有特殊情况会一直执行完毕;
-
当主线程运行到异步任务时,会安排一个子线程去运行异步任务,当异步任务运行结束后,向任务队列发送一个事件。表示该异步任务可以进入主线程执行了。
-
-
当不考虑下面讲的微任务和宏任务时,我们的程序运行时,会先执行同步任务,执行到异步任务后,将其发送到子线程中运行,运行结束会向任务队列发送一个事件。同步任务执行完毕即主线程清空后,主线程会向任务队列询问,是否有接收到事件,如果没有,那么主线程会一直询问,这个过程称为
event loop
;如果有,就执行这个事件。直到程序全部完成。
事件
在上文我们提到了事件这一概念,可以将事件理解为异步任务返回的一个回调函数,这个回调函数也就是事件会在主线程上运行。异步任务必须有回调函数。
拓展:异步函数必须return一个Promise对象
关于事件,阮一峰前辈是这么写的:
"任务队列"中的事件,除了IO设备的事件以外,还包括一些用户产生的事件(比如鼠标点击、页面滚动等等)。只要指定过回调函数,这些事件发生时就会进入"任务队列",等待主线程读取。
“回调地狱”
在实际开发中,为了实现一些逻辑需求,可能会用到多层回调函数嵌套。这个时候会导致代码的可读性很差。人们称之为“回调地狱”
Promise对象
-
代表的是异步操作 最终完成或失败
-
目的:将回调函数的多层嵌套形式,拆解成链式调用的形式。
-
本质:函数返回的对象,在这个对象上绑定回调函数,避免从一开始将回调函数作为参数传入上一层函数。
-
一个Promise必然会处于这几种状态:
-
待定(pending):初始状态,既没有被兑现,也没有被拒绝
-
已兑现(fulfilled):意味着操作成功完成 resolve
-
已拒绝(rejected):意味着操作失败 reject
-
-
Promise的使用:一般作为函数的返回值
const fn = function(){ return new Promise((resolve,reject)=>{ if(ture){ resolve(a); }else{ reject(b); } }) } fn() .then((res)=>{有返回值的函数}) .then((res)=>{有返回值的函数}) .then((res)=>{有返回值的函数}) ... .then(res=>{最后的函数}) // 如果Promise对象中的请求完成了,那么将resolve的值传给then中的回调函数作为参数执行then方法 // 可以附加 .catch()在链式结构的末尾,来捕获错误(reject传回的值),并且之后的then不会执行 // 可以在最后加一个 .finally() 来执行清理操作 并且这个方法不管请求成功与否都会执行
async/await
是Promise的语法糖;让繁琐的then(),和冗长的链式调用可读性变得更长些;
具体使用方式:
//异步函数1 function getData(data){ return new Promise((reslove)=>{ reslove(data) }) } //异步函数2 function sayHello(data){ return new Promise((reslove)=>{ reslove(data) }) } //异步函数 async function fn(){ // await相当于.then() getData()相当于是回调函数 // await必须在async修饰的函数体内使用 const promiseA = await getData('info') const promiseB = await sayHello(promiseA); console.log(promiseB) }
谈谈微任务和宏任务
微任务
-
注意:Promise对象中的代码是同步的,then()方法中的回调函数才是异步的
-
then()中的是微任务
宏任务
-
定时器是宏任务
代码执行顺序口诀:先同步后异步,先微任务后宏任务
总之,同微宏