2023 前端一场面试及答案整理

金三马上就要开始了,俗话说得好,知己知彼百战百胜,多准备总是没有错的。以面试的形式和大家一起学习、一起回顾我们的职场生涯。今天简单总结一下我个人去面试,包括我在面试别人时的经验。加油加加油!!!

目录

开头热场问题

1. 说一下工作中解决过比较困难的问题 或 说一下自己项目中比较有亮点的地方

2. 你了解浏览器的事件循环吗?

    2.1 为什么 js 在浏览器中有事件循环的机制?

    2.2 你了解事件循环当中的两种任务吗?

    2.3 为什么要引入微任务的概念,只有宏任务可以吗?

    2.4 你了解 Node.js 的事件循环吗?Node中的事件循环和浏览器的事件循环有什么区别?

        2.5 这个时候理论问了这么多了,开始实践1

        2.6 实践2

扫描二维码关注公众号,回复: 14919937 查看本文章

     2.7 实践3 

3. 事件的捕获和冒泡机制你了解多少?

    3.1 基本概念

    3.2  window.addEventListener 监听的是什么阶段的事件?         

    3.3 平常有哪些场景用到了这些机制? 

4.  你工作中用过防抖和节流吗?

    4.1 基本概念

    4.2 分别适合用在什么场景?

    4.3  手写节流函数

5. 你了解 Promise 吗?平时用的多吗?

    5.1 Promise.all( ) 你知道有什么特性吗?

    5.2 如果其中一个 Promise 报错了怎么办?

    5.3 如果有一个 Promise 报错了,那么其他的 Promise 还会执行吗?

    5.4 手写一个 Promise.all(  )

    5.5 Promise 在初始化的时候已经执行了,那么利用这个特性我们可以做点什么(扩展性问题)?

 6. 字节经典算法题---- 接雨水


开头热场问题

1. 说一下工作中解决过比较困难的问题 或 说一下自己项目中比较有亮点的地方

   考察目的:面试官主要看一下我们解决问题的能力

   解答:这个问题 主要是靠大家的工作积累,平时工作过程中可以养成一个良好的习惯,无论做什么需求的时候,都花点时间去记录一下,这样积累无论是一年,还是两年,或者五年都会积累很多也无需求,然后自己梳理一下,在面试的时候反馈给面试官,让面试官虎躯一震,offer马上到手~~~

2. 你了解浏览器的事件循环吗?

     考察目的:以此问题作为一个突破口,深度挖掘你对整个概念的了解

    2.1 为什么 js 在浏览器中有事件循环的机制?

     解答

               ① JS 是单线程的

               ② event loop

    2.2 你了解事件循环当中的两种任务吗?

     解答

                ① 宏任务:整体代码块、setTimeOut、setInterval、I/O操作

                ② 微任务:new Promise().then()、mutationObserver(前端的回溯)

    2.3 为什么要引入微任务的概念,只有宏任务可以吗?

    解答

        宏任务:先进先出的原则

        在 JS 的执行过程中 或者说在页面的渲染过程中,宏任务是按照先进先出的原则执行,我们并不能准确的控制 这些任务被推进任务队列里,但是我们这个时候如果出来一个非常高优先级的任务,这个时候该怎么办?如果我们只有宏任务,再往任务队列里面推一个,秉承着先进先出的原则,那么它肯定是最后一个执行的,所以要引入微任务;

了解到宏任务与微任务过后,我们来学习宏任务与微任务的执行顺序。        

  • 代码开始执行,创建一个全局调用栈,script作为宏任务执行
  • 执行过程过同步任务立即执行,异步任务根据异步任务类型分别注册到微任务队列和宏任务队列
  • 同步任务执行完毕,查看任务队列
    • 若存在微任务,将微任务队列全部执行(包括执行微任务过程中产生的新微任务)
    • 若无微任务,查看宏任务队列,执行第一个宏任务,宏任务执行完毕,查看微任务队列,重复上述操作,直至宏任务队列为空

    2.4 你了解 Node.js 的事件循环吗?Node中的事件循环和浏览器的事件循环有什么区别?

    解答

            Node宏任务的执行顺序:

                ① timers定时器:执行已经安排过的,setTimeout 和 setInterval 的回调函数;

                ②pending callback 待定回调:执行延迟到下一个循环迭代的I/O回调;

                ③idle,prepare:仅系统内部使用;

                ④poll:检索新的I/O事件,执行与I/O相关的回调

                ⑤check:执行setImmediate() 回调函数

                ⑥close callback:socket.on('close', (  )=>{  })

          微任务和宏任务在node的执行顺序:

        首先大家要明白,Node中微任务和宏任务的执行顺序是和 node 的版本有关系的

        Node V10 之前:

                 ① 执行完上述一个阶段中的所有任务

                 ② 执行 nextTick 队列里面的内容

                 ③ 执行完微任务队列的内容

        Node V10 之后:

                和浏览器的行为统一了

        2.5 这个时候理论问了这么多了,开始实践1

