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

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

目录

开头热场问题

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

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

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

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

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

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

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

        2.6 实践2

     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