阅读 2827

前端一面基础知识 ③——异步(面试场景题)

前言

    根据慕课网的《快速搞定前端技术一面 匹配大厂面试要求》课程所整理的题目,陆续更新

常考面试题

如果你觉得自己对异步该部分知识掌握得已经差不多了,那么你可以不看前面的,直接跳转到最底下查看面试场景题

1.同步、异步的区别是什么?

异步:不会阻塞代码的执行

同步:会阻塞代码执行

同步和异步的差别就在于这条流水线上各个流程的执行顺序不同。

  • 同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有等主线程任务执行完毕,"任务队列"开始通知主线程,请求执行任务,该任务才会进入主线程执行。

2.手写promise

function myPromise(fn) {
   // 回调函数集合
   this.callbacks = []
   // resolve 方法
   // 往实例上挂载 data
   // 依次执行回调函数
   function resolve(value) {
     setTimeout(() => {
       this.data = value;
       this.callbacks.forEach((callback) => callback(value));
     });
   }

  // 将 resolve 方法交还
  fn(resolve.bind(this)); 
}

myPromise.prototype.then = function (onResolve) {
  return new myPromise((resolve) => {
    this.callbacks.push(() => {
       // 将结果返回给传入的回调
       const res = onResolve(this.data);
       // 链式调用  的返回值还是 Promise 对象时
       if (res instanceof myPromise) {
         res.then(resolve);
       } else {
         resolve(res);
       }
   });
 }); 
};
复制代码

3.手写用promise加载一张图片

function loadImg(src) {
    const p = new Promise(
         ( resolve , reject ) => {
            const img = document.createElement('img')
             img.onload = () => {
                resolve(img)
             }
             img.onerror = () => {
                 reject(new Error(`图片加载失败${src}`))
             }
             img.src = src
         }
     )
     return p
 }

 const url = 'xxx'
 loadImg(url).then( img => {
     console.log(img.width)
     return img
 } ).then( img => {
     console.log(img.height)
 } ).catch( ex => console.error(ex) )
复制代码

4.前端使用异步的场景有哪些?

  • ①网络请求,ajax图片加载
  • ②定时任务,setTimeout

5.异步产生的背景及异步机制

异步产生的背景

  • ①JS是单线程语言,只能同时做一件事
  • ②没有异步之前,遇到等待(网络请求、定时任务)会卡住
  • ③同步会阻塞代码执行

异步机制

  • 采用回调callback函数形式(但是这样子会产生坏代码,回调表达异步流程的方式是非线性的、非顺序的,这使得正确推导这样的代码难度很大。难于理解的代码是坏代码,会导致坏 bug,也就会产生大家一般所说的回调地狱callback hell问题)

6.promise产生背景及作用

回调地狱(异步调用异步)callback hell,于是产生了promise

  • 作用:promise把url变成了非嵌套的模式,转化成了管道形式,变成管道传递,如果是直接用ajax请求,如果有一份文件很大,那么页面就会卡住一直等待那一份数据请求成功
  • event loop 是异步回调的实现原理

7.请描述event loop的机制

    event是异步回调的实现原理

  • ①同步代码,一行一行放在call stack中执行
  • ②遇到异步,会先记录下,等待时机
  • ③时机到了,就移动到callback queue
  • ④如果call satck为空时(同步代码执行完),空闲下来之后,先执行微任务,尝试DOM渲染
  • ⑤event loop开始工作,执行宏任务
  • ⑥轮询查找callback queue,如有任务则移动到call stack执行
  • ⑦继续轮询查找

8.什么是宏任务、微任务?两者有什么区别?

宏任务:DOM渲染后触发,如setTimeout

  • script(整体代码)、setTimeout、setInterval、setImmediate、I/O、UI rendering、Ajax、DOM事件

微任务:DOM渲染前触发,如Promise

  • promise、Object observe、Mutation Observe、async/await

任务优先级:process.nextTick > promise.then > setTimeout > setImmediate

如何理解宏任务跟微任务处理的关系呢?

  • 记得之前看过一个例子是这么描述的,比较好理解,就像那些去银行排队办理业务的人,柜员里的业务员是js单线程,来排队的每一个人是宏任务,每一个人会办理几个业务,每一个业务是微任务,也就是每一个宏任务带着几个微任务一个个去执行。也就是只能处理完了当前所有的微任务,才会开始下一个宏任务

9.promise有哪三种状态?如何变化

