2024(地狱一年):字节前端面试

884 阅读4分钟

本人毕业后一直在广州从事前端开发,至今接近5年经验。2023是糟糕的一年,本想着过完年后,行情会有所恢复。春节后股票飙升了不少,Sora爆火。。。总感觉给人一种企稳的信心,但事实上经济依旧低迷,大家都不敢高消费。年后回来岗位少的可怜,特别是广州这个互联网荒漠地带。更甚的是,大厂一波接着一波裁员优化,僧多粥少的时代,真就是buff层叠满。

本人2月份就开始看机会,3月底有幸参加了字节的前端面试,总体面试体验还是不错的,分享自己面试经验,话不多说,开始吧。

一轮面试

事件循环

async function a1() {
  console.log(1);
  await a2();
  console.log(2);
}

async function a2() {
  console.log(3);
}

setTimeout(() => {
  console.log(4);
}, 0);

console.log(5);
a1();

new Promise((resolve) => {
  console.log(6);
  resolve();
}).then(() => {
  console.log(7);
});

console.log(8);

// 5 1 3 6 8 2 7 4

Promise知识

  • all 和 allSettled 的区别
  • all的实现思路
  • Promise.resolve()传入的不同参数结果有什么不同

tree shaking

  • 摇树是怎么实现的?
  • css 是怎么进行按需加载的
  • esm 是怎么转成 cjs 的
  • 组件库怎么实现按需加载

webpack

  • loader 和 plugin 的区别
  • plugin 的实现原理(node 的 eventbus 模块,发布订阅)

sentry

sentry 是怎么捕捉各种错误的,怎么区分错误类型。比如资源请求失败是怎么知道的。(用 error 和 unhandledrejection 事件, 用 Performance 可以监听资源加载失败)

实现并发函数

// 2 为并发最大数
const limit = pLimit(2);
const inputs = [
  limit(() => fetchSomething(1000), "param1"),
  limit(() => fetchSomething(2000), "param2"),
  limit(() => fetchSomething(3000), "param3"),
];
function fetchSomething(time) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, time);
  });
}
const time = Date.now();
Promise.all(inputs).then((results) => {
  console.log(results);
  console.log(Date.now() - time); // 需要打印为 4s。(解释:因为并发数限制为2,第一个和第二个请求并发进行,第三个等待,第一个结束后开始第三个请求。总体时间为4s)
});

// 实现 pLimit 函数?
function pLimit(limit) {}

我的实现

function pLimit(limit) {
  const queue = [];
  let activeCount = 0;

  function schedule() {
    if (activeCount < limit && queue.length > 0) {
      const { fn, resolve, reject, args } = queue.shift();
      activeCount++;
      fn(...args)
        .then((result) => {
          resolve(result);
        })
        .catch((err) => {
          reject(err);
        })
        .finally(() => {
          activeCount--;
          schedule();
        });
    }
  }

  return function (fn, ...args) {
    return new Promise((resolve, reject) => {
      queue.push({ fn, resolve, reject, args });
      schedule();
    });
  };
}

二轮面试

根据id查找树节点,打印查找路径

const arr = [
  {
    id: 1,
    child: [
      { id: 3 },
      {
        id: 4,
        child: [
          {
            id: 5,
            child: [{ id: 6 }],
          },
        ],
      },
    ],
  },
  {
    id: 2,
    child: [
      {
        id: 7,
      },
    ],
  },
];

我的实现

console.log(BFS(arr, 6)); // 查找id为6,需要打印路径:[1, 4, 5, 6]
function BFS(nodes, id) {
  const queue = nodes;
  while (queue.length > 0) {
    let len = queue.length - 1;
    while (len >= 0) {
      const node = queue.shift();
      if (!node.path) {
        node.path = [node.id];
      }

      if (id === node.id) {
        return node.path;
      }
      if (node.child && node.child.length > 0) {
        queue.push(
          ...node.child.map((item) => ({
            ...item,
            path: [...node.path, item.id],
          }))
        );
      }
      len--;
    }
  }
}

实现异步sum方法

function asyncAdd(a, b, callback) {
  setTimeout(function () {
    callback(a + b);
  }, 1000);
}

// 请实现 sum 方法
sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11).then((sum) => console.log(sum)); // 66

我的实现

function sum(...args) {
  return new Promise((resolve) => {
    let res = 0;
    let count = 0;
    for (item of args) {
      asyncAdd(res, item, (sum) => {
        res += sum;
        count++;
        if (count === args.length) {
          resolve(res);
        }
      });
    }
  });
}

性能优化

  • 图片懒加载原理
  • 虚拟列表原理

vite 简要原理

  • esm
  • 开发时用 esbuild,生产时用 rollup
  • ...

webpack 的 hash、contenthash、chunkhash

  • hash: 当任何项目文件发生更改时,Webpack 将生成一个新的哈希。这意味着如果你修改了任何一个文件,那么整个项目的哈希都会改变,导致所有的缓存失效。
  • contenthash: 这个哈希是根据文件内容来生成的。当文件的内容发生变化时,Webpack 才会生成新的哈希。这意味着即使项目中其他文件发生了变化,只要特定文件的内容没有改变,它的哈希也不会改变。
  • chunkhash: 这个哈希是根据特定的模块生成的。每个入口文件都会有一个唯一的 chunkhash。如果一个模块的内容发生变化,那么该模块的 chunkhash 就会改变,但其他模块的 chunkhash 不受影响。

三轮面试(业务总监)

不具体问技术细节,从项目层面问

  • 性能优化做了哪些
  • 内存泄露怎么排查 (chrome的性能面板)
  • React 和 Vue 的区别
  • 生涯规划等等

HRBP面

  • 规划,经历等

交叉面

全面介绍你最近做的一个项目

实现 0.1 + 0.2 = 0.3

  • 首先为什么 0.1 + 0.2 不等于 0.3 ?(精度问题)
  • 然后怎么实现让相加等于 0.3 (转成字符串再相加)

实现 红绿灯

我的实现


/**
 * 我的实现
 * @param {number} maxCount 红绿灯循环次数
 * @param {number} wait 红绿灯间隔时间
 */
function redAndGreen(maxCount, wait) {
  let count = 0;
  let status = "red";

  function run() {
    switch (status) {
      case "red":
        console.log("red");
        status = "yellow";
        setTimeout(run, wait);
        break;
      case "yellow":
        console.log("yellow");
        status = "green";
        setTimeout(run, wait);
        break;
      case "green":
        console.log("green");
        status = "red";
        count++;
        if (count < maxCount) {
          setTimeout(run, wait);
        }
        break;
    }
  }

  run();
}

redAndGreen(3, 100);

买卖股票

只能一次买卖,leetcode的简单题

最后

由于薪资职级等一些问题,最终没有去成,还是感到挺惋惜的。说真的,2024面整个互联网差到离谱,前端岗位更是少得可怜。对于切图仔的我有时真感到很无力。但是,我们虽然普通,但并不影响我们提升自己。想着继续保持学习的态度,同时探索副业的可能性,不管怎么样,都要为将来做好准备。最近看完《飞驰人生 2》,真的很喜欢张弛的一句话:“我努力了无数次,我知道机会只会出现在其中一两次”。