面试题 JS 异步 ---- 进阶

JS 异步 — 进阶

  • 之前讲解 JS 异步,在于初阶的应用
  • 本章在于 JS 异步的原理和进阶
  • 对初学者有点难度,尽量深入浅出

思维导图

在这里插入图片描述

本章主要内容

  • event loop
  • promise 进阶
  • async / await
  • 微任务 / 宏任务

先看几个面试题

问答

请描述 event loop (事件循环 / 事件轮询)的机制,可画图

1在这里插入图片描述
2

在这里插入图片描述

3
微任务是ES6语法规定的
宏任务是有浏览器灰顶的
在这里插入图片描述
4

在这里插入图片描述

什么是宏任务和微任务,两者有是区别?

1.宏任务(MacroTask),微任务(MicroTask)
-1.两者都是异步
-2.同步和异步的区别:

(1)同步代码执行:你写的顺序是什么,他就按什么顺序执行,显而易见,同步任务是阻塞的,也就是说,只有前一句执行完成之后,才能进行下一句
(2)异步代码的执行:常见的异步就是callback,不是按照写的顺序执行的就是异步,但是异步并不等于非阻塞,callback被调用的时机也是通过执行到它才会被执行,也是一步步往下执行的,只不过不是按照写的程序从上到下的顺序而已,所以如果有些任务占用了整个线程,那么异步任务也会被阻塞,这也是由于JS都是单进程单线程的
(3)经典的JS都是单进程单线程的,单现代的JS已经突破了这一点,我们有方法新建额外地进程共同处理任务,NodeJS也可以在同一个进程中新建线程,浏览器也有要支持线程的计划
(4)异步除了回调,还有其他种类的,比如任务,任务是一种队列类型的,先注册的任务先被执行,任务里面,根据执行的时机分为宏任务和微任务,根据什么区分发执行时机,需要去看设计部分

-3.进程的切换肯定是宏任务,因为需要花费大量的资源
-4.线程的切换是微任务,因为只需要在同一个进程中切换就可以了
-5.更准确的说,很多微任务都是纤程的切换导致的,纤程是比线程更小的概念
-6.为什么和定时器有关的任务是宏任务?

(1)因为计时是实时的,它一定不能被阻塞,所以定时器被设计在另一个进程中被管理,所以,定时器任务会有进程的切换,所以只能是宏任务

-7.事件为什么是宏任务呢?

事件的触发是依赖于浏览器的实现,平台有它自己的事件注册和派发机制,比如nodeJS用的内核libuv,所以不管怎样,事件的独立注册表和派发机制导致,他也不会和JS存在一个进程中,事件的管理中心一定是在另外一个进程中实现的,那么去派发事件,也就是宏任务

-8.JS本身语法内的

(1)比如async/await,它本身是一个迭代器,迭代器利用了纤程,在不同的纤程之间切换,所以是微任务
(2)进程和纤(线)程最大的区别是:进程占用单独的资源,即不会共享CPU和内存,而线程往下,都是共享CPU和内存的,只是在一个空间中在进行拆分,做切换
(3)如果是JS本身的命令是进程的话,就没有办法拿到外面上下文的数据了,因为不共享内存
(4)JS本身的部分一定是比线程小的

-9.非JS本身的

非JS本身的,因为往往被设计成不被JS阻塞的,所以会单独开启一个进程作管理,这样出来的都是宏任务

-10.那么像Observer(如MutationObserver等),和一些渲染为什么都是微任务呢?

(1)虽然它们和JS的本身无关,但是它们的执行时机和它们所在的进程是有关的
(2)比如MutationObserver,观察的是DOM,它的作用即是对DOM的变化做出响应,所以,他会在管理DOM的进程中
(3)渲染也是一样的,是在整个渲染流程中的某一步作的回调,并没有切换出它的本身所在的空间

-11.微任务在执行时,它能获取到任务外的上下文
-12.宏任务在执行时,他不能获取到任务外的上下文
-13.为什么定时器任务是宏任务?

(1)可以把定时器和定时器任务是分离的,先把定时器想成一个时间管理中心
(2)然后在上面注册一个个任务,这些任务本身和时间无关
(3)时间管理中心和时间有关的,当时间管理中心发现时间到了,要执行任务,就从任务列表中找出注册的任务,并通知JS执行任务
(4)所以可以看到,时间管理中心(定时器的进程)和执行的任务(JS运行时)是无关的,不共享上下文,所以是宏任务
(5)控制渲染的函数如requestAnimationFrame,这个函数本身是要求在下一帧重绘前做什么,它的本身被设计成是在渲染的流程中,然后它做的事情应该也是和渲染有关的,也就是这个任务和它所处的空间的上下文一致

-14.微任务和宏任务执行的顺序

