抖音前端一面面经
概括
过去一年跳槽了两次,最近的一年都在面试,失败了很多次,也拿了很多offer,腾讯,字节,虾皮,滴滴都oc,最终人生的第三方份工作选择了腾讯,工作空闲之余,把最近一年的面经整理一下。
联系:Chan-FE 可腾讯内推~
抖音一面面经
- Taro升级
- 为什么要升级,两个技术版本的本质区别
- 升级遇到了什么问题
- 升级如何记录新框架的异常情况,如果自己实现一个监控系统,要怎么做
- 多端方案的区别和优劣势
- hybird架构里面h5与原生通信的手段
- 常见的优化手段中容器端的优化策略
- http123的区别
- 实现一个订阅发布者函数
- 数组转树
- lc198原题
项目
1.背景,升级过程(用了什么工具、原理是什么)
taro1兼容性比较差,公司业务需求需要拓展多平台。使用了jscodeshift进行代码自动化重构,其原理是将 JS/TS 代码解析为抽象语法树,并提供一系列用于访问和修改 AST 的 API 以实现自动化的代码重构。通过正则识别匹配抽象语法树的节点,将一些导入规则、配置项给替换、移除等。
2.为什么要升级,两个技术版本的本质区别
Taro 1/2 属于编译型架构,主要通过对类 React 代码进行语法编译转换地方式,得到各个端可以运行的代码,再配合非常轻量的运行时适配,以及根据标准组件库、API 进行差异抹平,从而实现多端适配的目的。Taro 3 可以大致理解为运行时或解释型架构,主要通过在小程序端模拟实现 DOM、BOM API 来让前端框架直接运行在小程序环境中,从而达到小程序和 H5 统一的目的。
3.升级遇到了什么问题
新框架兼容性较好,没遇到什么大的问题,主要一些流程规范性问题,比如新技术升级要循序渐进,先在个别渠道进行升级,做好边界处理,测试没问题再逐步开量推广。
4.升级如何记录新框架的异常情况,如果自己实现一个监控系统,要怎么做
基于ams的监控体系。如果自己实现一个监控系统,可以注重关注用户行为、页面性能、稳定性等几方面,且数据上报的时候要注意进行数据脱敏,有一些数据比如具体接口数据等,可以根据业务情况避免前后端重复上报。
八股
5.多端方案的区别和优劣势
- 原生app方案:开发成本高
- 自研渲染引擎的fluter方案: 开发成本较高,国内生态一般
- rn方案:依赖原生控件,通过js桥接,可能有性能损耗
- hybird架构:开发成本最低,基于WebView容器加载H5页面,通过桥接调用原生功能,但是渲染效率低,弱网环境体验差
6.hybird架构里面h5与原生通信的手段
native调用js:
- 通过webview的loadUrl
- 通过webview的evaluateJavascript
js调用native
- 通过webview的addjavascriptInterface注入api, 进行对象映射
- shouldOverrideUrlLoding方法拦截特定的协议调用
- 弹窗拦截
- 桥接,通过windows对象,基于发布订阅模式进行各种监听回调
7.常见的优化手段中容器端的优化策略
- 预热容器:提前准备下一个页面的容器
- 容器复用
- 容器层级做网络请求拦截和缓存
8.http123的区别
- 0.9 只有 GET 命令,没有 HEADER 等描述数据的信息
- 1.0 增加了很多命令,如 POST、PUT 等,增加了状态码和 HEADER 相关信息,增加了缓存
- 1.1 持久连接,增加了 pipeline,可以在同一个 TCP 连接里面发送多个 http 请求,但是串行的,增加了头部 host。
- 2.0 头部信息压缩、多路复用、二进制分帧、主动推送,但是还是存在tcp队头阻塞
- 3.0 基于udp,增加quic流帧的概念,解决tcp阻塞的痛点
算法
9.实现一个订阅发布者函数
class EventEmitter { constructor() { this.events = {}; } // 订阅事件 on(event, listener) { if (!this.events[event]) { this.events[event] = []; } this.events[event].push(listener); } // 取消订阅事件 off(event, listener) { if (!this.events[event]) return; this.events[event] = this.events[event].filter(l => l !== listener); } // 触发事件 trigger(event, ...args) { if (!this.events[event]) return; this.events[event].forEach(listener => listener(...args)); } // 订阅一次事件,触发后自动取消订阅 once(event, listener) { const onceListener = (...args) => { listener(...args); this.off(event, onceListener); }; this.on(event, onceListener); } } // 使用示例 const emitter = new EventEmitter(); // 订阅事件 emitter.on('hello', (name) => { console.log(`Hello, ${name}!`); }); // 订阅一次事件 emitter.once('goodbye', (name) => { console.log(`Goodbye, ${name}. You will not see this message again.`); }); // 触发事件 emitter.trigger('hello', 'Alice'); // 输出: Hello, Alice! emitter.trigger('hello', 'Bob'); // 输出: Hello, Bob! emitter.trigger('goodbye', 'Charlie'); // 输出: Goodbye, Charlie. You will not see this message again. emitter.trigger('goodbye', 'David'); // 不会输出任何内容
10.数组转树
function arrayToTreeV3(list, root) { return list .filter(item => item.parent_id === root) .map(item => ({ ...item, children: arrayToTreeV3(list, item.id) })) }
const arr = [ { "id": 12, "parentId": 1, "name": "朝阳区" }, { "id": 241, "parentId": 24, "name": "田林街道" }, { "id": 31, "parentId": 3, "name": "广州市" }, { "id": 13, "parentId": 1, "name": "昌平区" }, { "id": 2421, "parentId": 242, "name": "上海科技绿洲" }, { "id": 21, "parentId": 2, "name": "静安区" }, { "id": 242, "parentId": 24, "name": "漕河泾街道" }, { "id": 22, "parentId": 2, "name": "黄浦区" }, { "id": 11, "parentId": 1, "name": "顺义区" }, { "id": 2, "parentId": 0, "name": "上海市" }, { "id": 24, "parentId": 2, "name": "徐汇区" }, { "id": 1, "parentId": 0, "name": "北京市" }, { "id": 2422, "parentId": 242, "name": "漕河泾开发区" }, { "id": 32, "parentId": 3, "name": "深圳市" }, { "id": 33, "parentId": 3, "name": "东莞市" }, { "id": 3, "parentId": 0, "name": "广东省" } ]; // 调用 arrayToTree(arr, 0);
11.lc198原题
搞了个公主号《FE前端指南》,感兴趣的可以关注一下~
#字节##腾讯##前端面经##面经#