前端面试题集

JavaScript 基础

1. 解释 JavaScript 中的原型和原型链

答案
JavaScript 中的每个对象都有一个原型(prototype),原型也是一个对象,对象可以从原型继承属性和方法。

  • 原型:每个函数都有一个 prototype 属性,指向一个对象,这个对象就是该函数的实例的原型
  • 原型链:当访问一个对象的属性时,如果对象本身没有这个属性,JavaScript 会沿着原型链向上查找,直到找到该属性或到达原型链的末端(null
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Person(name) {
this.name = name;
}

Person.prototype.sayHello = function() {
console.log(`Hello, my name is ${this.name}`);
};

const alice = new Person('Alice');
alice.sayHello(); // "Hello, my name is Alice"

// 原型链示例
console.log(alice.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true

2. var、let 和 const 的区别是什么?

答案

  • var

    • 函数作用域或全局作用域
    • 存在变量提升(可以在声明前使用,值为 undefined)
    • 可以重复声明同名变量
    • 可以重新赋值
  • let

    • 块级作用域
    • 存在暂时性死区(不能在声明前使用)
    • 不可以重复声明同名变量
    • 可以重新赋值
  • const

    • 块级作用域
    • 存在暂时性死区(不能在声明前使用)
    • 不可以重复声明同名变量
    • 不可以重新赋值(但对于引用类型,可以修改其属性)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// var 示例
var x = 1;
if (true) {
var x = 2; // 同一个变量
}
console.log(x); // 2

// let 示例
let y = 1;
if (true) {
let y = 2; // 不同的变量
}
console.log(y); // 1

// const 示例
const z = { value: 1 };
z.value = 2; // 可以修改属性
console.log(z.value); // 2
// z = { value: 3 }; // 错误:不能重新赋值

3. 解释事件循环(Event Loop)机制

答案
JavaScript 是单线程的,事件循环是 JavaScript 实现异步的核心机制,它由以下部分组成:

  • 调用栈(Call Stack):执行同步代码
  • 任务队列(Task Queue)
    • 宏任务(Macrotask):setTimeout、setInterval、I/O、UI 渲染等
    • 微任务(Microtask):Promise 回调、MutationObserver、queueMicrotask() 等

事件循环的执行顺序:

  1. 执行同步代码(调用栈中的任务)
  2. 执行所有微任务
  3. 执行一个宏任务
  4. 重复步骤 2-3
1
2
3
4
5
6
7
8
9
10
11
12
13
console.log('1'); // 同步代码

setTimeout(() => {
console.log('2'); // 宏任务
}, 0);

Promise.resolve().then(() => {
console.log('3'); // 微任务
});

console.log('4'); // 同步代码

// 输出顺序:1, 4, 3, 2

4. 闭包是什么?有什么用途和潜在问题?

答案
闭包是指一个函数可以记住并访问其词法作用域,即使该函数在其词法作用域之外执行。

用途

  • 数据私有化/封装
  • 创建函数工厂
  • 实现模块模式
  • 维持状态

潜在问题

  • 内存泄漏:闭包会保持对外部变量的引用,可能导致这些变量无法被垃圾回收
  • 性能问题:过度使用闭包可能影响性能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 闭包示例 - 计数器
function createCounter() {
let count = 0; // 私有变量

return {
increment: function() {
count++;
return count;
},
decrement: function() {
count--;
return count;
},
getCount: function() {
return count;
}
};
}

const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.decrement()); // 1

5. Promise、async/await 的作用和区别

答案
Promise 和 async/await 都是用于处理 JavaScript 中的异步操作。

Promise

  • 表示一个异步操作的最终完成(或失败)及其结果值
  • 有三种状态:pending(进行中)、fulfilled(已成功)、rejected(已失败)
  • 使用 .then().catch() 方法处理结果和错误

async/await

  • 是基于 Promise 的语法糖
  • async 函数返回一个 Promise
  • await 关键字只能在 async 函数内使用,用于等待 Promise 解决
  • 使代码看起来更像同步代码,更易读

区别

  • 语法:async/await 语法更简洁,更接近同步代码
  • 错误处理:Promise 使用 .catch(),async/await 使用 try/catch
  • 调试:async/await 更容易调试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Promise 示例
function fetchDataPromise() {
return fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => {
console.log(data);
return data;
})
.catch(error => {
console.error('Error:', error);
});
}

