抖音前端二面面经
概括
过去一年跳槽了两次,最近的一年都在面试,失败了很多次,也拿了很多offer,腾讯,字节,虾皮,滴滴都oc,最终人生的第三方份工作选择了腾讯,工作空闲之余,把最近一年的面经整理一下。
联系我wx:Chan-FE 可腾讯内推~
抖音二面面经
- 介绍项目亮点,负责的模块
- webpack升级过程做了哪些优化,提升了多少,具体的指标
- 三种哈希的使用区别是什么,为什么css使用contenthash
- webpack升级优化过程中踩了什么坑,或者说两个版本差异需要注意的地方
- 除了这些还有别的优化手段嘛,用到的没用到的都可以说一下
- webpack热更的原理
- websocket的原理
- TLS握手的过程
- 对前端安全方面的理解
- 简单请求和复杂请求
- 代码输出题
- 最接近的两数和
- 模拟网络请求,最多重试5次,初始等待为1s,每次时间乘以2
项目
1.介绍项目亮点,负责的模块
前端技术框架升级,webpack4升级webpack5
2.webpack升级过程做了哪些优化,提升了多少,具体的指标
- 压缩算法优化,主要说一下Terser和uglify这些常见算法的区别与压缩效果对比
- tree-skaing优化,tree-skaing引用副作用导致打包压缩得不够极致(副作用解释:例如在utils文件夹下面有index.js文件,用于系统导出utils里面其他文件,好处就是写的少, 不管 utils 里面有多少方法,我都只需要引入 utils 即可。这个时候,如果有一个函数A在外界没有用到,但是他在 utils/index.js 里面被引用了,使用了而 tree-shaking是不能它摇掉的,这个 A 就是副作用),这个时候通过配置sideEffects可将副作用摇掉。
- 长缓存机制,通过splitchunk抽离公共代码,固定moduleid和chunkid
- 打包命名优化,js使用chunkhash, css使用contenthash
- js相关的产物由7点多兆下降到5点多兆
3.三种哈希的使用区别是什么,为什么css使用contenthash
- 全局hash由全部的文件公共决定生成。
- chunkhash 根据不同的入口文件进行依赖文件解析、构建对应的 chunk,生成对应的哈希值。只要我们不改动同一个chunk的某一个文件的代码,就可以保证其哈希值不会受影响。
- contenthash只跟文件自身有关系。
- css文件hash使用contenthash,这样不受js模块变化影响,只要css不变化,都不用重新生成css 产物
- js使用chunkhash是因为js作为入口文件,对Css存在引用关系,当css发生变化的时候,css产物的命名也会变化,这个时候js要感知同一个chunk的css命名的变化,进行引入调整,所以只能使用chunkhash
4.webpack升级优化过程中踩了什么坑,或者说两个版本差异需要注意的地方
- 移除tree-shaking副作用的时候,会把css的引入也当初副作用给打掉,sideEffects要忽略样式文件
- webpack5内置了压缩器terser-webpack-plugin,可以免配置开箱即用,但是如果同时配置了其他压缩器,会导致terser的失效,这个时候也需要对terser进行相关的配置
- 内置了缓存模块,不再需要引入持久缓存的第三方插件/loader
- file-loader等变成了内置的assets-modules
5.除了这些还有别的优化手段嘛,用到的没用到的都可以说一下
- hybird长会话机制,在页面启动阶段就可以取容器上次的缓存数据先展示,后面再根据登录态做动态更新
- 切分chunk的时候不能切得太小也不能切得太大,要充分利用http的并行优势
- 首页主动推送功能,请求html的时候就把相关css js给推送过来
- ssr服务端渲染
八股
6.webpack热更的原理
- 启动webpack,生成compiler实例。compiler上有很多方法,比如可以启动 webpack 所有编译工作,以及监听本地文件的变化。
- 使用express框架启动本地server,让浏览器可以请求本地的静态资源。
- 本地server启动之后,再去启动websocket服务,通过websocket,可以建立本地服务和浏览器的双向通信。这样可以实现当本地文件发生变化,立马告知浏览器可以热更新代码
7.websocket的原理
全双工、二进制帧、握手(需要握手才能正式收发数据)
握手的过程为,客户端发送的请求中包含了 Upgrade: websocketConnection: Upgrade 以及一个 base 编码的密文,用于简单的认证密钥。服务器返回 Upgrade: websocketConnection: Upgrade 表示接受 webSocket 协议的客户端连接,返回一个密钥用于验证客户端请求报文,防止误连接。
8.TLS握手的过程
内容较多,可查看知乎文章分享:https://zhuanlan.zhihu.com/p/662069195
9.对前端安全方面的理解
xss、csrf、csp、指纹追踪、css安全键盘时间、爬虫与反爬以及业务做的反扒处理
10.简单请求和复杂请求
简单请求不会触发预检
请求方法是以下三种方法之一: HEAD GET POST
HTTP的头信息不超出以下几种字段: Accept:指定返回类型,: text/plain(纯文本类型), text/html(html类型) Accept-Language 指定返回语言 Content-Language 指定内容语言 Last-Event-ID Content-Type:只限于三个值 application/x-www-form-urlencoded:对发送内容进行编码 multipart/form-data:上传的表单内包含文件 text/plain:发送内容为纯文本格式
复杂请求会先发option预检查
算法
11.代码输出题
const p1 = new Promise((resolve, reject) => {
console.log('1');
resolve();
console.log('2');
})
p1.then(() => {
console.log('3');
Promise.resolve(() => {
console.log('4');
}).then((res) => {
console.log('5', res);
});
console.log('6');
})
console.log('7');
// 1 2 7 3 6 5
12.最接近的两数和
function testNear (arrNear: Array<number>, targetNear: number): number {
// 排序
arrNear.sort((a, b) => a - b);
let left = 0, right = arrNear.length - 1;
let res: number = 0;
while(left < right) {
// 这里的结束边界不能是left <= right
// 因为arrNear[left]和arrNear[right]需要是不同的元素
// 《首尾收缩遍历》
const temp = arrNear[left] + arrNear[right];
if(temp === targetNear) {
return res;
} else if (temp > targetNear) {
// 尾部收缩
right--;
} else {
// 头部收缩
left++;
}
// 更新最接近的值
res = Math.abs(targetNear - res) > Math.abs(targetNear - temp) ? temp : res;
}
return res;
}
// const arrNear = [24,69,14,37];
// const targetNear = 60;
// const nearP = testNear(arrNear, targetNear);
// console.log({ nearP }); // { nearP: 61 }
13.模拟网络请求,最多重试5次,初始等待为1s,每次时间乘以2
const timer = (time) => {
console.log({ time });
return new Promise((resolve) => {
setTimeout(resolve, time);
})
}
const test = async () => {
let index = 0;
let flag = false;
let time = 100;
while(index < 5 && !flag) {
await new Promise((resolve, reject) => {
const successFlag = Math.random() > 0.5;
if (successFlag) {
resolve(successFlag);
} else {
reject(successFlag);
}
}).then((successFlag) => {
flag = true;
console.log('请求成功', index, successFlag);
}).catch((successFlag) => {
console.log('请求失败', index, successFlag);
index++;
})
// 成功就不需要等待了
if (!flag) {
await timer(time);
time *= 2;
}
}
}
test()
// 请求失败 0 false
// { time: 100 }
// 请求成功 1 true
搞了个公主号《 FE前端指南 》,感兴趣的可以关注一下~
