概括
过去一年跳槽了两次,最近的一年都在面试,失败了很多次,也拿了很多offer,腾讯,字节,虾皮,滴滴都oc,最终人生的第三方份工作选择了腾讯,工作空闲之余,把最近一年的面经整理一下。
联系我wx:Chan-FE 可腾讯内推~
抖音一面面经
- Taro升级
- 为什么要升级,两个技术版本的本质区别
- 升级遇到了什么问题
- 升级如何记录新框架的异常情况,如果自己实现一个监控系统,要怎么做
- 多端方案的区别和优劣势
- hybird架构里面h5与原生通信的手段
- 常见的优化手段中容器端的优化策略
- http123的区别
- 实现一个订阅发布者函数
- 数组转树
- lc198原题
项目
1.Taro升级
项目背景,升级过程(用了什么工具、原理是什么)
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前端指南 》,感兴趣的可以关注一下~