【前端面经】2023面试复盘之字节跳动

hello大家好,我是 Range。今天带来一篇字节的前端社招面试复盘,通过这篇复盘总结,再一次说明了,对社招同学来说,八股文只是基础,在这个充满寒意的冬天,光背背八股文已经完全行不通了,必须要能够和自己实际项目结合起来,融汇贯通,才是面试通过的关键。

结论

✅通过

字节的面试还挺曲折,一开始是某部门的HR直接联系我的,在面了三轮之后感觉面试官的状态瞅着蔫蔫的🫠,又咨询了一下在字节的朋友,便决定更换部门(但是该部门的HR小姐姐很好,跟了我一个多月,还提前给我做面试建议和辅导,希望她能找到更好的候选人🙏🏻)。

不得不说字节真是宇宙厂,在这样的行情下依然有很多部门在招人。因为之前拒过字节的offer加上不少朋友在,所以也是跟好几个部门的ld微信聊了聊,之后便选择了一个新的部门。而前面流程的三面通过也促使了换部门后可以直通终面,这点是真的不错~

整体看下来,字节的面试给我的感觉就是比较正常的从技术到管理循序渐进,代码题的话不再是执着于leetcode,而是替换为了一些前端或业务可能遇到的问题,对于我这种从来不刷题的算法渣来说确实挺不错的👍。最后由于我目前的工作经历的原因导致有面试官的定级不一致,便又加了一轮定级面。

一面

总时长:65min

这次换工作的第一个面试,在2023的行情下还是挺珍惜的,所以准备了挺久,结果发现准备的一点也没有用上,好在问的一些问题靠着平时的积累还是能够应付一下。

感觉面试官可能不算太资深,面试和代码都没有很难,不过自己答得也不是太好,完全没有达到自己的预期和标准,所以对于结果也不是特别有把握。

聊项目

不知道是不是面的岗位的原因,本来准备了很久了基础完全没有问,上来就开始聊项目。这部分主要是我自己在讲,面试官穿插着问了一些问题,不过感觉技术性没有那么强,都是比较开放性的问题。

本身聊项目是我最擅长的事情,不过因为很多项目事先都没准备,所以感觉项目的难度和技术的复杂点讲的不够,这部分聊完差不多就半个多小时过去了。

后面就是针对于我的简历及项目情况问的一些具体的技术问题。

作为前端负责人在前端基础设施做了哪些事情

  1. 开发阶段,包含了技术选型、项目创建、模板化、脚手架工具等方面的工作

  2. 部署阶段,如何去做自动化的CI/CD、如何将项目部署到服务器

  3. 质量保证,尝试使用一些自动化测试框架进行项目测试,同时对项目配置进行收敛,减少配置的修改,以保证每个项目的基础设施统一

  4. 如何提效,通过脚手架工具来实现零配置启动部署项目,通过模板、组件以及对应的schema调用方式来降低开发门槛和效率提升

Vite的底层原理是什么

这部分确实是还没有去看,只知道是基于esbuildrollup实现的,就直接说不知道了。

你觉得Vite相比于Webpack哪些方面更好

  1. 本地开发的构建速度快,由于Vite是基于ESMesbuild的,所以在本地开发时整体的构建速度会比webpack更快

  2. 使用简单,Vite内置了很多loader和配置,让开发者可以零配置跑起来一个项目,而webpack则是需要写很多复杂的配置

Vite的热更新原理是什么

  1. Vite本地启动时会创建一个WebSocket连接,同时去监听本地的文件变化

  2. 当用户修改了本地的文件时,WebSocket的服务端会拿到变化的文件的ID或者其他标识,并推送给客户端

  3. 客户端获取到变化的文件信息之后,便去请求最新的文件并刷新页面

介绍一下之前的监控告警是怎么做的

分为两部分,第一部分为日志的埋点和上传,包含了代码日志和业务日志。主要是自己实现了一个基于WebSocket的日志服务,在客户端项目加载的时候启动WebSocket,然后通过提供的log方法在代码中去进行日志打点。服务端收到上传的日志之后传入到公司内的数仓,之后通过数仓的API实现日志的查询。
对于业务数据的埋点则是在对应的用户操作时进行埋点上报。