// async/await 示例
async function fetchDataAsync() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);
return data;
} catch (error) {
console.error('Error:', error);
}
}

HTML & CSS

6. 语义化 HTML 的意义是什么?

答案
语义化 HTML 是指使用恰当的 HTML 标签来表示内容的结构和含义,而不仅仅是为了展示效果。

意义

  • 可访问性:屏幕阅读器等辅助技术可以更好地解释页面内容
  • SEO 优化:搜索引擎更容易理解页面内容和结构
  • 可维护性:代码更清晰,易于理解和维护
  • 设备兼容性:在不同设备上有更好的展示效果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- 非语义化的写法 -->
<div class="header">
<div class="logo">网站名称</div>
<div class="nav">
<div>首页</div>
<div>关于</div>
</div>
</div>

<!-- 语义化的写法 -->
<header>
<h1>网站名称</h1>
<nav>
<ul>
<li><a href="/">首页</a></li>
<li><a href="/about">关于</a></li>
</ul>
</nav>
</header>

7. 解释 CSS 盒模型及其不同类型

答案
CSS 盒模型描述了元素内容(content)、内边距(padding)、边框(border)和外边距(margin)如何一起决定元素的总尺寸。

两种盒模型

  • 标准盒模型(content-box)

    • widthheight 只包括内容区域
    • 总宽度 = width + padding + border + margin
  • 替代盒模型(border-box)

    • widthheight 包括内容区域、内边距和边框
    • 总宽度 = width + margin(width 已经包含了 padding 和 border)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* 标准盒模型(默认) */
.box-content {
box-sizing: content-box;
width: 300px;
padding: 20px;
border: 10px solid black;
margin: 15px;
/* 总宽度 = 300px + 40px(padding) + 20px(border) + 30px(margin) = 390px */
}

/* 替代盒模型 */
.box-border {
box-sizing: border-box;
width: 300px;
padding: 20px;
border: 10px solid black;
margin: 15px;
/* 总宽度 = 300px + 30px(margin) = 330px */
/* 内容区实际宽度 = 300px - 40px(padding) - 20px(border) = 240px */
}

8. CSS 选择器的优先级是如何计算的?

答案
CSS 选择器的优先级按照以下规则计算:

  1. 内联样式:1000 分
  2. ID 选择器:100 分
  3. 类选择器、属性选择器、伪类:10 分
  4. 元素选择器、伪元素:1 分
  5. 通配符(*):0 分
  6. 继承的样式:无优先级

当优先级相同时,后声明的样式会覆盖先声明的样式。
使用 !important 可以覆盖所有其他样式(除非对方也使用了 !important,此时仍按优先级计算)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* 优先级: 1 (元素选择器) */
p {
color: black;
}

/* 优先级: 10 (类选择器) */
.text {
color: blue;
}

/* 优先级: 11 (类选择器 + 元素选择器) */
p.text {
color: green;
}

/* 优先级: 110 (ID选择器 + 元素选择器) */
#content p {
color: red;
}

/* 优先级: 10 (类选择器),但使用 !important */
.highlight {
color: yellow !important; /* 会覆盖上面所有规则 */
}

9. 响应式设计的核心原则和实现方法有哪些?

答案
响应式设计是一种让网站能够适应不同设备和屏幕尺寸的设计方法。

核心原则

  • 流式布局:使用相对单位(%、em、rem)而非固定单位(px)
  • 媒体查询:根据设备特性应用不同的样式
  • 灵活的图片:确保图片能够缩放而不溢出容器
  • 移动优先:先设计移动端界面,再逐步增强到大屏幕

实现方法

  • 使用媒体查询(@media)
  • 使用 viewport 元标签
  • 使用 CSS Grid 和 Flexbox 布局
  • 使用相对单位(%、em、rem、vw、vh)
  • 使用响应式图片技术(srcset、sizes 属性)
1
2
<!-- viewport 设置 -->
<meta name="viewport" content="width=device-width, initial-scale=1.0">
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/* 流式布局 */
.container {
width: 90%;
max-width: 1200px;
margin: 0 auto;
}

/* 响应式图片 */
img {
max-width: 100%;
height: auto;
}

/* 媒体查询 */
/* 移动设备 */
@media (max-width: 767px) {
.sidebar {
display: none;
}
.main-content {
width: 100%;
}
}