三种状态

  • pending 过程中
  • fulfilled 解决了(成功)
  • rejected 被拒绝(失败)
  • 一旦Promise 被 resolve 或 reject,不能再迁移至其他任何状态(即状态 immutable)(面试场景题中会在这埋坑)

如何变化?

  • then正常会返回fulfilled
  • catch正常也会返回fulfilled
  • 有报错就返回rejected
  • resolved触发后续的then
  • rejected触发后续的catch

10.async/await简单认识

  • Promise的then、catch链式调用,其实也还是基于回调函数
  • 而async/await是同步语法,彻底消灭回调函数
  • async/await和promise并不互斥,是相辅相成的作用
  • 执行async函数,返回的是Promise对象,await相当于promise的then,try、catch可捕捉异常,代替了promise的catch
  • async/await只是一个语法糖,本质上JS还是单线程,还是异步,还是基于event loop,但这颗糖真香

11.各种面试异步场景题

下面是各种相关异步场景题,可以做一下,然后左滑查看注释里的答案

1.setTimeout

  console.log(1)
        setTimeout( function () {
            console.log(2)
        } , 1000 )
        console.log(3)
        setTimeout( function () {
            console.log(4)
        } , 0 )
        console.log(5)

                                              // 1 3 5 4 2
复制代码

2.promise状态(1)

  Promise.resolve()
            .then( () => {
                console.log(1)
                throw new Error('error1') // 排出错误,状态变为rejected
        } ).catch( () => {  // rejected状态触发catch
            console.log(2)
        } ).then( () => {  // catch返回fulfilled状态,再触发then
            console.log(3)
        } )

                                               // 1 2 3

复制代码

3.promise状态(2)

  Promise.resolve()
            .then( () => {
                console.log(1)
                throw new Error('error1')
        } ).catch( () => {
            console.log(2)
        } ).catch( () => {
            console.log(3)
        } )

                                               // 1 2
复制代码

4.promise状态(3)

  Promise.resolve()
            .then( () => {
                console.log(1)
        } ).catch( () => {  // fulfilled状态不触发catch
            console.log(2)
        } ).then( () => {
            console.log(3)
        } )

                                               // 1 3
复制代码

5.微任务、宏任务

  console.log(100)

        setTimeout( () => {
            console.log(200)
        } ) // 宏任务

        Promise.resolve().then( () => {
            console.log(300)
        } ) // 微任务

        console.log(400)
        
                                                                                                // 100 400 300 200
                                                                                                // 微任务执行时机比宏任务早
复制代码

如果以上题目你都做对了,那么你对promise的理解已经差不多了,那么接下来看看async/await

6.async/await (1)

(async function () {

            console.log('start')
            
            const a = await 100
            console.log('a:',a)

            const b = await Promise.resolve(200)
            console.log('b:',b)

            const c = await Promise.reject(300)
            console.log('c:',c)

            console.log('end')
            
        })()

                                                                                                      // start
                                                                                                      // a 100
                                                                                                      // b 200
                                                                                                      // 报错
复制代码

7.async/await (2)

  async function async1() {
            console.log('async1 start')
            await async2()
            // await 的后面,都可以看作是 callback 里的内容,即异步
            console.log('async1 end') // 异步内容先不执行,放到一边,先执行完同步内容再回来执行异步 5
        }

        async function async2() {
            console.log('async2')
        }


        console.log('script start')
        async1()
        console.log('script end')
        
                                              // script start、async1 start、async2、script end、async1 end
复制代码

8.async/await (3)

  async function async1() {
            console.log('async1 start')  
            await async2()

            // 下面都是异步回调
            console.log('async1 end') 
            await async3() 
            console.log('async1 end 2') 
        }

        async function async2() {
            console.log('async2')  
        }

        async function async3() {
            console.log('async3')   
        }


        console.log('script start')  
        async1()
        console.log('script end')  

                                             // script start、async1 start、async2、script end、async1 end、async3、async1 end 2、
                                                                                     
复制代码

9.终极结合场景面试题(听说是当年今日头条面试原题)

  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')
        
                                             //script start、async1 start、async2、promise1、script end、async1 end、promise2、setTimeout、
复制代码

如果这道题你也做对了,那说明你对这异步这方面的知识已经掌握得差不多了

写在文末

    如果你觉得我写得还不错的话,可以给我点个赞哦^^,如果哪里写错了、写得不好的地方,也请大家评论指出,以供我纠正。

其它文章

本文使用 mdnice 排版