另一部分则是告警的实现,这部分利用公司的统一基础设施去做。在拿到前面的埋点信息之后,在公司的告警平台可以看到对应的埋点数据的趋势图,根据趋势可以设置告警阈值。告警阈值主要是通过人工去指定告警策略并根据实际情况进行调整和优化,以实现更准确的告警。

怎么去监控业务指标情况

根据业务流程中对应环节的埋点数据可以计算出业务指标,如提单成功率 = 支付成功的埋点数 / 购物车页点击下单的埋点数。获取到各个业务指标的计算公式之后,可以在告警平台去配置对应的趋势图,之后按照前面的方式设置监控和告警即可。

小程序性能优化做了哪些事

小程序本身是双线程架构,渲染层和逻辑层相互独立,通过微信原生中的JSBridge进行通信,因此性能的损耗主要花费在通信过程中。而在通信过程中影响性能的点最主要的则是setData频率数据量
所以主要做的优化手段就是去减少setData的次数,同时当setData数据量过大时对数据量进行拆分,分为多个setData去执行,从这两者中找到一个平衡。

除此之外,还利用了Wxml节点压缩,CSS样式合并,以及请求预加载等方式进行性能优化。

【代码题】实现一个React的useState hook


const useState = defaultValue => {
const value = useRef(defaultValue);

const setValue = newValue => {
if (typeof newValue === 'function') {
value.current = newValue(value.current);
} else {
value.current = value;
}
}

// 触发组件的重新渲染
dispatchAction();

return [value, setValue];
}

二面

总时长:60min

D2C做了哪些事情

主要是做了一个sketch插件,通过插件的开发语言cocoascript和其中的webview的方式来实现的。为了降低开发成本,插件面板内的所有内容都是在webview中去开发前端页面。当从面板中拖拽某个图标或组件出来时,通过message将组件的信息传递到插件原生种,之后通过cocoascript开发sketch文件的查找和放置画板的逻辑。

为了简单实现,我们在插件安装的时候内置了符合业务规范的sketch组件包,因此面板中的每个组件都有唯一标识与sketch组件一一对应。最后导出页面时同样是对画板中模板和组件进行遍历,找到对应的组件代码并进行组装。

小程序的日志和监控服务做了哪些事情

分为两部分,第一部分为日志的埋点和上传,包含了代码日志和业务日志。主要是自己实现了一个基于WebSocket的日志服务,在客户端项目加载的时候启动WebSocket,然后通过提供的log方法在代码中去进行日志打点。服务端收到上传的日志之后传入到公司内的数仓,之后通过数仓的API实现日志的查询。
对于业务数据的埋点则是在对应的用户操作时进行埋点上报。

另一部分则是告警的实现,这部分利用公司的统一基础设施去做。在拿到前面的埋点信息之后,在公司的告警平台可以看到对应的埋点数据的趋势图,根据趋势可以设置告警阈值。告警阈值主要是通过人工去指定告警策略并根据实际情况进行调整和优化,以实现更准确的告警。

作为前端负责人在技术上做了哪些基建

脚手架

  1. 提供前端脚手架工具,支持一行代码自动完成项目创建,同时调用gitlab的API完成远程仓库的创建,最后自动生成相应的CI/CD脚本实现自动化部署

  2. 将项目的配置项进行收敛,包含ESLintPrettierTSConfigVite的等,将标准的配置文件全部内置在脚手架当中,只提供部分配置项以单独的脚手架配置项透出

  3. 提供自动化命令,包含代码格式化、质量检测、本地开发、生产打包等

框架

  1. 前端框架部分主要是对一些公共模块和服务进行了单独的封装,包含请求模块、状态管理、路由等,所有的功能都由框架导出给开发者直接调用

  2. 提供了业务通用能力的封装,如PDF预览、统一图表展示、富文本编辑器等

组件

  1. 组件部分主要是基于Antd去做一些样式和改造以及更上层的组件封装

  2. 对常见的CRUD页面封装成模板,并提供JSON2Page的使用方式,以实现通过JSON配置直接生成页面

富文本编辑器是如何实现的

一开始是通过slate.js去实现的,slate.js提供了富文本编辑器的核心能力,基于这些核心能力我们可以在上层实现各种复杂的富文本编辑器。但是随着业务对富文本编辑器的功能需求越来越多,我们就需要在slate.js上投入更多的人力去开发这些新的功能需求,这显然在我们这样的小团队中是不太现实的。于是就考虑找一款功能完善的富文本编辑器,最后选择了jodit,一款基于TS实现的富文本编辑器,整体代码比较易读,便于后续对其进行二次开发。