/* 平板设备 */
@media (min-width: 768px) and (max-width: 1023px) {
.sidebar {
width: 30%;
}
.main-content {
width: 70%;
}
}

/* 桌面设备 */
@media (min-width: 1024px) {
.sidebar {
width: 25%;
}
.main-content {
width: 75%;
}
}

10. 解释 CSS Grid 和 Flexbox 的区别及适用场景

答案
CSS Grid 和 Flexbox 都是现代 CSS 布局技术,但它们有不同的设计目标和适用场景。

Flexbox(弹性盒子)

  • 一维布局系统:主要沿一个轴(主轴或交叉轴)进行布局
  • 内容驱动:元素大小由其内容决定
  • 适用场景:导航菜单、卡片布局、居中元素、简单的一维布局

CSS Grid(网格布局)

  • 二维布局系统:同时控制行和列
  • 布局驱动:先定义布局结构,再放入元素
  • 适用场景:整体页面布局、复杂的二维布局、不规则布局
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/* Flexbox 示例 - 导航菜单 */
.nav {
display: flex;
justify-content: space-between;
align-items: center;
}

.nav-item {
flex: 0 1 auto;
margin: 0 10px;
}

/* CSS Grid 示例 - 页面布局 */
.page {
display: grid;
grid-template-columns: 1fr 3fr 1fr;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header header header"
"sidebar main aside"
"footer footer footer";
min-height: 100vh;
}

.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; }
.aside { grid-area: aside; }
.footer { grid-area: footer; }

前端框架

11. React 中的虚拟 DOM 是什么?它有什么优势?

答案
虚拟 DOM (Virtual DOM) 是 React 中的一个概念,它是真实 DOM 的一种轻量级的 JavaScript 对象表示。

工作原理

  1. 当组件状态改变时,React 创建一个新的虚拟 DOM 树
  2. 将新的虚拟 DOM 树与之前的虚拟 DOM 树进行比较(Diffing 算法)
  3. 计算出需要更新的部分
  4. 只更新真实 DOM 中需要变化的部分

优势

  • 性能优化:减少直接操作 DOM 的次数,批量处理 DOM 更新
  • 跨平台:虚拟 DOM 是平台无关的,可以渲染到不同环境(Web、Native、服务器等)
  • 声明式编程:开发者只需关注状态和 UI 的映射关系,不需要手动操作 DOM
  • 调试方便:可以追踪 DOM 变化的历史记录
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// React 组件示例
function Counter() {
const [count, setCount] = React.useState(0);

return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}

// 当点击按钮时,React 会:
// 1. 创建新的虚拟 DOM 树(count 值更新)
// 2. 与旧的虚拟 DOM 树比较
// 3. 发现只有 <p> 中的文本内容需要更新
// 4. 只更新真实 DOM 中的那部分内容

12. Vue 的响应式原理是什么?

答案
Vue 的响应式系统是其核心特性之一,它能够自动追踪依赖关系并在数据变化时更新视图。

Vue 2 响应式原理

  • 使用 Object.defineProperty() 劫持对象的属性
  • 在 getter 中收集依赖(Watcher)
  • 在 setter 中通知依赖更新
  • 对于数组,通过重写数组方法(push、pop 等)实现响应式

Vue 3 响应式原理

  • 使用 ES6 的 Proxy 代替 Object.defineProperty
  • 可以监听整个对象,包括属性的添加和删除
  • 可以监听数组的索引和长度变化
  • 性能更好,没有 Vue 2 中的一些限制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// Vue 2 响应式系统简化示例
function defineReactive(obj, key, val) {
const dep = new Dep(); // 依赖收集器

Object.defineProperty(obj, key, {
get() {
if (Dep.target) {
dep.depend(); // 收集依赖
}
return val;
},
set(newVal) {
if (newVal === val) return;
val = newVal;
dep.notify(); // 通知依赖更新
}
});
}

// Vue 3 响应式系统简化示例
function reactive(obj) {
return new Proxy(obj, {
get(target, key, receiver) {
track(target, key); // 收集依赖
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
trigger(target, key); // 触发更新
return result;
}
});
}

13. React Hooks 的作用和常用 Hook 有哪些?