(1)因为微任务不需要执行上下文(这里指的是资源的切换),所以它可以在一次上下文切换间隔中把所有的微任务都做掉
(2)而宏任务因为需要切换上下文,所以他会慢点执行
(3)所以会先执行已注册的微任务,然后是宏任务
(4)这样,就相当于做了一次上下文的切换,如果在此期间,又有新的微任务或宏任务被注册了,就不断重复上面的这个流程

Promise 有哪三种状态? 如何变化?

Promise的三种状态
-pending - 进行中
-fulfilled - 成功
-rejected - 失败

Promise对象,可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数,但Promise对象的状态不受外界影响,Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败),只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态,这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。

状态的改变(或者说决议)不可逆,一旦决议就不能再更改。

任何时候都可以得到这个结果,Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected,只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型),如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果,这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

开始讲解知识点

event loop (事件循环 / 事件轮询)

  • JS 是单线程运行的
  • 异步呀基于回调来实现
  • event loop 就是异步回调的实现原理

JS 如何执行

  • 从前到后,一行一行执行
  • 如果某一行执行报错,则停止下面代码的执行
  • 先把同步代码执行完
    在这里插入图片描述

开始讲解 event loop 过程

  • 初学者可能会感觉难,尽量深入浅出
  • 第一遍讲解是,感觉不懂得不要停下,会重复讲三遍
  • 不要抠细节,不要扩大范围。核心是 event loop 的过程
    先看下图
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

总结 event loop 过程

  • 同步代码,一行一行放在 Call Stack 执行
  • 遇到异步,辉县“记录”下,等待时机(定时,网络请求等)
  • 时机到了,就移动到 Callback Queue
  • 如果 Call Stack 为空(即同步代码执行完)Event Loop
  • 轮询查找 Callback Queue ,如有则移动到 Call Stack 执行
  • 然后继续轮询查找(永动机一样)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

DOM 事件和 event loop

  • JS是单线程的
  • 异步( setTimeout , ajax 等)使用回调,基于 event loop
  • DOM 事件也使用回调,基于 event loop

在这里插入图片描述
在这里插入图片描述

Promise

  • 三种状态
  • 状态的表现和变化
  • then 和 catch 对状态的影响

 function loadImg(res){
    
    
        const p = new Promise(
            (resolve,reject) => {
    
    
                const img = document.createElement('img')
                img.noload = () =>{
    
    
                    resolve(img)
                }
                img.onerror = () => {
    
    
                    const err = new Errorr(`图片加载失败 ${
      
      src}`)
                    reject(err)
                }
                img.src = src
            }
        )
        return p
    }
    const url = "放图片地址"
    const p = loadImg(url)
    p.then(img => {
    
    
        console.log(img.width)
    }).them(img => {
    
    
        console.log(img.height)
    }).catch( ex =>{
    
    
        console.log(ex)
    })

    // callback hell 
    

三种状态

  • pending resolved rejected
  • pending ——> resolved 或 pending ——> rejected
  • 变化不可逆

代码演示:


<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>

</body>

</html>
<script>
    const p1 = new Promise((resolve, reject) => {
    
    

    })
    console.log('p1', p1)  // Pending

    const p2 = new Promise((resolve, reject) => {
    
    
        setTimeout(() => {
    
    
            resolve()
        })
    })
    console.log('p2', p2)  // Pending 一开始打印是
    setTimeout(() => console.log('p2-setTimeout',p2)) // resolve

    const p3 = new Promise((resolve, reject) => {
    
    
        setTimeout(() => {
    
    
            resolve()
        })
    })
    console.log('p3', p3) 
    setTimeout(() => console.log('p2-setTimeout',p2)) // resolve
</script>

状态的表现

  • pending 状态 ,不会触发 then 和 catch
  • resolve 状态,会触发后续的 then 回调函数
  • rejected 状态,会触发后续的 catch 回调函数

 function loadImg(res){
    
    
        const p = new Promise(
            (resolve,reject) => {
    
    
                const img = document.createElement('img')
                img.noload = () =>{
    
    
                    resolve(img) // resolve 
                }
                img.onerror = () => {
    
    
                    const err = new Errorr(`图片加载失败 ${
      
      src}`)
                    reject(err) //rejected 
                }
                img.src = src
            }
        )
        return p
    }
    const url = "放图片地址"
    const p = loadImg(url)
    p.then(img => {
    
    
        console.log(img.width)
    }).them(img => {
    
    
        console.log(img.height)
    }).catch( ex =>{
    
    
        console.log(ex)
    })

    // callback hell 
    

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>

</body>

</html>
<script>
    const p1 =  Promise.resolve(100) // resolved
    // console.log('p1',p1)
    p1.then(data => {
    
    
        console.log('data',data)
    }).catch(err => {
    
    
        console.log('err',err)
    })

    const p2 = Promise.reject('err') // rejetced
    // console.log('p2',p2)
    p1.then(data => {
    
    
        console.log('data2',data)
    }).catch(err => {
    
    
        console.log('err2',err)
    })
