一面
关于项目:
常规问题:
webpack 打包细节
- commonjs 与 esmodule 是如何相互转换(这里不会)
Task 与 Microtask 的区别, 以及时序
详细说明可以见: https://html.spec.whatwg.org/multipage/webappapis.html#queuing-tasks
简单的说就是每次执行task
之前会把microtask
都处理掉。在处理过程中加入的microtask
也会按照顺序处理掉
闭包问题:
1 2 3 4 5
| for (var i = 0; i < 5; i++) { setTimeout(() => { console.log(i); }, 1000); }
|
结果是什么? (我回答了5个4
, 其实是5个5
..扶额)
然后问如何让其输出 0 1 2 3 4
解法一:
1 2 3 4 5 6 7
| for (var i = 0; i < 5; i++) { (function (_i) { setTimeout(() => { console.log(_i); }, 1000); })(i); }
|
解法二:
1 2 3 4 5
| for (let i = 0; i < 5; i++) { setTimeout(() => { console.log(i); }, 1000); }
|
我没有说出解法二, 想了半天想出了一个解法三。。
1 2 3 4 5 6 7
| for (var i = 0; i < 5; i++) { Promise.resolve(i).then((i) => { setTimeout(() => { console.log(i); }, 1000); }); }
|
中文数字转阿拉伯数字
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
|
const chineseNumMap = { 一: 1, 二: 2, 三: 3, 四: 4, 五: 5, 六: 6, 七: 7, 八: 8, 九: 9, };
const unitMap = { 千: 1000, 百: 100, 十: 10, };
function parseChineseNumWhenLowerThan10K(chineseNum: string) { let res = 0; let hasNum = false; let currentNum = 0; for (const s of chineseNum) { if (hasNum === false && chineseNumMap[s]) { res += chineseNumMap[s]; hasNum = true; currentNum = chineseNumMap[s]; } else if (hasNum === true && unitMap[s]) { res -= currentNum; res += currentNum * unitMap[s]; hasNum = false; currentNum = 0; } }
return res; }
function chineseNumToInt(input: string): number { const chineseNumArr = input.split("万"); let res = 0;
res += parseChineseNumWhenLowerThan10K(chineseNumArr[0]);
if (typeof chineseNumArr[1] === "string") { res *= 10000; res += parseChineseNumWhenLowerThan10K(chineseNumArr[1]); }
return res; }
console.log(chineseNumToInt("五千三百万零五百零一"));
|
二面
一面过后第二天 HR 就打电话过来约二面,就效率方面来说还是非常迅速的
二面没有考察具体代码,主要是问了问项目方面的问题。
比如公司项目的架构,公司团队协作方面是如何协作的,除了平时工作之外有没有做什么其他的事情,有自己的项目么,自己的项目由什么亮点难点这种比较抽象的问题。
其中可能也有混入价值观方面的问题。总之需要注意一下。
然后我这面被刷了,第三天收到了感谢面试的信。至少有个反馈,从这点来看字节做的还是非常不错的。
再战
又被别的部门捞起来了, 之前是飞书, 现在是抖音部门, 就再战一次呗。
一面
- 聊聊项目
- esmodule 和 commonjs 的区别
- webpack 打包原理
- nodejs 的 event loop
- nodejs 的使用 microtask 和 task 的时机
算法题
路径总和:给你二叉树的根节点 root 和一个表示目标和的整数 targetSum ,判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| class TreeNode { left: TreeNode | null = null; right: TreeNode | null = null; value: number; }
function getTreePathSum(node: TreeNode, targetSum: number): boolean { if (!node) { return false; }
if (targetSum - node.value === 0) { if (node.left === null && node.right === null) { return true; }
}
return ( getTreePathSum(node.left, targetSum - node.value) || getTreePathSum(node.right, targetSum - node.value) ); }
const root = new TreeNode(); root.value = 1; const left = new TreeNode(); left.value = 2; const right = new TreeNode(); right.value = 3;
root.left = left; root.right = right;
console.log(getTreePathSum(root, 5));
|
二面
- js有哪些类型
number
, boolean
, string
, object
, undefined
, symbol
- 以上是我的回答, 是错误答案,正确答案应该是
string
, number
, bigint
, boolean
, undefined
, symbol
, null
. (Reference: https://developer.mozilla.org/en-US/docs/Glossary/Primitive)
- js中基本类型和引用类型分别存在哪里
- 如何获得地址输入 - 缓存 - 加载模块 - 渲染 - 加载请求 - 渲染可交互 这个过程中各个区域的时延
- 如何设计一个全链路的监控体系
我的回答:1 2 3 4 5 6 7 8 9 10
| 1. 监听所有的用户事件 并区分重点事件,和普通事件。 2. 全局捕获错误信息。并将错误信息上报,以及错误信息所在的堆栈信息。 如果可能的话,同时发送相关的dom接口与网络请求,以及截屏。 如果是无法复现的一些线上问题,内置一个web录屏工具,在开启一个flag后录屏并将录屏信息上传(rrweb) 3. 对用户的操作,比如RUM,等关键信息,以及用户的请求。划分不同的measure标准。并通过这些信息来衡量用户的痛苦程度。 资源是有限的,我们应当优先处理用户最痛苦的部分——即使用频率最高,相对痛苦程度最高的部分。 4. 增加有效的反馈机制,比如聊天机器人,比如报出异常时弹出反馈错误的模态框。或者引导用户到相关的社区。来尽可能让用户知晓我们会尽快解决问题。 同时能够收集来自用户的直接反馈,而这些是看log很难看到的信息。 5. 对线上服务进行监控。使用监控工具来检测各个关键服务,以及相对独立的服务的可访问性。以及相关的报警措施(比如钉钉消息)
|
- React Native 的热更新 / 部署
- 长列表 VirturalList
- React Native的事件推送
算法题
二面算法题难度急速升高, 反正我都没做过。
1 2 3 4 5
| 用 Javascript 对象模拟 DOM 树,并实现它的 render 方法,通过调用该方法可以转成真正的 DOM 节点,例如我们已经实现了 element.js,通过 require('element.js'),我们可以定义 ul, 如下:
function el(tag, props, children) { return ... }
|
我的算法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function el(tag, props, children) { const el = document.createElement(tag); for(let key in props) { el.setAttrible(key, props[key]) }
const childrenEls = children.map(item => { if(typeof item === 'string') { return item; }
return el(item.tag, item.props, item.children); }) el.append(...childrenEls)
return el; }
|
1 2 3 4 5 6 7 8 9
| 给你一副类似于如下的地图: 0000000000 0010001011 000010E001 00S0000100 0000001000 1000000000
初始时你在S的位置, 你可以向上下左右四个方向发射一枚子弹
|
我的算法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| const land = [ '0000000000', '0010001011', '000010E001', '00S0000100', '0000001000', '1000000000' ].map((str) => { return str.split('') });
function findS(target: string): [number, number] { for(let i = 0; i < land.length; i++) { for(let j = 0; j < land[i].length; j++) { if(land[i][j] === target) { return [i, j] } } } }
function getLandSymbol(y: number, x: number): '0' | '1' | 'S' | 'E' | undefined { return land[y][x] as any; }
const size = [land.length, land[0].length]; const sPos = findS('S');
function tryPath(step: number, currentPos: [number,number], vector: [number, number]): number { step = step + 1; const symbol = getLandSymbol(currentPos[0], currentPos[1]); if(symbol === '1') { vector = [vector[1], -vector[0]] }else if(symbol === 'E') { return step; }else if(symbol === undefined) { return 999999; }
return tryPath(step, [currentPos[0] + vector[0], currentPos[1] + vector[1]], vector); } console.log(tryPath(0, sPos, [-1, 0]));
|
三面
面试官上来就说,之前面了很多技术问题,我们来聊一聊项目吧。
大概问了问项目经历,工作经历,职业规划,如何协作,如何codereview,最近在学什么新技术等等看上去很随意的问题。
回头跟朋友们复盘了一下,其实是一道情商题,只有情商高的人才能答对,而我情商。。不高,就是一个憨憨。
如何面对这种软刀子题:
记住以下几点:
- 面试不是跟你谈心,作为候选人得揣测对方想听啥。
- 可以先说下自己有啥优点,经验。能为公司,部门,要做的事情带来什么转机。然后再夸一把公司,部门。说来虚心学习。
- 学会包装自己,技术是为业务服务的
三战
又换了个团队继续,直接从二面开始
二面
- 项目经历。项目是如何打包,如何发布的。
- 有使用过一些自动化工具来确保项目质量么。
- 有了解过
webpack
打包原理么?你们的项目是用什么打包的
- 了解过
lock
文件么,升级单个依赖时会遇到什么坑
- 看你的项目用到过
cypress
, 你是如何解决cypress
的下载问题的
题:
1 2 3 4 5 6 7 8
| 设计一个组件,用 React 编写,写出伪代码,有三个要求
Input 组件 非受控 Validate: 1. 只接受英文字母
2. 如果出现不合法的字符,就删掉
|
我的解法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| import React, {useState, useCallback} from 'react';
interface Props { initVal: string; onChange: (newVal: string) => void; } const MyInput: React.FC<Props> = React.memo((props) => { const [val, setVal] = useState(props.initVal ?? ''); const handleChange = useCallback((e) => { const text = e.target.value; const newVal = ''; for(let c of text) { if(/[a-zA-Z]/.test(c)) { newVal += c } }
if(typeof props.onChange === 'function') { props.onChange(newVal); }
setVal(newVal); }, [props.onChange])
return <input value={val} onChange={handleChange} /> }) MyInput.displayName = 'MyInput'
|
其实还有就是使用jquery
时代的代码来进行处理,然后外面包一层react代码
但是我觉得不够react
因此没有选择这个方案。
三面
杂七杂八的聊了聊,大部分都忘了,就记录一下记得的
- 讲讲你的开源项目
- 你的开源项目是怎么宣传的,取得了什么样的milestone
- 你的开源项目未来的发展是怎么样的
- 你公司的产品的基础架构是怎么样的
- 你对未来三年的职业规划是怎么样的
- 你希望做什么样的产品,是to b的还是to develop的
- 你如何实现一个需求,在用户不知道自己想要什么样的功能的情况下
- 你现在是965, 可以接受996么
一道题:
1 2 3 4 5 6 7 8 9
| 实现 tom().eat('apple').play('football').sleep(5).eat('apple').play('football')
输出: // tom // eat apple // play football // 停顿 5 s // eat apple // play football
|
我的写法, 其实不对。主要是一个sleep函数不太会写。求大佬给出正解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| function tom() { return new Tom() }
function Tom() { this.p = Promise.resolve() console.log('tom') }
Tom.prototype.eat = function(name: string) { this.p.then(() => { console.log('eat ' + name); })
return this; }
Tom.prototype.play = function(name: string) { this.p.then(() => { console.log('play ' + name); })
return this; }
Tom.prototype.sleep = function(interval: number) { this.p.then(() => { return new Promise(resolve => { setTimeout(() => { resolve(1); }, interval * 1000) }) })
return this; }
|
四战
直接三面
- 聊了聊项目
- 怎么做工程化
- 如何监控前端的性能
- 我们会做很多页面,如何提升开发体验
- 了解无头浏览器么?它内部的引擎是如何去计算代码的
题:
实现一个LRU:
我的回答:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| class LRU { list: string[] = []; map: Record<string, unknown> = {};
get(key: string): unknown { const index = this.list.findIndex((item) => key === item); if (index >= 0) { this.list.splice(index, 1); } this.list.unshift(key);
return this.map[key]; }
set(key: string, value: unknown): void { const index = this.list.findIndex((item) => key === item); if(index >= 0) { this.list.splice(index, 1); } this.list.unshift(key);
if(this.list.length > 1000) { const _key = this.list.pop(); delete this.map[_key]; }
this.map[key] = value; } }
|
我的不是一个优解。
建议正确的答案参考:
五战
又被教育部门捞起来了,反正我来者不拒继续面呗。
一面
- 一个空数组的原型链是怎么样的
- 用过flex么? 讲讲flex, 有那些熟悉,干嘛用的
- webpack_require esmodule commonjs, 他们的区别是什么。esmodule可以通过拼字符串来动态加载么
- function的构造函数与class的构造函数有什么区别
- 讲讲HTTP2解决了什么问题
- 二进制传输
- Header 压缩
- 多路复用
- server push
- TLS(虽然HTTP2不强制, 但是chrome, firefox 只支持tls的HTTP2)
- 讲讲箭头函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| inner = 'window';
function say() { console.log(inner); console.log(this.inner); }
var obj1 = (function() { var inner = '1-1'; return { inner: '1-2', say: function() { console.log(inner); console.log(this.inner); } } })();
var obj2 = (function() { var inner = '2-1'; return { inner: '2-2', say: () => { console.log(inner); console.log(this.inner); } } })();
say(); obj1.say(); obj2.say(); obj1.say = say; obj1.say(); obj1.say = obj2.say; obj1.say();
|
做算法题:
实现16进制加法,不能将两数直接转成十进制相加再转回十六进制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| var map = { a: 10, b: 11, c: 12, d: 13, e: 14, f: 15 }
function parseToNumber(hex) { if(hex === undefined) { return 0; }
if(Object.keys(map).includes(hex)) { return map[hex] }else { return Number(hex) } }
function hexAdd(hex1, hex2) { const arr1 = hex1.split('') const arr2 = hex2.split('')
let addon = 0; const result = []; for(let i = 0; i < Math.max(arr1.length, arr2.length) + 1; i++) { const num1 = parseToNumber(arr1[arr1.length - 1 - i]) const num2 = parseToNumber(arr2[arr2.length - 1 - i])
let sum = num1 + num2 + addon; addon = Math.floor(sum / 16); let rest = sum % 16; let r = String(rest) if(rest >= 10) { const [hex] = Object.entries(map).find(([key, val]) => { return val === rest })
r = hex }
result.unshift(r) }
return result.join('') }
console.log(hexAdd('ff', '1'))
|
结果
最后还是没有成功拿到offer, 很遗憾,原因是【不太适合团队协作】(当然也有可能只是单纯的敷衍)。面试者除了提升自己的硬实力,还应当提升一下自己在职场的软实力,比如朋友推荐的这本书 软技能
六战
被飞书部门又拉起来了,继续
一面
- 聊一下项目
- 聊一下你对前端的优化的实践,达成了那些目的
- 聊一下在前端体验方面做的工作
- 聊聊vue和react的区别
- 聊聊如何处理中文输入法在输入过程中定时保存状态的问题
算法题
- 用css实现一个布局, 来实现一个3x3的布局,每个窗口都要实现16比9的比例
没做出来,关键词 padding-bottom
百分比
实现: https://blog.csdn.net/weixin_39357177/article/details/81186498
- 实现一个方法,通过栈来存储,实现O(1)的复杂度下获取栈的最小值。
没做出来
框架
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class MiniStack { store = [];
push(val: number) { }
pop() { }
getMini(): number { } }
|
原题: https://leetcode-cn.com/problems/min-stack/
二面
- 聊聊公司的项目实现
- 了解http3么
- 聊聊react hooks
- 聊聊公司的业务规范与组件设计规范
- 聊聊业务代码
- 聊聊你是如何进行项目优化的
- 聊聊项目上线的流程
- 聊聊网络安全性方面的问题,做过xss用户输入防护么
- 聊聊你是如何关注用户的体验的,主要关心那些指标
- 让你设计一个倒计时组件,你会怎么设计
做题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
|
function fixStr(str) { let result = str;
function loop(str) { const len = str.length const fixedStr1 = str.replace('y', ''); if(len !== fixedStr1.length) { loop(fixedStr1) return; }
const fixedStr2 = str.replace('xz', ''); if(len !== fixedStr2.length) { loop(fixedStr2); return }
result = str; }
loop(str)
return result } console.log(fixStr('xxyyz')) console.log(fixStr('xxxyyyzzz')) console.log(fixStr('xyzwzyx'))
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
function renderErrorPage() { }
function renderSuccessPage(resultMap) { }
const arr = ['a', 'b', 'c', 'd', 'e']; const successedResultMap = {};
let errorCount = 0;
Promise.all( arr.map((url, i) => fetch(url) .then((res) => { successedResultMap[url] = res; }) .catch(() => { errorCount++; }) ) ).finally(() => { if (errorCount >= 4) { renderErrorPage(); } else { renderSuccessPage(successedResultMap); } });
|
该问题可以使用Promise.allSettled
进行简化
三面
问的和二面差不多,只是更加细一点
算法题在面试官的要求下不做公开
HR面
反正基本不挂人,主要是谈薪的问题。有一个比较难受的点就是字节的薪资是基于你上家薪资来的,或者说大部分大厂的薪资都是基于上家来的。所以比较好的做法是可以先去别的厂然后再来字节996。我就是基础薪资比较低的那种,最后也没感觉有太大的竞争力,比较菜。
结论
万万没想到,我最终还是拿到了Offer。虽然经历比较坎坷,但总不算完全浪费时间。总得来说跳槽这事三分靠运气七分靠实力,但绝对不要被眼前的利益限制住了自己的发展,主要还是要看这次跳槽在自己的职业生涯中能收获什么,而不要为跳而跳。