答案
React Hooks 是 React 16.8 引入的特性,允许在函数组件中使用状态和其他 React 特性,而不需要编写类组件。

作用

  • 在函数组件中使用状态和生命周期特性
  • 复用状态逻辑,而不需要改变组件层次结构
  • 将相关逻辑组合在一起,而不是按生命周期方法分散

常用的 Hook

  • useState:管理组件状态
  • useEffect:处理副作用(类似 componentDidMount、componentDidUpdate、componentWillUnmount)
  • useContext:访问 React Context
  • useReducer:使用 reducer 管理复杂状态逻辑
  • useCallback:记忆函数,避免不必要的重新渲染
  • useMemo:记忆计算结果,避免重复计算
  • useRef:保存可变值,不触发重新渲染
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// useState 示例
function Counter() {
const [count, setCount] = useState(0);

return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}

// useEffect 示例
function DataFetcher() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);

useEffect(() => {
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
const result = await response.json();
setData(result);
} catch (error) {
console.error('Error fetching data:', error);
} finally {
setLoading(false);
}
}

fetchData();

// 清理函数(类似 componentWillUnmount)
return () => {
// 取消请求或清理资源
};
}, []); // 空依赖数组表示只在组件挂载时执行

if (loading) return <p>Loading...</p>;
return <div>{/* 渲染数据 */}</div>;
}

14. 前端路由的实现原理是什么?

答案
前端路由是指在单页应用(SPA)中,通过 JavaScript 控制页面内容的切换,而不刷新整个页面。主要有两种实现方式:

Hash 模式

  • 基于 URL 的哈希部分(# 后面的部分)
  • 通过监听 hashchange 事件检测路由变化
  • 兼容性好,但 URL 不够美观

History 模式

  • 基于 HTML5 History API(pushStatereplaceState
  • 通过监听 popstate 事件检测路由变化
  • URL 更美观,但需要服务器配置支持
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// Hash 模式路由简单实现
class HashRouter {
constructor() {
this.routes = {};
window.addEventListener('hashchange', this.handleHashChange.bind(this));
this.handleHashChange();
}

route(path, callback) {
this.routes[path] = callback;
}

handleHashChange() {
const hash = window.location.hash.slice(1) || '/';
const handler = this.routes[hash];
if (handler) {
handler();
}
}
}

// History 模式路由简单实现
class HistoryRouter {
constructor() {
this.routes = {};
window.addEventListener('popstate', this.handlePopState.bind(this));
this.handlePopState();
}

route(path, callback) {
this.routes[path] = callback;
}

navigate(path) {
history.pushState(null, null, path);
this.handlePopState();
}

handlePopState() {
const path = window.location.pathname;
const handler = this.routes[path];
if (handler) {
handler();
}
}
}

15. 状态管理工具(Redux/Vuex/Pinia)的核心概念和工作原理

答案
状态管理工具用于集中管理应用的状态,使状态变化可预测和可追踪。

Redux(React)核心概念

  • Store:存储应用的状态
  • Action:描述状态变化的普通对象
  • Reducer:纯函数,根据当前状态和 action 计算新状态
  • Dispatch:发送 action 的方法
  • Middleware:扩展 Redux 功能的中间件(如处理异步操作)

Vuex(Vue 2)核心概念

  • State:应用的状态
  • Getters:从 state 派生的状态
  • Mutations:同步修改状态的方法
  • Actions:可包含异步操作,提交 mutation
  • Modules:将 store 分割成模块

Pinia(Vue 3)核心概念

  • Store:定义状态和操作的容器
  • State:存储的响应式状态
  • Getters:类似计算属性,从 state 派生状态
  • Actions:修改状态的方法,可以是异步的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// Redux 示例
// 创建 reducer
function counterReducer(state = { count: 0 }, action) {
switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
case 'DECREMENT':
return { count: state.count - 1 };
default:
return state;
}
}

// 创建 store
const store = Redux.createStore(counterReducer);

// 订阅变化
store.subscribe(() => {
console.log(store.getState());
});

// 发送 action
store.dispatch({ type: 'INCREMENT' }); // { count: 1 }
store.dispatch({ type: 'INCREMENT' }); // { count: 2 }
store.dispatch({ type: 'DECREMENT' }); // { count: 1 }

// Vuex 示例
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++;
},
decrement(state) {
state.count--;
}
},
actions: {
incrementAsync({ commit }) {
setTimeout(() => {
commit('increment');
}, 1000);
}
},
getters: {
doubleCount: state => state.count * 2
}
});