有没有在质量和稳定性上做一些事情

包含两部分,一部分是开发过程中的质量保障,主要有两点一个是单元测试,通过Jest去实现,另一个则是UI的自动化测试,通过cypress实现。第二部分是对线上稳定性的监控,这个是基于开源项目加二次开发实现的。具体可以分为以下几个步骤:

  1. 异常捕获:对异常进行分类,不同分类通过不同的方式进行捕获,如addEventListener('error')监听JS代码异常,window.onerror监听资源加载异常,xhr.addEventListener('error')监听请求异常等

  2. 异常上报:可以通过WebSocket进行上报,或者直接通过HTTP请求上报,在上报的时候需要考虑到弱网的缓存,以及高并发的数据合并情况

  3. 数据接收:启动一个Node服务,在接收到异常数据之后直接存在数据库中即可

  4. 数据使用:对上传的异常和监控数据进行分类查询,并提供对应的监控告警机制,当超出阈值范围时发送告警邮件

【代码题】描述下列代码的输出结果和原因


// 最害怕这种题目了,总是想不明白,一开始说了结果是3,后来面试官让我再想想
// 仔细缕了一下后发现,fn() 里的函数声明虽然会提前,但不会提前到最外层,只会到 fn() 的顶层
// 里面的 foo = 3 修改的其实是里面函数的那个声明,不会影响到外部,所以最终的结果应该是 1

var foo = 1;
function fn() {
foo = 3;
return;
funciton foo() {
// todo
}
}
fn();
console.log(foo);

【代码题】按照版本号由小到大排序

样例输入:versions = ['0.1.1', '2.3.3', '0.302.1', '4.2', '4.3.5', '4.3.4.5']
输出:['0.1.1', '0.302.1', '2.3.3', '4.3.4.5', '4.3.5']


function compareVersions(versions) {
return versions.sort((a, b) => {
const tempA = a.split('.');
const tempB = b.split('.');
const maxLen = Math.max(tempA.length, tempB.length);
for (let i = 0; i < maxLen; i++ ) {
const valueA = +tempA[i] || 0;
const valueB = +tempB[i] || 0;
if (valueA === valueB) {
continue;
}
return valueA - valueB;
}
return 0;
});
}

三面

总时长:80min

三面的问题都还在预期之内,自己提前也做了一些准备,所以整体上回答的也还算系统和全面,最后的代码题不难,这种贴近实际业务的还是要比leetcode上的题要舒服一些,不过一开始考虑的不够全面,在面试官提醒之后做了补全。

三面的时间不短,唯一担心的点就是我的项目经历技术复杂度一般,可能会跟面试官的期望不符。

介绍一下业务做了哪些系统

先从现在公司的业务讲起,然后根据业务内容介绍了对应的系统,我觉得这个问题主要是表现出自己对业务的理解即可。

在以往的工作经历中有哪些最有成就感的项目

详细讲了一下之前做的监控和告警项目,并且如何通过技术手段来推导业务指标,并根据业务指标的监控数据来反推业务的发展。具体技术上的关键点做了详细介绍,但是整体复杂度并不高,主要还是从收益上看属于低成本高收益的项目。

当时为什么考虑现在的公司

从三个方面进行考虑:

  1. 在互联网行业待了一段时间,想要看看其他行业是什么样的,同时自己对金融还是比较感兴趣

  2. 大厂“打螺丝”不能充分发挥出自己的价值,想要到小公司去做更多的事情,让自己变得更全面

  3. 有一定的创业精神,选择了一个创业团队,可以收获一些创业的经验

现在为什么又要从现在的公司离开

有两个原因:

  1. 团队发展不看好:新换了一个老板,对团队的定位发生了变化,由创新的探索性团队变成了支持性的IT团队,跟自己的期望不符

  2. 个人发展受限:从技术上来看,产品用户量较少,且复杂度较低,技术上持续在做输出,但是输入很少;从管理上来看,团队规模趋于稳定,后续不会再有更多人让自己来带

对自己未来的发展规划是什么

也是可以从两个方面来讲,一个走技术路线,一个走管理路线。

