抖音前端二面面经
概括
过去一年跳槽了两次,最近的一年都在面试,失败了很多次,也拿了很多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前端指南 》,感兴趣的可以关注一下~