// Pinia 示例 (Vue 3)
import { defineStore } from 'pinia';

export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0
}),
getters: {
doubleCount: (state) => state.count * 2
},
actions: {
increment() {
this.count++;
},
async incrementAsync() {
await new Promise(resolve => setTimeout(resolve, 1000));
this.increment();
}
}
});

前端工程化与性能优化

16. 前端模块化的发展历程和主要规范

答案
前端模块化是为了解决全局变量污染、代码复用、依赖管理等问题而发展起来的。

发展历程

  1. 全局函数:最初的代码组织方式,容易造成命名冲突
  2. 命名空间:将相关功能封装在对象中,减少全局变量
  3. IIFE(立即执行函数表达式):创建私有作用域,避免变量泄露
  4. CommonJS:Node.js 采用的模块规范,同步加载
  5. AMD(Asynchronous Module Definition):异步加载模块,适用于浏览器
  6. UMD(Universal Module Definition):兼容 CommonJS 和 AMD
  7. ES Modules:ECMAScript 官方模块系统,现代浏览器原生支持

主要规范

CommonJS

  • 使用 require() 导入,module.exportsexports 导出
  • 同步加载,适合服务器环境
  • Node.js 默认使用
1
2
3
4
5
6
7
8
9
// 导出 (math.js)
module.exports = {
add: (a, b) => a + b,
subtract: (a, b) => a - b
};

// 导入
const math = require('./math');
console.log(math.add(2, 3)); // 5

AMD

  • 使用 define() 定义模块,require() 加载模块
  • 异步加载,适合浏览器环境
  • RequireJS 是其实现
1
2
3
4
5
6
7
8
9
10
11
12
// 定义模块
define('math', [], function() {
return {
add: function(a, b) { return a + b; },
subtract: function(a, b) { return a - b; }
};
});

// 使用模块
require(['math'], function(math) {
console.log(math.add(2, 3)); // 5
});

ES Modules

  • 使用 import 导入,export 导出
  • 静态分析,支持 tree-shaking
  • 现代浏览器原生支持
1
2
3
4
5
6
7
8
9
10
11
12
// 导出 (math.js)
export function add(a, b) {
return a + b;
}

export function subtract(a, b) {
return a - b;
}

// 导入
import { add, subtract } from './math.js';
console.log(add(2, 3)); // 5

17. Webpack 的核心概念和工作原理

答案
Webpack 是一个现代 JavaScript 应用程序的静态模块打包工具,它将项目中的各种资源(JS、CSS、图片等)视为模块,根据模块的依赖关系进行打包。

核心概念

  • Entry:入口,指定 webpack 开始构建的起点
  • Output:输出,指定打包后的资源输出到哪里
  • Loaders:加载器,处理非 JavaScript 文件(如 CSS、图片)
  • Plugins:插件,执行范围更广的任务(如打包优化、资源管理)
  • Mode:模式,指定开发环境或生产环境
  • Chunks:代码块,打包过程中的代码单元

工作原理

  1. 初始化参数:从配置文件和命令行参数中读取配置
  2. 开始编译:初始化 Compiler 对象,加载所有配置的插件
  3. 确定入口:根据配置中的 entry 找出所有入口文件
  4. 编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行转换,再递归处理依赖的模块
  5. 完成模块编译:得到每个模块被转换后的最终内容和它们之间的依赖关系
  6. 输出资源:根据依赖关系,组装成一个个包含多个模块的 Chunk
  7. 输出完成:根据配置确定输出路径和文件名,将文件内容写入文件系统
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// webpack.config.js 示例
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
// 入口
entry: './src/index.js',

// 输出
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.[contenthash].js'
},

// 模式
mode: 'production',

// 模块处理
module: {
rules: [
// 处理 JavaScript
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
},
// 处理 CSS
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
// 处理图片
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource'
}
]
},

// 插件
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
],

// 优化
optimization: {
splitChunks: {
chunks: 'all'
}
}
};

18. 前端性能优化的主要方法和指标

答案
前端性能优化是提升用户体验的关键,包括多个方面的优化策略。

主要优化方法