async function async1() {
  console.log("async1 start");
  await async2();
  console.log("async1 end");
}
async function async2() {
  console.log("async2");
}
console.log("script start");
setTimeout(() => {
  console.log("setTimeout");
}, 0);
async1();
new Promise((resolve) => {
  console.log("promise1");
  resolve();
}).then(() => {
  console.log("promise2");
});
console.log("script end");

// 1. script start
// 2. async1 start
// 3. async2
// 4. promise1
// 5. script end
// 6. async1 end
// 7. promise2
// 8. setTimeout

        2.6 实践2

                这个就比较有难度了,如果真的不会,千万不要张嘴就来一句:“我不会”,这个时候我作为面试官的时候心里就在想:“我屮艸芔茻,这好尴尬”!娱乐一下开个玩笑,这个时候要尝试着说出自己的思路,即使是错的,你也要让面试官看到你的 进取、钻研精神!

console.log("start");
setTimeout(() => {
  console.log("children2");
  Promise.resolve().then(() => {
    console.log("children3");
  });
}, 0);

new Promise((resolve, reject) => {
  console.log("children4");
  setTimeout(() => {
    console.log("children5");
    resolve("children6"); // 此处大坑
  }, 0);
}).then((res) => {
  console.log("children7");
  setTimeout(() => {
    console.log(res);
  }, 0);
});

// 1. start
// 2. children4
/** 第一轮宏任务执行结束,尝试清空微任务队列,发现没有微任务,尝试执行下一轮宏任务   */
// 3. children2
/** 第二轮宏任务执行结束,尝试清空微任务队列,   */
// 4. children3
// 5. children5
/** 第三轮宏任务执行结束,尝试清空微任务队列,   */
// 6. children7
// 7. children6

     2.7 实践3 

        到这一步,大家心里是不是在想:“面试官怎么抓着 事件循环不放了”,最后一道题

const p = () => {
  return new Promise((resolve, reject) => {
    const p1 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve(1); // 这里 不会再输出了,因为 resolve(2) 已经把结果输出
      }, 0);
      resolve(2);
    });
    p1.then((res) => {
      console.log(res);
    });
    console.log(3);
    resolve(4);
  });
};
p().then((res) => {
  console.log(res);
});
console.log("end");

// 1. 3
// 2. end
// 3. 2
// 4. 4

                 到此为止,事件循环结束!

3. 事件的捕获和冒泡机制你了解多少?

    3.1 基本概念

           以HTML为例:↓

           捕获:从window →  parent  →  child  →  son 到目标元素以后,转为冒泡

           冒泡:目标元素 son  →  child  →  parent  → window

    3.2  window.addEventListener 监听的是什么阶段的事件?         

// 冒泡阶段
window.addEventListener("click", () => {

}); // 第三个参数默认为 false,为false 时,监听的为冒泡阶段

// 捕获阶段
window.addEventListener("click", () => {
  
}, true);

    3.3 平常有哪些场景用到了这些机制? 

        3.3.1. 事件委托 

    <ul id="ul">
      <li>1</li>
      <li>2</li>
      <li>3</li>
      <li>4</li>
      <li>5</li>
      <li>6</li>
      <li>7</li>
      <li>8</li>
    </ul>


    const ul = document.querySelector("ul");
    ul.addEventListener("click", (e) => {
      const target = e.target;
      if (target.tagName.toLowerCase() === "li") {
        const liList = document.querySelectorAll("li");
        //  这里之所以会这么写,是因为 liList 并非是一个真正的 Array
        // 此时返回的是一个 nodeList,如果想使用 数组的 方法,需要改变this
        const index = Array.prototype.indexOf.call(liList, target);
        console.log(`内容:${target.innerHTML},索引:${index}`);
      }
    });

        3.3.2  场景设计题

        一个历史页面,上面有若干按钮的点击逻辑,每个按钮都有自己的 click 事件

        新需求来了:给每一个访问的用户添加了一个属性,如果 banned = true,此用户点击页面上的任何按钮或元素,都不可响应原来的函数。而是直接 alert 提示,你被封禁了。

        实现:采用 事件捕获 机制完成(当然实现的思路有三种,甚至更多,这里只说事件捕获)      

/**
    * 场景设计题
    一个历史页面,上面有若干按钮的点击逻辑,每个按钮都有自己的 click 事件
     新需求来了:给每一个访问的用户添加了一个属性,如果 banned = true,此用户点击页面上的任何按钮或元素,
    都不可响应原来的函数。而是直接 alert 提示,你被封禁了。
*/


window.addEventListener(
  "click",
  (e) => {
    if (banned) {
      e.stopPropagation();
    }
  },
  true
);

4.  你工作中用过防抖和节流吗?

    4.1 基本概念

        防抖:持续触发事件的时候,一定时间段内,没有再触发事件,时间处理函数才会执行一次

        节流:持续触发事件的时候,保证一段时间内只调用一次事件处理函数(固定时间)

    4.2 分别适合用在什么场景?

        防抖:input输入(巨量引擎)

        节流:resize(屏幕大小改变)、 scroll(滚动时)   ---> 一定要执行的,给一个固定间隔 

    4.3  手写节流函数

        时间戳写法,第一次立即执行

