前言
大家好,我是木斯佳。
在这个春节假期,当大家都在谈论返乡、团圆与休息时,作为一名技术人,我的思考却不由自主地转向了行业的「冬」与「春」。
相信很多人都感受到了,在AI浪潮的席卷之下,前端领域的门槛在变高,纯粹的"增删改查"岗位正在肉眼可见地减少。曾经热闹非凡的面经分享,如今也沉寂了许多。但我们都知道,市场的潮水退去,留下的才是真正在踏实准备、努力沉淀的人。学习的需求,从未消失,只是变得更加务实和深入。
正值春节,也是复盘与规划的好时机。结合CSDN这次「春节代码贺新年」活动所提倡的"用技术视角记录春节、复盘成长",我决定在这个假期持续更新专栏,帮助年后参加春招的同学。
这个专栏的初衷很简单:拒绝过时的、流水线式的PDF引流贴,专注于收集和整理当下最新、最真实的前端面试资料。
我会在每一份面经和八股文的基础上,尝试从面试官的角度去拆解问题背后的逻辑,而不仅仅是提供一份静态的背诵答案。无论你是校招还是社招,目标是中大厂还是新兴团队,只要是真实发生、有价值的面试经历,我都会在这个专栏里为你沉淀下来。
温馨提示:市面上的面经鱼龙混杂,甄别真伪、把握时效,是我们对抗内卷最有效的武器。
在这个假期,让我们一起充电,为下一个技术春天做好准备。