网络优化

  • 减少 HTTP 请求数(合并文件、CSS Sprites、Base64 编码)
  • 使用 HTTP/2
  • 使用 CDN 加速
  • 启用 Gzip 压缩
  • 使用浏览器缓存(Cache-Control、ETag)
  • 懒加载和预加载资源

资源优化

  • 压缩代码(JS、CSS、HTML)
  • 压缩和优化图片(WebP、SVG、响应式图片)
  • Tree-shaking 移除未使用的代码
  • 代码分割(Code Splitting)
  • 使用现代格式的图片

渲染优化

  • 避免重排(reflow)和重绘(repaint)
  • 使用 CSS 动画代替 JavaScript 动画
  • 使用 requestAnimationFrame 处理动画
  • 使用 Web Workers 处理复杂计算
  • 虚拟滚动处理长列表

应用优化

  • 服务端渲染(SSR)或静态站点生成(SSG)
  • 应用 PWA 技术
  • 使用 Web 缓存 API
  • 实现骨架屏(Skeleton Screen)
  • 优化首次内容绘制(FCP)

主要性能指标

  • FCP (First Contentful Paint):首次内容绘制,页面上首次绘制任何文本、图像、非空白 canvas 或 SVG 的时间
  • LCP (Largest Contentful Paint):最大内容绘制,视口中最大的内容元素绘制完成的时间
  • FID (First Input Delay):首次输入延迟,用户首次与页面交互到浏览器响应的时间
  • CLS (Cumulative Layout Shift):累积布局偏移,页面加载过程中元素意外移动的程度
  • TTI (Time to Interactive):可交互时间,页面完全可交互所需的时间
  • TBT (Total Blocking Time):总阻塞时间,FCP 和 TTI 之间主线程被阻塞的总时间
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 性能监测示例
// 使用 Performance API 测量关键指标
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(`${entry.name}: ${entry.startTime}ms`);
}
});

// 监听各种性能指标
observer.observe({ entryTypes: ['paint', 'largest-contentful-paint', 'layout-shift'] });

// 自定义性能标记
performance.mark('app-init-start');
// ... 初始化应用 ...
performance.mark('app-init-end');
performance.measure('app-initialization', 'app-init-start', 'app-init-end');

19. 前端安全问题及防范措施

答案
前端安全是 Web 应用安全的重要组成部分,主要包括以下安全问题和防范措施:

XSS(跨站脚本攻击)

  • 问题:攻击者将恶意脚本注入到网页中,当用户浏览页面时执行
  • 防范措施
    • 对输入输出进行转义和验证
    • 使用 Content-Security-Policy (CSP) 头
    • 使用 HttpOnly 和 Secure 标记保护 Cookie
    • 使用现代框架的内置 XSS 保护
1
2
3
4
5
6
7
8
9
10
11
12
13
// 输入转义示例
function escapeHTML(str) {
return str
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}

// CSP 示例
// 在 HTTP 头中设置
// Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted-cdn.com;

CSRF(跨站请求伪造)

  • 问题:攻击者诱导用户在已认证的网站上执行非预期操作
  • 防范措施
    • 使用 CSRF Token
    • 验证 Origin 和 Referer 头
    • 使用 SameSite Cookie 属性
    • 对敏感操作要求重新认证
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// CSRF Token 示例
// 服务端生成 token 并在表单中包含
const csrfToken = generateRandomToken();
session.csrfToken = csrfToken;

// HTML 表单
<form action="/api/update" method="POST">
<input type="hidden" name="csrf_token" value="{{csrfToken}}">
<!-- 其他表单字段 -->
</form>

// 服务端验证
if (request.body.csrf_token !== session.csrfToken) {
return response.status(403).send('CSRF token 验证失败');
}

点击劫持

  • 问题:攻击者将透明的目标网站覆盖在另一个网站上,诱导用户点击
  • 防范措施
    • 使用 X-Frame-Options 头
    • 使用 CSP 的 frame-ancestors 指令
    • JavaScript 框架防御
1
2
3
4
5
6
7
8
// X-Frame-Options 示例
// 在 HTTP 头中设置
// X-Frame-Options: DENY
// 或
// X-Frame-Options: SAMEORIGIN

// CSP frame-ancestors 示例
// Content-Security-Policy: frame-ancestors 'none';