// 时间戳写法,第一次立即执行
const throttle = (fn, interval) => {
  let last = 0;
  return () => {
    let now = Date.now();
    if (now - last >= interval) {
      fn.apply(this, arguments);
    }
  };
};
const handle = () => {
  console.log(Math.random());
};
const throttleHandle = throttle(handle, 3000);
throttleHandle();
throttleHandle();

        定时器写法,第一次也需要延时 具体 时间以后再执行

// 定时器写法,第一次也会延时 具体的时间执行
const throttle = (fn, interval) => {
  let timer = null;
  return function () {
    let context = this;
    let args = arguments;
    if (!timer) {
      timer = setTimeout(() => {
        fn.apply(context, args);
        timer = null;
      }, interval);
    }
  };
};
const handle = () => {
  console.log(Math.random());
};
const throttleHandle = throttle(handle, 1000);
throttleHandle();
throttleHandle();

        精确的实现一个节流函数,无论是第一次之后还是最后一次(避免最后一次执行还会再等具体时间之后再执行)

// 精确的实现一个节流函数,无论是第一次之后还是最后一次(避免最后一次执行还会再等具体时间之后再执行)
const throttle = (fn, delay) => {
  let timer = null;
  let startTime = Date.now();
  return function () {
    let curTime = null;
    let remainning = delay - (curTime - startTime);
    let context = this;
    let args = arguments;
    clearTimeout(timer);
    if (remainning <= 0) {
      fn.apply(context, args);
      startTime = Date.now();
    } else {
      timer = setTimeout(fn, remainning);
    }
  };
};

        

5. 你了解 Promise 吗?平时用的多吗?

    5.1 Promise.all( ) 你知道有什么特性吗?

         解答Promise.all( ) 会接受一个 Promise 数组,数组里面可以是 Promise 也可以是一个常量或者其他;执行情况为:Promise 里面的所有 Promise 执行完成以后才会返回结果;

    5.2 如果其中一个 Promise 报错了怎么办?

        解答:如果有一个报错了,那么整个 Promise.all(  ),就会返回一个 catch

    5.3 如果有一个 Promise 报错了,那么其他的 Promise 还会执行吗?

        解答:会的,因为 Promise 是在创建之初(实例化) 的时候已经执行了

    5.4 手写一个 Promise.all(  )

        面试官:“给你 三个 如下的 Promise ,调用你的Promise.all( )以后,看是否会在三秒以内返回对应的结果”        

// 测试
const pro1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("1");
  }, 1000);
});
const pro2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("3");
  }, 2000);
});
const pro3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("3");
  }, 3000);
});

// 测试题
const PromiseAll = (promiseArray) => {};

     如何实现?

        考点1:Promise.all(  ) 里面的参数有可能不是一个 Promise,如何处理?

        考点2:Promise.all(  ) 返回值的顺序,是你传入的 Promise 顺序,如何处理?

// 测试题
const PromiseAll = (promiseArray) => {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(promiseArray)) {
      return reject(new Error("Type can only be array"));
    }
    const result = []; // promise 执行的结果集
    const promiseNums = promiseArray.length; // 当前循环次数
    let counter = 0; // 记录当前 promise 执行顺序,需要按照 传入的 promise 顺序返回
    for (let i = 0; i < promiseNums; i++) {
      Promise.resolve(promiseArray[i])
        .then((value) => {
          counter++;
          result.push(value);
          if (counter === promiseNums) {
            resolve(result);
          }
        })
        .catch((e) => reject(e));
    }
  });
};

console.log(
  PromiseAll([pro1, pro2, pro3])
    .then((res) => {
      console.log(res);
    })
    .catch((e) => {
      console.log(e);
    })
);

    5.5 Promise 在初始化的时候已经执行了,那么利用这个特性我们可以做点什么(扩展性问题)?

         解答:可以利用 promise 的这个特性做缓存;

        利用 装饰器 + Map结构,实现一个 Promise 的缓存

const cacheMap = new Map();
const enableCache = (target, name, descriptor) => {
  const val = descriptor.value;
  descriptor.value = async (...args) => {
    const cacheKey = name + JSON.stringify(args);
    if (!cacheMap.get(cacheKey)) {
      const cacheValue = Promise.resolve(val.apply(this, args)).catch((_) => {
        cacheMap.set(cacheKey, null);
      });
      cacheMap.set(cacheKey, cacheValue);
    }
    return cacheMap.get(cacheKey);
  };
  return descriptor;
};
class PromiseClass {
  @enableCache
  static async getInfo() {}
}

PromiseClass.getInfo(); // 第一次发送请求
PromiseClass.getInfo(); // 第二次以后就是缓存
PromiseClass.getInfo();
PromiseClass.getInfo();

 6. 字节经典算法题---- 接雨水

题干:

        给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

        示例1:

        输入 height = [0,1,0,2,1,0,1,3,2,1,2,1]

        输出:6

        解释:上面是由数组  [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 单位的雨水(如上图,蓝色部分表示雨水)

        示例2:

        输入 height = [4,2,0,3,2,5]

        输出:9

猜你喜欢

转载自blog.csdn.net/weixin_56650035/article/details/123163419