</script>

then 和 catch 改变状态

  • then 正常返回 resolved , 里面有报错则返回 rejetced

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>

</body>

</html>
<script>
    const p1 =  Promise.resolve().then(() => {
    
    
        return 100
    })
    // console.log(p1) // resolve 触发后续 then 回调
   p1.then(() => {
    
    
       console.log('123')
   })
   
    const p2 =  Promise.resolve().then(() => {
    
    
        return new Error('then erroe')
    })
    // console.log('p2',p2) // rejeced 触发后续 catch 回调
    p1.then(() => {
    
    
       console.log('456')
   }).catch(err => {
    
    
       console.error('err',err)
   })
</script>

  • catch 正常返回 resolved ,里面有报错则返回 rejetced

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>

</body>

</html>
<script>
    const p3 = Promise.reject('my error').catch(err => {
    
    
        console.log(err)
    })
    console.log('p3', p3) // resolve !注意
    p3.then(() => {
    
    
        console.log(100)
    })

    const p4 = Promise.reject('my error').catch(err => {
    
    
        throw new Error('catch err')
    })
    console.log('p4', p4) // rejected 
    p4.then(() => {
    
    
        console.log(200)
    }).catch(() => {
    
    
        console.log('some err')
    })
</script>


<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>

</body>

</html>
<script>
    // 第一题
    Promise.resolve().then(() => {
    
    
        console.log(1) // 1
    }).catch(() => {
    
    
        console.log(2)
    }).then(() => {
    
    
        console.log(3) // 3
    })

    // 第二题
    Promise.resolve().then(() => {
    
    
        console.log(1) // 1
        throw new Errp+or('errol')
    }).catch(() => {
    
       // resolve
        console.log(2) // 2
    }).then(() => {
    
    
        console.log(3) // 3
    })

    // 第三题
    Promise.resolve().then(() => {
    
     // rejeced 触发 catch 回调
        console.log(1) // 1
        throw new Errp+or('errol')
    }).catch(() => {
    
       // resolve 触发 then 回调
        console.log(2) // 2
    }).catch(() => {
    
       // !主意这里是 catch
        console.log(3)
    })
</script>

Promise 总结

  • 三种状太,状态的表现和变化
  • then 和 catch 对状态的影响(重要)
  • then 和 catch 的练市调用(常考)

async / await

  • 异步回电 callback hell
  • Promise then catch 练市调用,但也是基于回调函数
  • async / await 是同步发育发,彻底消灭回到函数
    -
    在这里插入图片描述

在这里插入图片描述

async / await 和 Promise 的关系

  • async / await 是消灭异步回调的终极武器
  • 但和 Promise 并不互斥
  • 反而,两者相辅相成
  • 执行 async 函数,返回的是 Promise 对象
  • await 相当于 Promise 的 then
  • tyr …catch 可捕获异常,代替 Promise 的 catch

在这里插入图片描述
在这里插入图片描述

异步的本质

  • async / await 是消灭异步回调的终极武器
  • JS 单线程,还得是有异步,还得是基于 event loop
  • async / await 只是一个语法糖,但这颗糖真香

for . . . of

  • for … in(以及 forEach for)是常规的同步遍历
  • for … of 常用于异步的遍历
    在这里插入图片描述

async / await 总结

  • async / await 解决了异步回调,是一个很香语法糖
  • async / await 和Promise 的关系,重要!
  • for … of 的使用

宏任务 macroTask 和微任务 microTask

  • 什么事宏任务 ,什么微任务
  • event loop 和 DOM 渲染
  • 微任务好人宏任务的区别

宏任务和微任务

  • 宏任务 : setTimeout ,setInterval , Ajax ,DOM 事件
  • 微任务 : Promise async / await
  • 我任务执行时机闭宏任务要早(先记住)

event loop 和 DOM 渲染

  • 再次回归一遍 event loop 的活成
  • JS 是单线程,而且和 DOM 渲染公用一个单线程
  • JS执行的时候,得留一些时机供 DOM 渲染
    在这里插入图片描述

回顾 event loop过程(增强 DOM 渲染时机)

在这里插入图片描述

  • 每次 Call Stack 清空(即每次轮询结束),及同步任务执行完
  • 都是 DOM 重新渲染的机会,DOM 结构如有改变则重新渲染
  • 然后再去触发下一次 Event Loop

微任务和宏任务的区别

  • 宏任务 : DOM 渲染后触发,如 setTimeout
  • 微任务 : DOM 渲染前触发,如 Promise

为什么

  • 微任务是 ES6 语法规定的
  • 宏任务是浏览器规定的
    在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/WLIULIANBO/article/details/115013791