其他安全问题

  • 中间人攻击:使用 HTTPS 防范
  • 不安全的依赖:定期更新依赖,使用安全扫描工具
  • 敏感信息泄露:避免在前端存储敏感信息,使用环境变量
  • 服务器端请求伪造 (SSRF):验证和限制 URL,使用白名单
  • 本地存储安全:不在 localStorage/sessionStorage 中存储敏感数据

20. 微前端架构的原理和实现方式

答案
微前端是一种将前端应用分解为更小、更易于管理的部分的架构风格,每个部分可以独立开发、测试和部署。

核心原则

  • 技术栈无关:每个微前端可以使用不同的技术栈
  • 独立开发部署:团队可以独立工作,不影响其他团队
  • 运行时集成:微前端在浏览器中组合,而不是构建时
  • 隔离:微前端之间不应相互影响

实现方式

1. 基于路由的分发

  • 不同路由对应不同的微应用
  • 简单易实现,但页面间集成度低
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 简单的路由分发示例
const routes = {
'/app1': loadApp1,
'/app2': loadApp2,
'/app3': loadApp3
};

function router() {
const path = window.location.pathname;
const route = Object.keys(routes).find(route => path.startsWith(route));

if (route) {
routes[route]();
} else {
loadDefaultApp();
}
}

window.addEventListener('popstate', router);
router();

2. 使用 iframe

  • 完全隔离各个应用
  • 但存在通信困难、样式不一致等问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- iframe 集成示例 -->
<div id="container">
<iframe id="micro-frontend" src="https://app1.example.com"></iframe>
</div>

<script>
// 根据某些条件切换 iframe 的 src
function loadApp(appUrl) {
document.getElementById('micro-frontend').src = appUrl;
}

// 应用间通信
window.addEventListener('message', (event) => {
if (event.origin === 'https://app1.example.com') {
console.log('Received message:', event.data);
}
});
</script>

3. Web Components

  • 使用自定义元素封装微前端
  • 提供良好的封装性和互操作性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// Web Components 示例
class MicroApp extends HTMLElement {
connectedCallback() {
const appName = this.getAttribute('name');
const appUrl = this.getAttribute('url');

// 加载微应用
fetch(`${appUrl}/asset-manifest.json`)
.then(res => res.json())
.then(manifest => {
// 加载 JS 和 CSS
this.loadResources(manifest, appUrl);
});
}

loadResources(manifest, baseUrl) {
// 加载 CSS
if (manifest.css) {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = `${baseUrl}/${manifest.css}`;
this.appendChild(link);
}

// 加载 JS
if (manifest.js) {
const script = document.createElement('script');
script.src = `${baseUrl}/${manifest.js}`;
script.onload = () => {
// 调用微应用的挂载函数
window[`mount${this.getAttribute('name')}`](this);
};
this.appendChild(script);
}
}

disconnectedCallback() {
// 调用微应用的卸载函数
const appName = this.getAttribute('name');
if (window[`unmount${appName}`]) {
window[`unmount${appName}`]();
}
}
}

customElements.define('micro-app', MicroApp);

4. JavaScript 模块加载

  • 动态加载 JavaScript 模块
  • 灵活性高,但需要处理好依赖和冲突
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 动态加载模块示例
function loadApp(name, url) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = url;
script.onload = () => {
resolve(window[name]);
};
script.onerror = reject;
document.head.appendChild(script);
});
}

// 使用
loadApp('app1', 'https://app1.example.com/bundle.js')
.then(app => {
app.mount(document.getElementById('app1-container'));
});

5. 使用微前端框架

  • single-spa:JavaScript 微前端框架
  • qiankun:基于 single-spa 的增强框架
  • Module Federation:Webpack 5 提供的模块联邦功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// single-spa 示例
import { registerApplication, start } from 'single-spa';

// 注册应用
registerApplication({
name: 'app1',
app: () => import('app1'),
activeWhen: '/app1'
});

registerApplication({
name: 'app2',
app: () => import('app2'),
activeWhen: '/app2'
});

// 启动
start();

挑战与解决方案

  • 样式隔离:使用 CSS Modules、CSS-in-JS 或 Shadow DOM
  • 共享依赖:使用 Webpack 的 Module Federation 或 import maps
  • 通信机制:使用自定义事件、发布订阅模式或全局状态管理
  • 认证授权:使用 SSO 或 token 共享

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器