JS部分面试题必备

1. 异步的面试题

问答题

  • 请描述event loop (事件循环/事件轮询)的机制,可画图
  • 什么是宏任务和微任务,两者有什么区别?
  • Promise有哪三种状态?如何变化?
  • 场景题-promise then 和catch 的链接
// 第一题
Promise.resolve().then(() => {  
    //返回resolved状态的promise后会执行.then的回调,.then的回调又会返回resolved状态的promise,resolved状态的promise不会执行catch,它会执行.then的回调。整个最后返回resolved状态的promise
    console.log(1)
}).catch(() => {
    console.log(2)
}).then(() => {
    console.log(3)
})  //resolved
// 1 3

// 第二题
Promise.resolve().then(() => {  
    //.then里报错,那么.then返回的是rejected状态的promise,rejected的promise触发.catch回调,因为.catch没有报错,所以返回的是resolved状态的promise,resolved状态的promise会触发.then。整个最后返回的还是resolved状态的promise
    console.log(1)
    throw new Error('erro1')
}).catch(() => {
    console.log(2)
}).then(() => {
    console.log(3)
}) //resolved

// 1 2 3

// 第三题
Promise.resolve().then(() => {//返回rejected状态的promise  触发 catch回调
    console.log(1)
    throw new Error('erro1')
}).catch(() => {            //返回resolved状态的promise  触发 then回调
    console.log(2)
}).catch(() => { // 注意这里是 catch
    console.log(3)
})
// 1 2

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

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

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

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

  throw new Error('error2');
  console.log(3);

})

//2
  • 场景提 - async/await 语法

    (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);
      //reject状态的Promise,不会执行await要用try...catch捕捉异常错误,此题报错显示没有捕获的错误,所以后边的代码不会执行
            console.log("c",c);
            console.log('end');
        })()
    //start  
    //a 100
    //b 200
    //Uncaught (in promise) 300
    
    
       async function fn(){
            return 100;
        }
        (async function(){
            const a = fn();
            console.log(a);
          //调用async修饰的函数返回的是promise对象  且这里的值是100   promise{<fulfilled>:100} 
            const b = await fn();
            console.log(b);//await相当于then   所以直接返回数值100
        })()
    
  • 场景提 - promise 和setTimeout的顺序

    console.log(100);
    setTimeout(()=>{
      console.log(200);
    })
    Promise.resolve().then(()=>{
      console.log(300);
    })
    console.log(400);
    
  • 场景题 - 外加 async/await 的顺序问题

    //网上经典面试题
    async function async1 () {
      console.log('async1 start') //2
      //await后面都是回调内容--微任务
      await async2() // 这一句会同步执行,返回 Promise ,其中的 `console.log('async2')` 也会同步执行
      console.log('async1 end') //6 上面有 await ,下面就变成了“异步”,类似 cakkback 的功能(微任务)
    }
    
    async function async2 () {
      console.log('async2')//3
    }
    
    console.log('script start')//1
    
    setTimeout(function () { // 异步,宏任务
      console.log('setTimeout')//8
    }, 0)
    
    async1()
    
    //初始化promise时,传入的函数会立刻被执行
    new Promise (function (resolve) { // 返回 Promise 之后,即同步执行完成,then 是异步代码
      console.log('promise1') //4  Promise 的函数体会立刻执行 
      resolve()
    }).then (function () { // 异步,微任务
      console.log('promise2')//7
    })
    
    console.log('script end')//5
    
    // 同步代码执行完之后,屡一下现有的异步未执行的,按照顺序
    // 1. async1 函数中 await 后面的内容 —— 微任务
    // 2. setTimeout —— 宏任务
    // 3. then —— 微任务
    //除了宏任务和微任务其他都是同步的
    

event loop

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

JS 如何执行?

  • 从前到后,一行一行执行

  • 如果某一行报错,则停止下面代码的执行

  • 先把同步代码执行完,再执行异步

  • 总结event loop过程 

    第一步:把console.log('Hi')这行代码推入调用栈调用栈执行代码,执行完之后打印出Hi

    第二步:这行代码执行完毕,清空调用栈第三步:执行到setTimeout()函数(浏览器定义(WebAPIs)的),把异步函数cb1放到定时器里面,定时是5s,5s之后把cb1放到Call Queue里面,至此setTimeout函数(三行代码)结束,console里不会打印任何东

    ![image-20210405134825179](/Users/yangxianqiang/Library/Application Support/typora-user-images/image-20210405134825179.png)第四步: 执行最后一行代码console.log("Bye"),和第一行一样,把它推送到调用栈中执行打印Bye

    执行完毕之后,调用栈清空。此时后边已经没有要在被推送到callstack中执行的代码了,也就是同步代码已经执行完毕了,这时就会启动EventLoop(浏览器内核自动启动),他会像永动机或者死循环一样不断的执行循环,它会在callback queue(异步回调函数)里去找函数,如果有的话它会把函数放到callStack里执行,cb1有函数体,把console.log('cb1')放到调用栈里执行, 最后清空调用栈

    • Call Stack:真正触发执行某行代码

    • Web APIs:对于浏览器的运行环境和一些API的定义,setTimeout(setInterval,dom操作)不是SE里定义的,而是浏览器里面定义的API.处理定时或者异步的api的

    • Event Loop:当Call Stack 空了的时候(同步代码执行完毕,尝试dom渲染后)开始触发,它就像永动机一样或者想死循环一样不停地在callback Queue中找有没有可以执行的函数,如果有的话就把它移动到Call Stack中执行,

    • Callback Queue:回调函数的队列

  1. 同步代码,一行一行放在call stack执行

  2. 遇到异步,会先’‘记录’’下,等待时机(定时,网络请求等)

  3. 时机到了,就移动到Callback Queue

  4. 如果Call Stack 为空,尝试渲染dom之后,(即同步代码执行完)Event Loop开始工作

  5. 轮询查找Callback Queue中的回调函数,如有则移动到CallStack执行,再尝试渲染dom

  6. 然后继续轮询查找(像永动机一样)

  7. Callstack,尝试dom渲染,触发下一次eventloop 轮询查找是循环往复的