面经原文内容
📍面试公司:字节
🕐面试时间: 2 月 1 日
💻面试岗位:前端
❓面试问题:
- 1.自我介绍
- 2.平时如何学习前端
- 3.实验室的指导
- 4.用户在浏览器地址栏输入一万到页面展示发生了什么(http,浏览器渲染逻辑)
- 5.异步代码输出(set time out,async)
- 6.js事件循环
- 7.css垂直居中
- 8.position垂直居中具体做法
- 9.vue响应式原理(vue2.3)
- 10.工作中遇到的设计模式
- 11.选择器优先级
- 12.git代码提交冲突怎么解决
- 13.项目:拖拽功能实现,图片懒加载(什么情况下加载什么情况下不加载)
- 14.项目中最有技术挑战的是什么
-
- (1)实现new操作符
(2)列表到数的数据变形
- (1)实现new操作符
来源: 牛客网 温柔的海螺ssp到手了
📝 字节前端一面·面经深度解析
🎯 面试整体画像
| 维度 | 特征 |
|---|---|
| 部门定位 | 字节跳动(未明确部门) |
| 面试风格 | 广度覆盖型 |
| 难度评级 | ⭐⭐(两星,覆盖面广都是常见八股文) |
| 考察重心 | 网络原理、事件循环、Vue原理、设计模式、Git协作、手写实现 |
🌐 从输入URL到页面展示·完整流程
问题:用户在浏览器地址栏输入URL到页面展示发生了什么
✅ 参考复习知识:
第一阶段:网络请求
javascript
// 1. URL解析
- 协议(http/https)
- 域名(www.example.com)
- 端口(默认80/443)
- 路径(/path/to/resource)
- 参数(?key=value)
- 哈希(#section)
// 2. DNS解析
- 浏览器缓存 → 操作系统缓存 → 本地hosts文件 → 路由器缓存 → ISP DNS → 根域名服务器
- 递归查询 vs 迭代查询
- DNS预解析:<link rel="dns-prefetch" href="//example.com">
// 3. TCP连接(三次握手)
客户端 ──SYN──> 服务端
客户端 <─SYN+ACK─ 服务端
客户端 ──ACK──> 服务端
// 4. TLS握手(HTTPS)
- Client Hello(支持的加密套件)
- Server Hello(选择加密套件 + 证书)
- 客户端验证证书
- 密钥交换(RSA/ECDHE)
- 生成会话密钥
- 加密通信开始
// 5. HTTP请求
- 请求行(GET /path HTTP/1.1)
- 请求头(Host、User-Agent、Accept等)
- 请求体(POST数据)
// 6. 服务器处理
- 负载均衡
- 反向代理(Nginx)
- 应用服务器处理
- 数据库查询
- 生成响应
// 7. HTTP响应
- 状态码(200、304、404等)
- 响应头(Content-Type、Cache-Control等)
- 响应体(HTML/JSON等)
// 8. TCP四次挥手
客户端 ──FIN──> 服务端
客户端 <─ACK─── 服务端
客户端 <─FIN─── 服务端
客户端 ──ACK──> 服务端
第二阶段:浏览器渲染
javascript
// 1. 解析HTML生成DOM树
HTML字符串 → 词法分析 → 语法分析 → DOM树
// 2. 解析CSS生成CSSOM树
CSS字符串 → 词法分析 → 语法分析 → CSSOM树
// 3. 合并生成渲染树(Render Tree)
DOM树 + CSSOM树 → 去除不可见元素(head、display:none)→ 渲染树
// 4. 布局(Layout/Reflow)
计算每个元素在视口中的确切位置和大小
- 盒模型计算
- 相对单位转换(rem、vw等)
- 浮动、定位处理
// 5. 绘制(Paint)
将元素绘制到屏幕上
- 绘制背景、边框、文字、阴影等
- 分层绘制(图层独立)
// 6. 合成(Composite)
将不同图层合成最终图像
- GPU加速(transform、opacity)
- 避免重绘重排
第三阶段:特殊处理
javascript
// 优化点
1. 预加载扫描器(Preload Scanner)
- 在构建DOM树的同时,提前加载资源
2. 渲染阻塞
- CSS阻塞渲染(CSSOM未构建完成)
- JS阻塞解析(async/defer优化)
3. 关键渲染路径
- 首屏渲染优化
- 避免长时间JS执行
💡 面试官考察点:
这个问题不是让你背流程,而是看你对Web技术栈的整体理解
能讲出细节(如TLS握手、渲染树构建) → 加分
能讲出优化点 → 加分
🔄 异步代码输出·事件循环
问题:异步代码输出 + JS事件循环
✅ 完整解析:
1. 典型考题
javascript
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(() => {
console.log('setTimeout');
}, 0);
async1();
new Promise((resolve) => {
console.log('promise1');
resolve();
}).then(() => {
console.log('promise2');
});
console.log('script end');
// 输出顺序:
// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// setTimeout
2. 关键点解析
javascript
// 为什么await后面的代码在promise2之前?
- await async2() 相当于 Promise.resolve(async2()).then(() => { console.log('async1 end') })
- 但这里有一个微任务队列的微妙顺序
// 更精确的理解
async function test() {
console.log('1');
await 1; // 相当于 Promise.resolve(1).then()
console.log('2');
}
// 等价于
function test() {
console.log('1');
return Promise.resolve(1).then(() => {
console.log('2');
});
}
3. 事件循环机制
javascript
// 完整流程
1. 执行当前宏任务(同步代码)
2. 执行所有微任务(清空微任务队列)
3. 尝试渲染(requestAnimationFrame)
4. 取下一个宏任务
// 宏任务与微任务
宏任务队列: [setTimeout, setInterval, I/O, UI渲染]
微任务队列: [Promise.then, MutationObserver, queueMicrotask]
// 特殊说明
- 微任务中产生的微任务,会在当前宏任务结束前执行(不是下一轮)
- requestAnimationFrame 在渲染前执行
- requestIdleCallback 在空闲时执行
🎨 CSS垂直居中·全方案总结
问题:CSS垂直居中 + position垂直居中具体做法
✅ 完整方案:
1. Flexbox方案(推荐)
css
.container {
display: flex;
align-items: center; /* 垂直居中 */
justify-content: center; /* 水平居中(可选) */
height: 300px;
}
2. Grid方案
css
.container {
display: grid;
place-items: center; /* 同时垂直水平居中 */
height: 300px;
}
3. Position + Transform(最常考)
css
.container {
position: relative;
height: 300px;
}
.child {
position: absolute;
top: 50%;
transform: translateY(-50%); /* 向上移动自身高度的一半 */
/* 水平居中 */
left: 50%;
transform: translate(-50%, -50%);
}
4. Position + Margin(需知宽高)
css
.child {
position: absolute;
top: 50%;
left: 50%;
width: 200px;
height: 100px;
margin-top: -50px; /* 高度的一半 */
margin-left: -100px; /* 宽度的一半 */
}
5. Line-height(单行文本)
css
.container {
height: 300px;
line-height: 300px; /* 与高度相等 */
text-align: center;
}
6. Table-cell
css
.container {
display: table-cell;
vertical-align: middle;
text-align: center;
height: 300px;
}
7. 伪元素法
css
.container::before {
content: '';
display: inline-block;
height: 100%;
vertical-align: middle;
}
.child {
display: inline-block;
vertical-align: middle;
}
💡 面试官追问:
position垂直居中为什么用translateY(-50%)而不是margin-top?
css
- translateY(-50%) 相对于元素自身高度,不需要知道具体数值
- margin-top 相对于父容器宽度(怪异),且需要知道高度
- translate 触发GPU加速,性能更好
⚛️ Vue响应式原理·Vue2 vs Vue3
问题:vue响应式原理(vue2.3)
✅ 完整对比:
Vue2响应式(Object.defineProperty)
javascript
// 核心实现
function defineReactive(obj, key, val) {
// 递归处理嵌套对象
observe(val);
const dep = new Dep(); // 依赖收集器
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
// 依赖收集
if (Dep.target) {
dep.depend();
}
return val;
},
set(newVal) {
if (newVal === val) return;
val = newVal;
// 对新值进行响应式处理
observe(newVal);
// 触发更新
dep.notify();
}
});
}
// 数组处理(需要hack)
const arrayMethods = Object.create(Array.prototype);
['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(method => {
const original = arrayMethods[method];
arrayMethods[method] = function(...args) {
const result = original.apply(this, args);
const ob = this.__ob__;
// 触发更新
ob.dep.notify();
return result;
};
});
Vue2的局限性
javascript
// 1. 无法检测属性新增/删除
Vue.set(obj, 'newProp', value); // 必须用Vue.set
Vue.delete(obj, 'prop'); // 必须用Vue.delete
// 2. 无法直接监听数组索引变化
this.arr[0] = 1; // 不会触发更新
this.arr.splice(0, 1, 1); // 必须用splice
// 3. 初始化时递归遍历所有属性,性能开销
Vue3响应式(Proxy)
javascript
function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
// 依赖收集
track(target, key);
const value = Reflect.get(target, key, receiver);
// 懒响应式:只有访问时才处理嵌套对象
if (typeof value === 'object' && value !== null) {
return reactive(value);
}
return value;
},
set(target, key, value, receiver) {
const oldValue = target[key];
const result = Reflect.set(target, key, value, receiver);
if (oldValue !== value) {
// 触发更新
trigger(target, key);
}
return result;
},
deleteProperty(target, key) {
const hadKey = Reflect.has(target, key);
const result = Reflect.deleteProperty(target, key);
if (hadKey) {
trigger(target, key);
}
return result;
}
});
}
Vue3的优势
javascript
// 1. 可以监听新增/删除属性
const obj = reactive({});
obj.newProp = 'value'; // ✅ 响应式
// 2. 可以监听数组索引变化
const arr = reactive([1, 2, 3]);
arr[0] = 10; // ✅ 触发更新
// 3. 懒响应式,性能更好
// 只有访问嵌套对象时才进行响应式处理
// 4. 支持Map、Set等
const map = reactive(new Map());
map.set('key', 'value'); // ✅ 响应式
🏗️ 设计模式·前端常见模式
问题:工作中遇到的设计模式
✅ 前端常用设计模式:
1. 单例模式(Singleton)
javascript
// 场景:全局状态管理、全局弹窗
class Store {
constructor() {
if (Store.instance) {
return Store.instance;
}
this.state = {};
Store.instance = this;
}
}
// Vuex/Pinia 本质就是单例
const store = createPinia();
2. 观察者模式(Observer)
javascript
// 场景:事件总线、Vue响应式
class EventBus {
constructor() {
this.events = {};
}
on(event, callback) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
}
emit(event, data) {
if (this.events[event]) {
this.events[event].forEach(cb => cb(data));
}
}
}
// Vue的$on/$emit 就是观察者模式
3. 工厂模式(Factory)
javascript
// 场景:创建不同类型的组件
class ButtonFactory {
createButton(type) {
switch(type) {
case 'primary':
return new PrimaryButton();
case 'danger':
return new DangerButton();
default:
return new DefaultButton();
}
}
}
4. 策略模式(Strategy)
javascript
// 场景:表单验证、动画算法
const validators = {
required: (value) => !!value,
email: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
minLength: (value, length) => value.length >= length
};
function validate(value, rules) {
for (const rule of rules) {
const [name, ...args] = rule.split(':');
if (!validators[name](value, ...args)) {
return false;
}
}
return true;
}
5. 装饰器模式(Decorator)
javascript
// 场景:高阶组件、AOP
function withLogging(Component) {
return (props) => {
useEffect(() => {
console.log('组件渲染', Component.name);
});
return <Component {...props} />;
};
}
// 使用
const LoggedButton = withLogging(Button);
6. 代理模式(Proxy)
javascript
// 场景:图片懒加载、缓存代理
const imageProxy = (src) => {
const img = new Image();
const proxy = new Proxy(img, {
set(target, key, value) {
if (key === 'src') {
// 显示占位图
target.src = 'placeholder.jpg';
// 真正加载
setTimeout(() => {
target.src = value;
}, 100);
}
return true;
}
});
proxy.src = src;
return proxy;
};
🎯 CSS选择器优先级
问题:选择器优先级
✅ 完整规则:
1. 优先级计算(权重)
css
/* 权重计算规则 */
!important > 内联样式 > ID选择器 > 类选择器/属性/伪类 > 元素/伪元素 > 通配符
/* 具体数值(256进制) */
- 内联样式:1000
- ID选择器:0100
- 类/属性/伪类:0010
- 元素/伪元素:0001
- 通配符(*):0000
- 继承:无权重
/* 示例 */
#nav .list li a:hover /* 权重:0100 + 0010 + 0001 + 0001 + 0010 = 0122 */
2. 特殊情况
css
/* !important 最高优先级 */
.color {
color: blue !important; /* 最高 */
}
/* 相同权重,后面的覆盖前面的 */
.title { color: red; }
.title { color: blue; } /* 生效 */
/* 继承的样式优先级最低 */
.parent {
color: red;
}
.child {
/* 继承的color优先级为0,会被任何直接样式覆盖 */
}
3. 实用技巧
css
/* 增加优先级的方法 */
- 重复选择器:.title.title { color: red; } /* 权重加倍 */
- 添加父选择器:.container .title
- 使用id:不要滥用
- !important:最后手段
🔧 Git代码冲突解决
问题:git代码提交冲突怎么解决
✅ 完整流程:
1. 冲突产生的原因
bash
# 两人修改同一文件的同一区域
A修改了main.js的第10行并push
B也修改了main.js的第10行,然后pull时产生冲突
2. 解决步骤
bash
# 1. 拉取最新代码
git pull origin main
# 2. 看到冲突提示
# Automatic merge failed; fix conflicts and then commit the result.
# 3. 查看冲突文件
git status
# both modified: src/main.js
# 4. 打开冲突文件
<<<<<<< HEAD
当前分支的修改
=======
合并进来的修改
>>>>>>> feature-branch
# 5. 手动解决冲突
# 选择保留哪个,或者合并两者
# 删除冲突标记 <<< === >>>
# 6. 标记为已解决
git add src/main.js
# 7. 完成合并
git commit -m "解决冲突"
# 8. 推送
git push
3. 更优雅的解决方式
bash
# 使用merge工具
git mergetool
# 使用rebase避免多余合并记录
git pull --rebase
# 然后逐个解决冲突
git add .
git rebase --continue
# 如果解决过程中想放弃
git merge --abort
# 或
git rebase --abort
4. 预防冲突的策略
javascript
// 1. 频繁pull
git pull --rebase 每天上班第一件事
// 2. 小粒度提交
不要攒一周的代码才提交
// 3. 明确分工
避免多人同时修改同一文件
// 4. 代码审查
PR/MR时及早发现潜在冲突
🖱️ 拖拽功能实现
问题:项目中的拖拽功能实现
✅ 完整实现:
1. 原生拖拽API
javascript
// HTML5 Drag and Drop
<div draggable="true" ondragstart="dragStart(event)">
可拖拽元素
</div>
function dragStart(e) {
e.dataTransfer.setData('text/plain', e.target.id);
e.dataTransfer.effectAllowed = 'move';
}
function drop(e) {
e.preventDefault();
const data = e.dataTransfer.getData('text/plain');
const element = document.getElementById(data);
e.target.appendChild(element);
}
2. 鼠标事件模拟(更常用)
javascript
class Draggable {
constructor(element) {
this.element = element;
this.isDragging = false;
this.startX = 0;
this.startY = 0;
this.initialLeft = 0;
this.initialTop = 0;
this.bindEvents();
}
bindEvents() {
this.element.addEventListener('mousedown', this.onMouseDown.bind(this));
document.addEventListener('mousemove', this.onMouseMove.bind(this));
document.addEventListener('mouseup', this.onMouseUp.bind(this));
}
onMouseDown(e) {
this.isDragging = true;
// 记录初始位置
this.startX = e.clientX;
this.startY = e.clientY;
// 获取元素当前位置
const rect = this.element.getBoundingClientRect();
this.initialLeft = rect.left;
this.initialTop = rect.top;
this.element.style.position = 'absolute';
this.element.style.zIndex = '1000';
this.element.style.cursor = 'grabbing';
// 阻止默认事件(避免选中文本)
e.preventDefault();
}
onMouseMove(e) {
if (!this.isDragging) return;
// 计算移动距离
const dx = e.clientX - this.startX;
const dy = e.clientY - this.startY;
// 设置新位置
this.element.style.left = this.initialLeft + dx + 'px';
this.element.style.top = this.initialTop + dy + 'px';
// 可选:添加边界限制
this.applyBoundary();
}
onMouseUp() {
this.isDragging = false;
this.element.style.cursor = 'grab';
}
applyBoundary() {
const left = parseInt(this.element.style.left);
const top = parseInt(this.element.style.top);
const parent = this.element.parentElement;
if (parent) {
const maxLeft = parent.clientWidth - this.element.offsetWidth;
const maxTop = parent.clientHeight - this.element.offsetHeight;
this.element.style.left = Math.max(0, Math.min(left, maxLeft)) + 'px';
this.element.style.top = Math.max(0, Math.min(top, maxTop)) + 'px';
}
}
}
🖼️ 图片懒加载
问题:图片懒加载(什么情况下加载,什么情况下不加载)
✅ 参考实现:
1. Intersection Observer(推荐)
javascript
class LazyLoader {
constructor() {
this.images = document.querySelectorAll('img[data-src]');
this.observer = null;
this.init();
}
init() {
// 创建观察者
this.observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
// 当图片进入视口时加载
if (entry.isIntersecting) {
const img = entry.target;
this.loadImage(img);
// 加载后停止观察
this.observer.unobserve(img);
}
});
}, {
root: null, // 视口
rootMargin: '50px', // 提前50px加载
threshold: 0.01 // 出现1%时触发
});
// 开始观察
this.images.forEach(img => {
this.observer.observe(img);
});
}
loadImage(img) {
const src = img.dataset.src;
if (src) {
img.src = src;
img.removeAttribute('data-src');
// 添加加载完成类
img.addEventListener('load', () => {
img.classList.add('loaded');
});
}
}
}
// 使用
const lazyLoader = new LazyLoader();
2. 什么情况下加载?
javascript
// 加载条件:
1. 图片进入视口(或即将进入)
2. 浏览器空闲时(预加载)
3. 网络状态良好(4G/WiFi)
4. 用户即将滚动到该位置(预测)
// 不加载条件:
1. 图片远离视口
2. 网络状态差(2G/弱网)
3. 节省流量模式开启
4. 图片被display:none隐藏
3. 优化版本(考虑网络状态)
javascript
class SmartLazyLoader extends LazyLoader {
constructor() {
super();
this.connection = navigator.connection ||
navigator.mozConnection ||
navigator.webkitConnection;
this.checkNetwork();
}
checkNetwork() {
if (this.connection) {
// 监听网络变化
this.connection.addEventListener('change', this.onNetworkChange.bind(this));
// 判断是否应该加载
if (this.shouldLoad()) {
this.startObserving();
} else {
this.pauseObserving();
}
}
}
shouldLoad() {
if (!this.connection) return true;
// 2G网络不预加载
if (this.connection.effectiveType.includes('2g')) {
return false;
}
// 流量敏感
if (this.connection.saveData) {
return false;
}
return true;
}
onNetworkChange() {
if (this.shouldLoad()) {
this.startObserving();
} else {
this.pauseObserving();
}
}
}
🏗️ 实现new操作符
问题:实现new操作符
✅ 参考实现:
javascript
function myNew(constructor, ...args) {
// 1. 创建一个空对象,原型指向构造函数的prototype
const obj = Object.create(constructor.prototype);
// 2. 将构造函数的this指向这个对象,并执行构造函数
const result = constructor.apply(obj, args);
// 3. 判断返回值
// 如果构造函数返回对象,则返回该对象;否则返回创建的对象
return (typeof result === 'object' && result !== null) || typeof result === 'function'
? result
: obj;
}
// 测试
function Person(name, age) {
this.name = name;
this.age = age;
// 如果没有return,默认返回this
}
const p1 = myNew(Person, 'Tom', 18);
console.log(p1); // Person { name: 'Tom', age: 18 }
console.log(p1 instanceof Person); // true
// 测试构造函数返回对象的情况
function ReturnsObject(name) {
this.name = name;
return { custom: 'object' };
}
const p2 = myNew(ReturnsObject, 'Jerry');
console.log(p2); // { custom: 'object' },不是ReturnsObject的实例
关键点解析:
javascript
// 1. Object.create 相当于
const obj = {};
obj.__proto__ = constructor.prototype;
// 2. 处理返回值
- 如果构造函数返回原始类型(string/number/boolean等),忽略,返回obj
- 如果构造函数返回对象/函数,返回该对象
- null是特殊情况(typeof null === 'object'),返回obj
🌳 列表转树·数据变形
问题:列表到树的数据变形
✅ 参考实现:
1. 题目理解
javascript
// 输入
const list = [
{ id: 1, name: '节点1', parentId: null },
{ id: 2, name: '节点2', parentId: 1 },
{ id: 3, name: '节点3', parentId: 1 },
{ id: 4, name: '节点4', parentId: 2 },
{ id: 5, name: '节点5', parentId: 3 }
];
// 输出
const tree = [
{
id: 1,
name: '节点1',
children: [
{
id: 2,
name: '节点2',
children: [
{ id: 4, name: '节点4', children: [] }
]
},
{
id: 3,
name: '节点3',
children: [
{ id: 5, name: '节点5', children: [] }
]
}
]
}
];
2. 一次遍历 + Map
javascript
function listToTree(list) {
const map = new Map();
const roots = [];
// 第一次遍历:建立 id -> node 映射
list.forEach(item => {
map.set(item.id, {
...item,
children: []
});
});
// 第二次遍历:建立父子关系
list.forEach(item => {
const node = map.get(item.id);
if (item.parentId === null || item.parentId === undefined) {
// 根节点
roots.push(node);
} else {
// 子节点,找到父节点并添加
const parent = map.get(item.parentId);
if (parent) {
parent.children.push(node);
} else {
// 处理父节点不存在的情况(数据错误)
roots.push(node);
}
}
});
return roots;
}
3. 递归版本(适用于小数据)
javascript
function listToTreeRecursive(list, parentId = null) {
return list
.filter(item => item.parentId === parentId)
.map(item => ({
...item,
children: listToTreeRecursive(list, item.id)
}));
}
4. 进阶要求:排序、过滤
javascript
function listToTreeAdvanced(list, options = {}) {
const {
idKey = 'id',
parentKey = 'parentId',
childrenKey = 'children',
sortKey = null,
sortOrder = 'asc',
filter = null
} = options;
const map = new Map();
const roots = [];
// 过滤
const filteredList = filter ? list.filter(filter) : list;
// 建立映射
filteredList.forEach(item => {
map.set(item[idKey], {
...item,
[childrenKey]: []
});
});
// 建立关系
filteredList.forEach(item => {
const node = map.get(item[idKey]);
const parentId = item[parentKey];
if (parentId === null || parentId === undefined) {
roots.push(node);
} else {
const parent = map.get(parentId);
if (parent) {
parent[childrenKey].push(node);
}
}
});
// 排序
if (sortKey) {
const sortFn = (a, b) => {
if (sortOrder === 'asc') {
return a[sortKey] > b[sortKey] ? 1 : -1;
} else {
return a[sortKey] < b[sortKey] ? 1 : -1;
}
};
const sortTree = (nodes) => {
nodes.sort(sortFn);
nodes.forEach(node => {
if (node[childrenKey].length) {
sortTree(node[childrenKey]);
}
});
};
sortTree(roots);
}
return roots;
}
5. 应用场景
javascript
// 1. 组织架构图
// 2. 权限菜单树
// 3. 评论楼中楼
// 4. 商品分类
// 5. 地区选择器
🎁 附:字节一面复习清单
| 知识点 | 掌握程度 | 重点方向 |
|---|---|---|
| 输入URL全过程 | ⭐⭐⭐⭐⭐ | DNS、TCP、TLS、HTTP、渲染 |
| 事件循环 | ⭐⭐⭐⭐⭐ | 宏任务/微任务、async/await |
| CSS垂直居中 | ⭐⭐⭐⭐ | 多种方案、优缺点 |
| Vue响应式 | ⭐⭐⭐⭐ | Vue2/3对比、原理实现 |
| 设计模式 | ⭐⭐⭐ | 常见模式、应用场景 |
| 选择器优先级 | ⭐⭐⭐ | 权重计算、特殊情况 |
| Git冲突解决 | ⭐⭐⭐ | 解决流程、预防策略 |
| 拖拽实现 | ⭐⭐⭐ | 原生API、鼠标模拟 |
| 图片懒加载 | ⭐⭐⭐ | Intersection Observer、网络判断 |
| new实现 | ⭐⭐⭐⭐ | 原型链、返回值处理 |
| 列表转树 | ⭐⭐⭐⭐ | 一次遍历、递归、排序过滤 |
📌 最后一句:
木木复盘:虽然分享者没有明确指出是实习还是校招面试,个人感觉这里的大部分八股已经在其他的面经中有遇到了,整体准备充分的话,这场面试应该会比较从容。这也是我提倡大家去复盘面经的原因。