一、HTML与CSS篇
1. 如何优化网页的SEO?请列举至少5个关键点
答案详解:
- 语义化HTML:正确使用h1-h6标题、section、article等语义化标签
- meta标签优化:设置准确的title、description和keywords
- 图片优化:使用alt属性描述图片内容,合理压缩图片大小
- URL结构:使用简洁、包含关键词的URL,避免过长参数
- 移动端适配:确保网站在移动设备上良好显示,Google优先索引移动版
- 页面速度:通过代码压缩、CDN、懒加载等方式提升加载速度
2. CSS选择器优先级是如何计算的?
答案详解:
CSS选择器优先级由四个级别组成,计算规则如下:
- 内联样式:1000分
- ID选择器:100分
- 类/属性/伪类选择器:10分
- 元素/伪元素选择器:1分
- 通配符/继承样式:0分
比较时从左到右逐级比较,如#nav .item a的优先级是100+10+1=111分。当优先级相同时,后定义的样式会覆盖前面的样式。
3. 什么是BFC?如何创建BFC?它有什么特性?
答案详解:
BFC(Block Formatting Context)是块级格式化上下文,是Web页面中一个独立的渲染区域。
创建方式:
- float值不为none
- position为absolute或fixed
- display为inline-block、table-cell等
- overflow不为visible
特性:
- 内部盒子垂直排列
- 垂直方向的距离由margin决定,同一个BFC内相邻盒子margin会重叠
- BFC区域不会与float元素重叠
- 计算BFC高度时,浮动元素也参与计算
二、JavaScript篇
4. 解释JavaScript中的事件循环机制
答案详解:
JavaScript是单线程语言,通过事件循环实现异步操作:
- 所有同步任务在主线程执行,形成执行栈
- 异步任务会被放入任务队列,分为宏任务和微任务
- 主线程任务执行完毕后,先检查微任务队列并执行所有微任务
- 然后从宏任务队列取出一个任务执行
- 重复上述过程
常见分类:
- 宏任务:setTimeout、setInterval、I/O操作等
- 微任务:Promise.then、MutationObserver等
5. 什么是闭包?它有什么优缺点?
答案详解:
闭包是指有权访问另一个函数作用域中变量的函数。
创建方式:
function outer() {
let count = 0;
return function inner() {
return ++count;
}
}
优点:
- 可以读取函数内部的变量
- 让变量长期保存在内存中
- 可以封装私有变量和方法
缺点:
- 过度使用会导致内存泄漏
- 闭包会在父函数外部改变父函数内部变量的值
6. ES6中let、const和var的区别是什么?
答案详解:
| 特性 | var | let | const |
|---|---|---|---|
| 作用域 | 函数作用域 | 块级作用域 | 块级作用域 |
| 变量提升 | 是 | 否 | 否 |
| 重复声明 | 允许 | 不允许 | 不允许 |
| 初始值 | 可不设 | 可不设 | 必须设置 |
| 修改值 | 可以 | 可以 | 不可以 |
建议:默认使用const,需要修改时用let,避免使用var。
三、框架篇
7. React中setState是同步还是异步的?为什么?
答案详解:
setState在React合成事件和生命周期中是”异步”的,但在原生事件和setTimeout中是同步的。
异步原因:
- 性能优化:批量处理多个setState调用
- 保证内部一致性:props和state同步更新
- 启用并发渲染:为未来的并发模式做准备
获取更新后状态:
this.setState({count: 1}, () => {
console.log(this.state.count); // 获取最新值
});
8. Vue3的Composition API相比Options API有哪些优势?
答案详解:
- 更好的逻辑复用:可以提取和重用逻辑片段
- 更灵活的代码组织:相关逻辑可以放在一起
- 更好的类型推断:对TypeScript支持更好
- 更小的打包体积:减少了this上下文的绑定
- 更清晰的依赖关系:显式地声明响应式依赖
示例对比:
// Options API
export default {
data() {
return { count: 0 }
},
methods: {
increment() { this.count++ }
}
}
// Composition API
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
const increment = () => count.value++
return { count, increment }
}
}
9. 什么是虚拟DOM?它的工作原理是什么?
答案详解:
虚拟DOM是真实DOM的轻量级JavaScript对象表示。
工作原理:
- 当状态变化时,创建新的虚拟DOM树
- 通过Diff算法比较新旧虚拟DOM树的差异
- 将差异应用到真实DOM上
优点:
- 减少直接操作DOM的性能开销
- 跨平台能力(如React Native)
- 提高开发效率,无需手动优化DOM操作
缺点:
- 首次渲染需要额外创建虚拟DOM的开销
- 内存占用比直接操作DOM更高
四、性能优化篇
10. 如何优化前端页面加载速度?
答案详解:
- 资源压缩:使用Gzip压缩,压缩图片、JS、CSS
- 减少HTTP请求:合并文件,使用雪碧图
- 使用CDN:加速静态资源加载
- 懒加载:图片和组件按需加载
- 缓存策略:合理设置Cache-Control和ETag
- 代码分割:使用动态import分割代码
- 预加载:使用preload、prefetch优化关键资源
- 服务端渲染:减少首屏加载时间
11. 如何诊断和解决内存泄漏问题?
答案详解:
常见内存泄漏场景:
- 意外的全局变量
- 未清除的定时器
- DOM引用未释放
- 闭包使用不当
诊断工具:
- Chrome DevTools的Memory面板
- Performance面板记录内存变化
- Node.js的heapdump模块
解决方案:
- 及时清除事件监听器和定时器
- 避免不必要的全局变量
- 谨慎使用闭包
- 使用WeakMap/WeakSet存储DOM引用
12. 描述浏览器从输入URL到页面渲染的完整过程
答案详解:
- DNS解析:将域名解析为IP地址
- TCP连接:与服务器建立TCP连接
- 发送HTTP请求:浏览器发送页面请求
- 服务器处理:服务器处理请求并返回响应
- 浏览器解析:
- 解析HTML构建DOM树
- 解析CSS构建CSSOM树
- 将DOM和CSSOM合并成渲染树
- 计算布局(重排)
- 绘制页面(重绘)
- 加载资源:解析到外部资源时并行加载
- 执行JS:遇到JS可能会阻塞解析
五、工程化篇
13. Webpack的构建流程是怎样的?
答案详解:
Webpack构建流程主要分为以下阶段:
- 初始化参数:从配置文件和命令行读取参数
- 开始编译:用参数初始化Compiler对象
- 确定入口:根据配置找到所有入口文件
- 编译模块:从入口出发,调用loader翻译模块
- 完成编译:得到每个模块的依赖关系和内容
- 输出资源:根据入口和模块依赖组装成chunk
- 写入文件:根据配置的输出路径和文件名写入文件系统
优化点:
- 使用HappyPack多进程编译
- 配置DllPlugin预编译不变资源
- 合理使用Tree Shaking
- 按需加载代码分割
14. 什么是微前端?它解决了什么问题?
答案详解:
微前端是将前端应用分解为多个小型、独立部署的前端应用的架构风格。
解决的问题:
- 大型项目维护困难
- 技术栈升级困难
- 团队协作效率低
- 独立部署能力差
实现方案:
- iframe:简单但隔离性太强
- Web Components:浏览器原生支持但兼容性要求高
- single-spa:流行的微前端框架
- qiankun:基于single-spa的阿里方案
优点:
- 独立开发、独立部署
- 渐进式升级
- 技术栈无关
- 更好的团队自治
15. 前端如何实现灰度发布?
答案详解:
实现方案:
- Nginx分流:根据IP、Cookie等条件路由到不同版本
- CDN灰度:通过CDN配置不同用户访问不同版本
- 前端路由:根据用户特征在前端代码中控制展示版本
- 服务端渲染:服务端根据规则返回不同版本HTML
具体实现示例(Nginx):
# 根据Cookie分流
map $cookie_gray $group {
default "stable";
"true" "gray";
}
server {
location / {
proxy_pass http://$group;
}
}
注意事项:
- 确保灰度规则可配置
- 做好数据监控和回滚方案
- 控制灰度范围逐步扩大
- 确保新旧版本兼容性
六、安全篇
16. 常见的前端安全漏洞有哪些?如何防范?
答案详解:
- XSS(跨站脚本攻击)
- 防范:输入过滤、输出转义、设置HttpOnly、CSP策略
- CSRF(跨站请求伪造)
- 防范:验证Referer、添加Token、SameSite Cookie
- 点击劫持
- 防范:X-Frame-Options头、Frame Busting代码
- 中间人攻击
- 防范:使用HTTPS、HSTS策略
- 敏感数据泄露
- 防范:不在前端存储敏感数据、加密传输
- 第三方依赖漏洞
- 防范:定期更新依赖、使用npm audit检查
17. 什么是CORS?如何正确配置?
答案详解:
CORS(跨域资源共享)是一种机制,允许网页从不同域的服务器请求资源。
简单请求配置:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, HEAD
Access-Control-Allow-Headers: Content-Type
预检请求配置:
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 86400
前端代码示例:
fetch('https://api.example.com/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Custom-Header': 'value'
},
credentials: 'include' // 携带cookie
});
注意事项:
- 不要盲目使用
*通配符 - 敏感接口应限制具体域名
- 带凭证的请求不能使用
* - 合理设置缓存时间
七、TypeScript篇
18. TypeScript中的interface和type有什么区别?
答案详解:
| 特性 | interface | type |
|---|---|---|
| 扩展方式 | extends | & |
| 合并声明 | 支持 | 不支持 |
| 实现类 | 可以 | 不可以 |
| 基本类型别名 | 不可以 | 可以 |
| 联合类型 | 不可以 | 可以 |
| 元组类型 | 可以 | 可以 |
| 函数类型 | 可以 | 可以 |
使用建议:
- 需要扩展或实现时用interface
- 需要定义联合类型或元组时用type
- 简单对象类型两者都可以
19. 解释TypeScript中的泛型及其应用场景
答案详解:
泛型允许在定义函数、接口或类时不预先指定具体类型,而在使用时再指定。
基本语法:
function identity<T>(arg: T): T {
return arg;
}
应用场景:
- 函数泛型:处理多种类型参数的函数
- 接口泛型:定义可复用的接口结构
- 类泛型:创建可处理多种类型的类
- 约束泛型:使用extends限制泛型范围
- 默认泛型:为泛型参数提供默认类型
高级用法示例:
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
八、综合篇
20. 如何设计一个前端埋点监控系统?
答案详解:
系统组成:
- 数据采集层:
- 用户行为(点击、滚动等)
- 性能数据(加载时间、FP/FCP等)
- 错误监控(JS错误、资源加载失败)
- 接口监控(成功率、耗时)
- 数据传输层:
- 使用img或navigator.sendBeacon减少对页面影响
- 数据压缩和批量上报
- 离线存储和重试机制
- 数据处理层:
- •数据清洗和格式化
- •数据存储(数据库或大数据平台)
- •实时计算和离线计算
- 数据展示层:
- 可视化仪表盘
- 报警机制
- 用户行为路径分析
技术实现要点:
- 使用单例模式确保只初始化一次
- 采用策略模式支持多种上报方式
- 使用防抖节流控制高频事件上报
- 区分开发和生产环境上报
- 支持自定义扩展点
示例代码:
class Tracker {
constructor(options) {
this.options = options;
this.queue = [];
this.init();
}
init() {
window.addEventListener('unload', () => {
this.send(this.queue);
});
}
track(event, data) {
this.queue.push({event, data});
if(this.queue.length >= this.options.batchSize) {
this.send(this.queue);
this.queue = [];
}
}
send(data) {
navigator.sendBeacon(this.options.url, JSON.stringify(data));
}
}
