2024前端面试复盘 1

122 阅读2分钟

题目一

async function async1() {
  console.log('async1 start');
  try {
    async2()
  } catch (e) {
    console.log(`error: ${e.message}`);
  }
}
function async2() {
  console.log('async2');
  async3();
}
async function async3() {
  throw new Error('async3 error');
}

console.log('script start');
setTimeout(function () {
  console.log('setTimeout');
}, 0)
requestAnimationFrame(function () {
  console.log('animation frame');
})
async1();
new Promise(function (resolve) {
  console.log('promise1');
  resolve();
}).then(function () {
  console.log('promise2');
});
console.log('script end');

这个题目中,我说错了两个地方

  1. async3 函数中 throw 出来的 error 是不会被捕获的,因为没有 await。想要错误被捕获有两种修改方式 方法一: 将 async3 改成普通函数,那么普通函数抛出的错误是会捕获的
async function async1() {
  console.log('async1 start');
  try {
    async2()
  } catch (e) {
    console.log(`error: ${e.message}`);
  }
}
function async2() {
  console.log('async2');
  async3();
}
function async3() {
  throw new Error('async3 error');
}

方案二:层层 await

async function async1() {
  console.log('async1 start');
  try {
    await async2()
  } catch (e) {
    console.log(`error: ${e.message}`);
  }
}
async function async2() {
  console.log('async2');
  await async3();
}
async function async3() {
  throw new Error('async3 error');
}

原因是在 promise 的 executor 和 then 的处理函数中,其实有一层隐式的 try ... catch ...,当有 throw error 或者有其它语法错误的时候就相当于 Promise.reject(error)。比如:

new Promise((resolve, reject) => {
   throw new Error("Whoops!")
}).catch(alert) // Error: Whoops!

// 等价于
new Promise((resolve, reject) => {
    reject(new Error("Whoops!"));
}).catch(alert) // Error: Whoops!

下面这两种也是一样的

new Promise((resolve, reject) => {
    resolve("ok")
}).then((result) => {
    throw new Error("Whoops!") // reject 这个 promise
}).cathc(alert); // Error: Whoops!

// 对于所有的 error,不仅仅是 throw 的 error 都会这样
new Promise((resolve, reject) => {
    resolve("ok")
}).then((result) => {
    blabal(); // reject 这个 promise
}).cathc(alert); // ReferenceError: blabla is not defined
  1. 还有就对 raf 是什么时候执行 在 B 站找到一个挺好的视频www.bilibili.com/video/BV1K4… 里面详细的讲了 eventLoop。有一个我的知识盲区,我只知道 raf 是下一次渲染之前调用的,使用它是为了不要等待太久这样一个概念。 通过这个视频,我理解了 eventLoop 会不停的从 task queue 里面获取新的 task,但是并不是一次 task 就会对应着一次渲染,有可能执行完多次 task 之后才会执行一次渲染。在题目中,如果不对页面做任何操作,那么 setTimeout 这个宏任务会先执行,然后在下一次渲染之前会执行 raf;但是执行这段代码的时候,页面还在持续滚动,可能就会先执行 raf 再执行 setTimeout。

image.png

题目二

下面这段代码在浏览器的表现

<style>
.box {
  width: 500px;
  height: 300px;
  background-color: gray;
}

.transition {
  background-color: black;
  transition: background-color 2000ms ease-in-out;
}
</style>

<body></body>

<script>
  const box = document.createElement('div')
  box.classList.add('box')
  document.body.appendChild(box)
  box.classList.add('transition')
</script>

实际上是会直接展示一个黑色的盒子而且也没有动画。 原因也是在上面的视频中提到的,上面的这一段脚本相当于一个 task,在这个 task 执行完之后,box 的 class 是即有 .box 又有 .transition,所以不会出现动画,要想出现动画,可以像下面这样改造一下

<script>
  const box = document.createElement('div')
  box.classList.add('box')
  document.body.appendChild(box)
  window.requestAnimation(() => {
    box.classList.add('transition')
  })

</script>

codesandbox.io/p/sandbox/e…

题目三

手写代码题目

class RequestQueue {
  maxLength: number;
  lastIndex: number;
  inProgressQueue: {
    fn: () => Promise<any>;
    resolve: (value: any | PromiseLike<any>) => void;
    reject: (reason?: any) => void;
    hasInvoked: boolean;
  }[];
  constructor(maxLength: number) {
    this.maxLength = maxLength;
    this.inProgressQueue = [];
    this.lastIndex = -1;
  }
  request(func: () => Promise<any>): Promise<any> {
    return new Promise((resolve, reject) => {
      this.inProgressQueue.push({
        fn: func,
        resolve,
        reject,
        hasInvoked: false,
      });
      if (this.inProgressQueue.length <= this.maxLength) {
        this.run();
      }
    });
  }
  run() {
    const item = this.inProgressQueue.find(
      (queueItem) => queueItem.hasInvoked === false
    );
    if (!item) {
      return;
    }
    const { fn, resolve, reject } = item;
    item.hasInvoked = true;
    fn()
      .then(
        (value) => {
          resolve(value);
        },
        (error) => {
          reject(error);
        }
      )
      .finally(() => {
        this.inProgressQueue = this.inProgressQueue.filter(
          (queueItem) => queueItem !== item
        );
        this.run();
      });
  }
}

const instance = new RequestQueue(3);

instance
  .request(async () => {
    await delay(100);
    return Promise.resolve(1);
  })
  .then((value) => {
    console.log({ result1: value });
  });

instance
  .request(async () => {
    await delay(1000);
    return Promise.resolve(2);
  })
  .then((value) => {
    console.log({ result2: value });
  });
instance
  .request(async () => {
    await delay(700);
    return Promise.resolve(3);
  })
  .then((value) => {
    console.log({ result3: value });
  });
instance
  .request(async () => {
    await delay(900);
    return Promise.resolve(4);
  })
  .then((value) => {
    console.log({ result4: value });
  });

function delay(time: number): Promise<void> {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve();
    }, time);
  });
}

export default RequestQueue;

参考: zh.javascript.info/promise-err…