本轮概述: 第二轮对技术深度有一定的考察。 对构建工具有深入理解,能够分析技术选型原因 掌握React核心概念和性能优化技巧 具备网络协议和安全知识 代码实现能力强,能够设计合理的算法和架构 理解设计模式在实际场景中的应用
本轮共 14 道题。答案默认折叠,便于先自行作答。
1. Node中间件的作用和使用
题目要点
请求拦截处理、横切关注点、链式执行、功能解耦
参考答案
中间件是在请求和响应之间执行的函数,用于处理横切关注点。在Express/Nest.js中,中间件按顺序执行,每个中间件可以修改请求对象、响应对象,或者终止请求-响应循环。
常用的中间件包括:
- 身份验证中间件:验证用户token
- 日志中间件:记录请求信息
- 错误处理中间件:统一处理异常
- CORS中间件:处理跨域请求
- 参数验证中间件:验证请求参数格式
2. 大模型的理解和应用
题目要点
语言模型、涌现能力、开发辅助、安全考虑
参考答案
大模型是基于Transformer架构的深度学习模型,通过大规模数据训练获得强大的语言理解和生成能力。主要特点是参数量巨大、涌现能力强、通用性好。
在开发中的应用:
- 代码生成和补全:提高开发效率
- 文档生成:自动生成API文档和注释
- 代码审查:发现潜在问题和优化建议
- 需求分析:辅助理解业务逻辑
使用时需要注意数据安全、结果准确性验证、成本控制等问题。
3. Webpack vs Vite 的理解和区别
题目要点
构建原理差异、性能表现、生态成熟度、配置复杂度
参考答案
Webpack是基于模块打包的构建工具,通过入口文件分析依赖关系,将所有资源打包成bundle。Vite是基于ES模块的构建工具,开发时利用浏览器原生ES模块支持,生产时使用Rollup打包。
主要区别:
- 启动速度:Vite利用ES模块按需加载,启动更快;Webpack需要预先打包
- 热更新:Vite基于ES模块的HMR更精确;Webpack需要重新打包相关模块
- 生态系统:Webpack生态更成熟,插件丰富;Vite相对较新但发展迅速
- 配置复杂度:Vite开箱即用,配置简单;Webpack配置相对复杂
4. 项目选择构建工具的考虑
题目要点
开发体验优先、项目延续性、团队熟悉度、稳定性考虑
参考答案
个人项目选择Vite主要考虑开发体验,启动快、热更新迅速,配置简单,适合快速原型开发。实习项目使用Webpack主要因为:
- 历史项目延续性,已有完整的配置和工作流
- 团队熟悉度高,维护成本低
- 生态系统成熟,特殊需求的插件支持更好
- 大型项目的稳定性考虑
5. Vite 开发环境与生产环境差异
题目要点
按需加载vs完整打包、开发体验vs生产优化、不同构建工具
参考答案
开发环境:
- 不进行完整打包,利用ES模块按需加载
- 使用esbuild进行依赖预构建
- 支持热模块替换(HMR)
- 源码直接在浏览器中运行
生产环境:
- 使用Rollup进行完整打包
- 代码压缩、Tree shaking、代码分割
- 静态资源优化和缓存策略
- 兼容性处理和polyfill
6. Tree Shaking 对比
题目要点
静态分析、配置差异、工具依赖、效果对比
参考答案
Webpack Tree Shaking:
- 基于ES6模块的静态分析
- 需要配置mode: ‘production’和optimization.usedExports
- 依赖UglifyJS或Terser进行死代码消除
- 支持sideEffects配置标记无副作用模块
Vite Tree Shaking:
- 开发环境不进行Tree Shaking,保持快速启动
- 生产环境使用Rollup的Tree Shaking
- 默认开启,无需额外配置
- 基于ES模块的静态分析,效果更好
7. React Fiber
题目要点
可中断渲染、优先级调度、时间切片、性能优化
参考答案
Fiber是React 16引入的新协调算法,解决了之前递归调和过程不可中断的问题。
核心特性:
- 时间切片:将渲染工作分割成小块,每个时间片执行一部分工作
- 优先级调度:不同类型的更新有不同优先级,高优先级可以中断低优先级
- 可中断渲染:长时间的渲染任务可以被中断,让出主线程给其他任务
- 双缓冲:current树和workInProgress树交替工作
实现原理是将递归改为循环,通过链表结构连接Fiber节点,支持暂停和恢复。
8. React Hooks 作用和常用Hook
题目要点
函数组件增强、状态管理、副作用处理、性能优化
参考答案
Hooks让函数组件能够使用状态和生命周期特性,解决了类组件的复杂性问题。
常用Hooks:
- useState:管理组件状态
- useEffect:处理副作用和生命周期
- useContext:消费Context数据
- useReducer:复杂状态管理
- useMemo:缓存计算结果
- useCallback:缓存函数引用
- useRef:访问DOM或保存可变值
9. useMemo、useCallback、useRef 区别
题目要点
缓存对象不同、使用场景差异、性能优化方式
参考答案
useMemo:
- 缓存计算结果,避免重复计算
- 依赖数组变化时重新计算
- 返回缓存的值
useCallback:
- 缓存函数引用,避免子组件不必要的重渲染
- 依赖数组变化时返回新函数
- 返回缓存的函数
useRef:
- 创建可变的ref对象,不会触发重渲染
- 常用于访问DOM元素或保存跨渲染的值
- 返回固定的ref对象
// useMemo示例
const expensiveValue = useMemo(() => {
return computeExpensiveValue(a, b);
}, [a, b]);
// useCallback示例
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);
// useRef示例
const inputRef = useRef(null);
10. TCP vs UDP、三次握手、SYN攻击
题目要点
协议特性差异、连接建立过程、安全攻击防护
参考答案
TCP vs UDP:
- TCP:面向连接、可靠传输、有序、流量控制、拥塞控制
- UDP:无连接、不可靠、无序、开销小、速度快
TCP三次握手:
- 客户端发送SYN包,进入SYN_SENT状态
- 服务器回复SYN+ACK包,进入SYN_RCVD状态
- 客户端发送ACK包,连接建立,进入ESTABLISHED状态
SYN攻击: 攻击者发送大量SYN包但不回复ACK,导致服务器维护大量半连接状态,消耗资源。防护措施包括SYN Cookie、连接限制、防火墙过滤等。
11. 跨域和同源策略
题目要点
安全策略、同源定义、多种解决方案、应用场景
参考答案
同源策略:浏览器安全策略,限制不同源之间的资源访问。同源要求协议、域名、端口完全相同。
跨域解决方案:
- CORS:服务器设置Access-Control-Allow-Origin等头部
- JSONP:利用script标签不受同源策略限制
- 代理:开发环境通过webpack-dev-server代理
- postMessage:跨窗口通信
12. TypeScript中type 和 interface的区别
题目要点
使用场景差异、功能特性、声明合并、类型操作
参考答案
interface:
- 主要用于定义对象结构
- 支持声明合并
- 可以被类实现
- 只能描述对象类型
type:
- 可以定义任何类型别名
- 支持联合类型、交叉类型
- 不支持声明合并
- 更灵活的类型操作
// interface示例
interface User {
name: string;
age: number;
}
interface User {
email: string; // 声明合并
}
// type示例
type Status = 'loading' | 'success' | 'error';
type UserWithStatus = User & { status: Status };
13. 对算术表达式求值
给定一个字符串,输出对应的计算结果,包括加减乘除,小括号,只考虑正整数,例如"2+3*2" => 输出8
例如:1+2*(5-2)+4 => 1+2*3+4 => 1+6+4 => 11
题目要点
括号优先级、运算符优先级、字符串解析、递归处理
参考答案
function calculate(expression) {
// 去除空格
expression = expression.replace(/\s/g, '');
// 处理括号
while (expression.includes('(')) {
let start = -1;
for (let i = 0; i < expression.length; i++) {
if (expression[i] === '(') {
start = i;
} else if (expression[i] === ')') {
let subExpr = expression.substring(start + 1, i);
let result = evaluateSimple(subExpr);
expression = expression.substring(0, start) + result + expression.substring(i + 1);
break;
}
}
}
return evaluateSimple(expression);
}
function evaluateSimple(expr) {
// 处理乘除
while (expr.includes('*') || expr.includes('/')) {
for (let i = 0; i < expr.length; i++) {
if (expr[i] === '*' || expr[i] === '/') {
let left = getLeftNumber(expr, i);
let right = getRightNumber(expr, i);
let result = expr[i] === '*' ? left.value * right.value : left.value / right.value;
expr = expr.substring(0, left.start) + result + expr.substring(right.end);
break;
}
}
}
// 处理加减
while (expr.includes('+') || (expr.includes('-') && expr.lastIndexOf('-') > 0)) {
for (let i = 1; i < expr.length; i++) {
if (expr[i] === '+' || expr[i] === '-') {
let left = getLeftNumber(expr, i);
let right = getRightNumber(expr, i);
let result = expr[i] === '+' ? left.value + right.value : left.value - right.value;
expr = expr.substring(0, left.start) + result + expr.substring(right.end);
break;
}
}
}
return parseFloat(expr);
}
function getLeftNumber(expr, pos) {
let start = pos - 1;
while (start >= 0 && /\d/.test(expr[start])) {
start--;
}
start++;
return {
value: parseFloat(expr.substring(start, pos)),
start: start
};
}
function getRightNumber(expr, pos) {
let end = pos + 1;
while (end < expr.length && /\d/.test(expr[end])) {
end++;
}
return {
value: parseFloat(expr.substring(pos + 1, end)),
end: end
};
}
14. 手写题2: 有一种花,两种鸟,花定时开放,鸟看到花开会叫,鸟的叫声不一样,用代码来实现这样一种场景
题目要点
观察者模式、事件通知、对象交互、定时触发
参考答案
// 观察者模式实现
class Flower {
constructor() {
this.observers = [];
this.isOpen = false;
}
addObserver(observer) {
this.observers.push(observer);
}
removeObserver(observer) {
this.observers = this.observers.filter(obs => obs !== observer);
}
bloom() {
this.isOpen = true;
console.log('花开了!');
this.notifyObservers();
}
notifyObservers() {
this.observers.forEach(observer => observer.onFlowerBloom());
}
}
class Bird {
constructor(name, sound) {
this.name = name;
this.sound = sound;
}
onFlowerBloom() {
console.log(`${this.name}看到花开了,发出声音:${this.sound}`);
}
}
// 使用示例
const flower = new Flower();
const bird1 = new Bird('喜鹊', '喳喳喳');
const bird2 = new Bird('黄鹂', '啾啾啾');
flower.addObserver(bird1);
flower.addObserver(bird2);
// 定时开花
setInterval(() => {
flower.bloom();
}, 3000);