技术路线:

  1. 对自身已经掌握的技术持续精进,并通过技术手段去回馈团队和业务(如前端架构、异常监控、性能优化、指标体系等)

  2. 在某一个技术方向上做到突出,能够沉淀出相应方法论,并建设出系统性的平台,在部门及公司内部普及应用

  3. 保持对新技术的热情,持续扩宽技术广度,对团队的技术栈持续迭代,保持团队整体技术的竞争力

管理路线:

  1. 定目标 —— 包含业务支撑、技术成长和团队培养三部分

  2. 看执行 —— 主要是对现有流程的问题进行梳理,让过程更加规范化、流程化,同时需要发掘高潜,给其空间快速成长

  3. 拿结果 —— 需要有可量化的具体数据来作为结果的衡量依据

【代码题】实现一个拼手气抢红包算法

提供了一个RedPackage的类,初始化时传入红包金额和个数,需要实现一个openRedPackage方法,每调一次都进行一次“抢红包”,并以console.log的形式输出抢到的红包金额。


class RedPackage {
money = 0;
count = 0;
_remain = 0;

constructor(money, count) {
this.money = money;
this.count = count;
this._remain = money;
}

openRedPackge() {
// 已经抢完了
if (this.count <= 0) {
console.log('红包已经被抢完啦~');
return;
}

// 只剩一个红包
if (this.count === 1) {
this.count--;
console.log(this._remain);
return;
}

const ratio = Math.random() * (this._remain / this.money);
// 这里会涉及到一个JS计算精度的问题
// 正常应该用第三方库或者字符串算法实现一个精准的加减乘除
// 这里为了简单就这么直接做了
let youGet = (this.money * ratio).toFixed(2);
const tempRemain = +(this._remain - youGet).toFixed(2);
const allLeast = this.count * 0.01;

// 如果剩余的金额不够每人一分钱,那么需要减少本次获得的金额
if (tempRemain < allLeast) {
youGet = +(this._remain - allLeast).toFixed(2);
this._remain = allLeast;
} else {
this._remain = tempRemain;
}
console.log(youGet);
this.count--;
}
}

换部门三面

总时长:60min

前面已经面完了三轮之后,在经过了一系列的调查和询问,最终决定还是换到新的部门来,由于前面三面都通过,因此过来之后直通的终面。虽然这一面聊的内容不是很hard,但还是能感觉出来面试官很资深,最后一个简单的代码题被我写的一塌糊涂,差点没写出来。今年写代码真是各种不在状态😅

聊项目和经历

这一轮没有问很多具体的前端技术,主要是在聊过往的项目以及工作经历,虽然聊的比较轻松,但是这种情况就有些没底,并不知道面试官那里的反馈是好或是不好。

【代码题】数字转字符串

样例输入:1234567890
样例输出:1,234,567,890


function toString(num) {
// 这是最简单的写法
// return num.toLocaleString();
const result = [];
const str = `${num}`.split('').reverse();
for (let i = 0; i < str.length; i++) {
if (i && i % 3 === 0) {
result.push(',');
}
result.push(str[i]);
}
return result.reverse().join('');
}

定级面

总时长:25min

三面聊完之后很快HR联系了我,告诉我通过了,但是职级上有些问题,需要我加一轮定级面,虽然很不太情愿,但是也只能答应。等到面试的时候还是挺出乎意料,面试官很直接,上来也没让自我介绍,直接让我开白板画一个架构图,然后就根据图进行讲解,之后很快就结束了。。。

画一个自己做过的最复杂项目的架构图

这个还挺出乎我意料,由于我这飞书是老版本,没有白板功能,所以后来就选择找了一个之前画过的前端架构图出来讲解。这部分就不多展开了,反正讲完我就感觉自己的内容好像很简单,没有让面试官满意😣

有想看这个架构图的可以在这里自取~ ProcessOn模板社区-前端架构图

如何理解业务的

这个问题跟前面第一次三面的第一个问题的回答差不多,都是先介绍公司做啥业务,然后再结合业务介绍对应的系统,最后讲一下每个系统的功能。


最后

还没有使用过我们刷题网站(https://fe.ecool.fun/)或者前端面试题宝典的同学,如果近期准备或者正在找工作,千万不要错过,我们的题库主打无广告和更新快哦~。

老规矩,也给我们团队的辅导服务打个广告。




原文链接:https://juejin.cn/post/7298218459795734582