第七章 前端工程化-上
# 第七章 前端工程化-上
# 一、前端工程化开篇
# 1.1 什么是前端工程化
前端工程化概念:前端工程化是使用软件工程的方法来系统性解决前端开发流程中模块化、组件化、规范化、自动化的问题,其主要目的是提高开发效率和降低维护成本。
前端工程化发展历程:前端开发经历了从简单到复杂的演进过程:
| 阶段 | 时期 | 特点 | 主要技术 |
|---|---|---|---|
| 蛮荒时代 | ~2010 | HTML/CSS/JS 混编,代码难以维护 | jQuery、原生 JS |
| 工具化 | 2010-2014 | 引入构建工具,代码压缩和合并 | Grunt、Gulp、Less、Sass |
| 模块化 | 2014-2016 | 模块化规范,依赖管理 | RequireJS、SeaJS、Browserify |
| 工程化 | 2016-至今 | 完整的工程化体系,组件化开发 | Webpack、Vite、Vue、React |
前端工程化核心价值
- 模块化:将复杂系统拆分为独立模块,便于开发和维护
- 组件化:UI 组件复用,提高开发效率
- 规范化:统一代码风格、开发流程,降低协作成本
- 自动化:自动构建、测试、部署,减少人工错误
# 1.2 前端工程化实现技术栈
技术栈:
| 技术 | 版本建议 | 作用 | 选型原因 |
|---|---|---|---|
| ECMAScript 6+ | ES2015+ | JavaScript 语言标准 | Vue3 大量使用 ES6+语法,提供更简洁的代码 |
| Node.js | 18.x LTS | JavaScript 运行环境 | 提供前端项目运行环境,支持 npm 生态 |
| npm | 9.x+ | 包管理工具 | 管理项目依赖,全球最大的包管理生态 |
| Vite | 5.x | 前端构建工具 | 快速的冷启动、HMR 热更新,基于 ESM |
| Vue3 | 3.x | 渐进式前端框架 | 组合式 API、更好的性能、TypeScript 支持 |
| Vue Router | 4.x | 路由管理 | 实现 SPA 单页面应用的页面切换 |
| Pinia | 2.x | 状态管理 | Vue3 官方推荐,替代 Vuex,更简洁的 API |
| Axios | 1.x | HTTP 客户端 | 封装 Ajax 请求,实现前后端数据交互 |
| Element-plus | 2.x | UI 组件库 | 丰富的 Vue3 组件,快速构建企业级应用 |
版本兼容性说明
- Node.js 建议使用 LTS(长期支持)版本,确保稳定性
- Vue3 与 Vue Router 4、Pinia 2 配套使用
- Element-plus 仅支持 Vue3,Vue2 项目请使用 Element-UI
开发环境要求:
# 推荐的开发环境配置
Node.js: >= 18.0.0 (建议18.x LTS)
npm: >= 9.0.0
编辑器:VS Code (推荐安装Volar插件)
浏览器:Chrome/Edge最新版
2
3
4
5
# 二、ECMA6Script
# 2.1 ES6 的介绍
# 2.1.1 什么是 ES6
ECMAScript 6(简称 ES6 或 ES2015)是 JavaScript 语言的一次重大更新,于 2015 年正式发布。ES6 带来了大量新特性,包括箭头函数、模板字符串、let/const 关键字、解构、类、模块系统等,极大提升了 JavaScript 的开发体验。
学习前提
Vue3 中大量使用 ES6+ 语法,掌握 ES6 是学习 Vue3 的必备基础。
# 2.1.2 ES6 的核心改进
| 改进方向 | 说明 | 典型特性 |
|---|---|---|
| 语法简洁 | 使代码更易读易写 | 箭头函数、模板字符串、解构赋值 |
| 功能增强 | 提供更强大的语言能力 | Promise、Proxy、Reflect、Symbol |
| 模块化 | 原生支持模块系统 | import/export、动态导入 |
| 类与继承 | 更清晰的面向对象编程 | class、extends、super |
| 异步编程 | 简化异步代码编写 | async/await、Promise |
# 2.1.3 ECMAScript 版本演进
| 标准版本 | 发布时间 | 新特性 | 备注 |
|---|---|---|---|
| ES1 | 1997 年 | 第一版 ECMAScript | |
| ES2 | 1998 年 | 引入 setter 和 getter 函数,增加了 try/catch,switch 语句允许字符串 | |
| ES3 | 1999 年 | 引入了正则表达式和更好的字符串处理 | |
| ES4 | 取消 | 取消,部分特性被 ES3.1 和 ES5 继承 | |
| ES5 | 2009 年 | Object.defineProperty,JSON,严格模式,数组新增方法等 | 广泛兼容的稳定版本 |
| ES5.1 | 2011 年 | 对 ES5 做了一些勘误和例行修订 | |
| ES6 | 2015 年 | 箭头函数、模板字符串、解构、let 和 const 关键字、类、模块系统等 | 里程碑式更新 |
| ES2016 | 2016 年 | 数组.includes,**指数操作符,Array.prototype.fill 等 | 年度更新开始 |
| ES2017 | 2017 年 | 异步函数 async/await,Object.values/Object.entries,字符串填充 | 异步编程革新 |
| ES2018 | 2018 年 | 正则表达式命名捕获组,几个有用的对象方法,异步迭代器等 | |
| ES2019 | 2019 年 | Array.prototype.{flat,flatMap},Object.fromEntries 等 | |
| ES2020 | 2020 年 | BigInt、动态导入、可选链操作符?.、空位合并操作符?? | Vue3 常用 |
| ES2021 | 2021 年 | String.prototype.replaceAll,逻辑赋值运算符,Promise.any 等 | 持续演进 |
| ... ... |
浏览器兼容性
现代浏览器(Chrome 60+、Firefox 60+、Safari 12+)已全面支持 ES6 特性。对于旧版本浏览器,可使用 Babel 进行转译。
# 2.2 ES6 的变量和模板字符串
# 2.2.1 变量声明:let、const、var
ES6 新增了 let 和 const 关键字,用于声明变量,与传统的 var 相比,提供了更严格、更安全的变量管理机制。
三种声明方式对比:
| 特性 | var | let | const |
|---|---|---|---|
| 作用域 | 函数作用域 | 块级作用域 | 块级作用域 |
| 重复声明 | ✅ 允许 | ❌ 报错 | ❌ 报错 |
| 变量提升 | ✅ 提升到顶部(值为 undefined) | ❌ 暂时性死区(TDZ) | ❌ 暂时性死区(TDZ) |
| 重新赋值 | ✅ 允许 | ✅ 允许 | ❌ 不允许 |
| 初始化要求 | ❌ 可选 | ❌ 可选 | ✅ 必须初始化 |
| 全局属性 | ✅ 成为 window 属性 | ❌ 不会 | ❌ 不会 |
术语解释
暂时性死区(Temporal Dead Zone, TDZ):在代码块内,使用 let/const 声明变量之前,该变量都是不可用的,访问会报错。这个区域称为"暂时性死区"。
<script>
// ========== 1. 块级作用域 ==========
{
let a = 1
var b = 2
}
// console.log(a); // ❌ ReferenceError: a is not defined(let是块级作用域)
console.log(b); // ✅ 2(var是函数作用域,没有块级限制)
// 实际应用:防止变量泄漏
if (true) {
let temp = 'temporary value'
var global = 'global value'
}
// console.log(temp); // ❌ 报错,temp仅在if块内有效
console.log(global); // ✅ 可访问,var没有块级作用域
// ========== 2. 不能重复声明 ==========
let name = '张三'
// let name = '李四' // ❌ SyntaxError: Identifier 'name' has already been declared
var age = 18
var age = 20 // ✅ var允许重复声明(不推荐)
// ========== 3. 暂时性死区(TDZ) ==========
console.log(test) // undefined(var变量提升,但未赋值)
var test = 'test'
// console.log(test1) // ❌ ReferenceError: Cannot access 'test1' before initialization
let test1 = 'test1' // let不会提升
// ========== 4. 不会成为全局对象属性 ==========
var x = 100
let y = 200
console.log(window.x) // 100(var声明的全局变量成为window属性)
console.log(window.y) // undefined(let不会污染全局对象)
// ========== 5. 循环中的经典问题 ==========
// ✅ let:每次迭代创建新的变量绑定
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log('let:', i), 100) // 输出: 0, 1, 2
}
// ❌ var:所有迭代共享同一个变量
for (var j = 0; j < 3; j++) {
setTimeout(() => console.log('var:', j), 100) // 输出: 3, 3, 3
}
// 实际应用:事件监听
const buttons = document.querySelectorAll('button')
for (let i = 0; i < buttons.length; i++) {
buttons[i].onclick = () => console.log(`点击了第 ${i} 个按钮`)
// 使用let,每个按钮都能正确记录自己的索引
}
</script>
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
使用建议
- 优先使用
let:需要重新赋值的变量 - 优先使用
const:不需要重新赋值的变量(包括对象、数组) - 避免使用
var:除非需要兼容老代码
# 2.2.2 const 常量声明
const 用于声明常量,与 let 类似,但有更严格的限制。
- 必须在声明时初始化
- 不能重新赋值(指向的内存地址不能改变)
- 块级作用域
- 不存在变量提升(有暂时性死区)
重要概念:const 保证的是什么?
const 保证的是变量指向的内存地址不可变,而非值不可变:
- 基本类型(number、string、boolean 等):值直接存储在内存地址中,等同于值不可变
- 引用类型(object、array 等):内存地址存储的是指针,
const只保证指针不变,对象内部属性可以修改
<script>
// ========== 1. 必须初始化 ==========
const PI = 3.1415926;
// const A; // ❌ SyntaxError: Missing initializer in const declaration
// ========== 2. 不能重新赋值 ==========
const NAME = 'Vue3'
// NAME = 'React' // ❌ TypeError: Assignment to constant variable
// ========== 3. 块级作用域 ==========
{
const A = 'block scope'
console.log(A); // 'block scope'
}
// console.log(A); // ❌ ReferenceError: A is not defined
// ========== 4. 引用类型的特殊性 ==========
// 4.1 数组(引用类型)
const TEAM = ['刘德华', '张学友', '郭富城'];
// ✅ 可以修改数组内容(内存地址不变)
TEAM.push('黎明');
TEAM[0] = '周星驰';
console.log(TEAM); // ['周星驰', '张学友', '郭富城', '黎明']
// ❌ 不能重新赋值(改变内存地址)
// TEAM = []; // TypeError: Assignment to constant variable
// TEAM = ['新数组']; // TypeError: Assignment to constant variable
// 4.2 对象(引用类型)
const USER = {
name: '张三',
age: 18
};
// ✅ 可以修改、添加、删除属性
USER.name = '李四'; // 修改属性
USER.gender = 'male'; // 添加属性
delete USER.age; // 删除属性
console.log(USER); // { name: '李四', gender: 'male' }
// ❌ 不能重新赋值
// USER = {}; // TypeError: Assignment to constant variable
// ========== 5. 真正的常量:冻结对象 ==========
const CONFIG = Object.freeze({
API_URL: 'https://api.example.com',
TIMEOUT: 5000
});
// CONFIG.API_URL = 'new url'; // ❌ 严格模式下报错,非严格模式静默失败
console.log(CONFIG.API_URL); // 'https://api.example.com'(未改变)
</script>
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
实际应用场景:
// ✅ 推荐:配置常量
const MAX_SIZE = 100
const API_BASE_URL = 'https://api.example.com'
// ✅ 推荐:不会重新赋值的对象/数组
const userInfo = { name: 'Alice', age: 20 }
const menuList = ['首页', '产品', '关于']
// ✅ 推荐:函数声明(函数表达式)
const calculateTotal = (price, count) => price * count
// ✅ 推荐:模块导入
import { ref, reactive } from 'vue'
const router = useRouter()
2
3
4
5
6
7
8
9
10
11
12
13
14
最佳实践
- 默认使用
const:除非明确知道变量需要重新赋值 - 需要重新赋值时用
let:如循环计数器、临时变量 - 避免使用
var:仅在维护老代码时使用 - 冻结对象:需要完全不可变的对象时使用
Object.freeze()
# 2.2.3 模板字符串
模板字符串(Template Literal)是增强版的字符串,使用反引号 ` 标识,提供了更强大的字符串处理能力。
核心特性:
- 支持多行字符串:无需使用
\n或字符串拼接 - 支持变量插值:使用
${expression}嵌入变量或表达式 - 支持表达式计算:
${}内可以是任意 JavaScript 表达式 - 保留格式:保留所有空格和缩进
<script>
// ========== 1. 多行字符串 ==========
// ❌ 传统方式:使用 + 拼接(繁琐)
let ulStr =
'<ul>' +
'<li>JAVA</li>' +
'<li>HTML</li>' +
'<li>VUE</li>' +
'</ul>'
// ✅ 模板字符串:直接换行(简洁)
let ulStr2 = `
<ul>
<li>JAVA</li>
<li>HTML</li>
<li>VUE</li>
</ul>`
// ========== 2. 变量插值 ==========
let name = '张小明'
let score = 95
// ❌ 传统拼接:使用 + 连接(易错)
let infoStr = name + '被评为本年级优秀学员,成绩:' + score + '分'
// ✅ 模板字符串:使用 ${} 插值(清晰)
let infoStr2 = `${name}被评为本年级优秀学员,成绩:${score}分`
console.log(infoStr2) // 张小明被评为本年级优秀学员,成绩:95分
// ========== 3. 表达式计算 ==========
let a = 10
let b = 20
// ${} 内可以是任意表达式
console.log(`${a} + ${b} = ${a + b}`) // 10 + 20 = 30
console.log(`较大值是:${a > b ? a : b}`) // 较大值是:20
console.log(`随机数:${Math.random()}`) // 随机数:0.xxx
// ========== 4. 调用函数 ==========
function formatDate(date) {
return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`
}
console.log(`今天是:${formatDate(new Date())}`) // 今天是:2025-11-26
// ========== 5. 嵌套模板字符串 ==========
const users = [
{ name: '张三', age: 20 },
{ name: '李四', age: 22 }
]
const userList = `
<ul>
${users.map(user => `
<li>${user.name} - ${user.age}岁</li>
`).join('')}
</ul>
`
console.log(userList)
</script>
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
实际应用场景:
// ✅ 场景1:动态生成HTML(Vue模板之外)
const createCard = (title, content) => `
<div class="card">
<h3>${title}</h3>
<p>${content}</p>
</div>
`
// ✅ 场景2:构建URL
const userId = 123
const apiUrl = `https://api.example.com/users/${userId}/posts?page=1&size=10`
// ✅ 场景3:错误消息
const showError = (field, value) => {
console.error(`验证失败:${field} 的值 "${value}" 不符合要求`)
}
// ✅ 场景4:SQL语句(注意:实际项目要防SQL注入)
const username = 'admin'
const query = `SELECT * FROM users WHERE username = '${username}'`
// ✅ 场景5:日志输出
const log = (level, message) => {
console.log(`[${new Date().toISOString()}] [${level}] ${message}`)
}
log('INFO', '用户登录成功')
// 输出: [2025-11-26T10:30:00.000Z] [INFO] 用户登录成功
// ✅ 场景6:Vue3 组合式API中
import { ref } from 'vue'
const username = ref('张三')
const greeting = `欢迎回来,${username.value}!`
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
模板字符串 vs 普通字符串
| 特性 | 普通字符串 | 模板字符串 |
|---|---|---|
| 定界符 | ' 或 " | ` |
| 多行 | 需要 \n | 直接换行 |
| 变量插值 | 使用 + 拼接 | 使用 ${} |
| 表达式 | 需要先计算再拼接 | 直接在 ${} 内计算 |
| 可读性 | 较差(拼接复杂) | 较好(接近自然语言) |
注意事项
- 反引号转义:模板字符串内使用反引号需要转义:
` - 性能考虑:大量字符串拼接建议使用数组
join()方法 - 安全问题:不要直接插入用户输入,防止 XSS 攻击
# 2.2.4 ES2020+ 常用新特性
以下是 ES2020 及之后版本中常用的新特性,在 Vue3 开发中经常使用,掌握这些特性能大幅提升代码质量。
1. 可选链操作符 ?. (Optional Chaining,ES2020)
安全地访问深层嵌套的对象属性,避免因中间属性为 null 或 undefined 而报错。
const user = {
name: '张三',
address: {
city: '北京'
}
}
// ❌ 传统写法:需要逐层判断(繁琐且易错)
const city1 = user && user.address && user.address.city
// ✅ 可选链写法:简洁安全
const city2 = user?.address?.city // '北京'
const street = user?.address?.street // undefined(不会报错)
// 配合方法调用
const result = user?.getName?.() // 如果 getName 存在则调用,否则返回 undefined
// 配合数组访问
const arr = [1, 2, 3]
const first = arr?.[0] // 1
const invalid = null?.[0] // undefined(不会报错)
// 链式调用
const value = obj?.prop1?.prop2?.prop3 // 任意一层为 null/undefined 都返回 undefined
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
在 Vue3 中的应用:
<script setup>
import { ref } from 'vue'
// 场景1:访问 props 深层属性
const props = defineProps({
user: Object
})
const userName = props.user?.name ?? '未知用户'
const userCity = props.user?.address?.city ?? '未知城市'
// 场景2:API 响应数据安全访问
const apiData = ref(null)
const fetchData = async () => {
const response = await fetch('/api/users')
apiData.value = await response.json()
}
// 使用数据时安全访问
const firstUserName = apiData.value?.data?.[0]?.name
// 场景3:事件对象安全访问
const handleClick = (event) => {
const targetValue = event?.target?.value
console.log(targetValue)
}
// 场景4:动态调用可能不存在的方法
const callback = props.onSuccess
callback?.() // 如果存在则调用,不存在也不报错
</script>
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
2. 空值合并操作符 ?? (Nullish Coalescing,ES2020)
只有当左侧为 null 或 undefined 时,才返回右侧的值。与 || 不同,?? 不会将 0、''、false 视为假值。
// ========== || 与 ?? 的区别 ==========
// ❌ || 的问题:0、''、false 都被视为假值
const count1 = 0 || 10 // 10(不符合预期!)
const name1 = '' || '默认名称' // '默认名称'(不符合预期!)
const flag1 = false || true // true(不符合预期!)
// ✅ ?? 只处理 null 和 undefined
const count2 = 0 ?? 10 // 0(符合预期)
const name2 = '' ?? '默认名称' // ''(符合预期)
const flag2 = false ?? true // false(符合预期)
// null 和 undefined 才会使用默认值
const value1 = null ?? '默认值' // '默认值'
const value2 = undefined ?? '默认值' // '默认值'
2
3
4
5
6
7
8
9
10
11
12
13
14
15
在 Vue3 中的应用:
<script setup>
import { ref, computed } from 'vue'
// 场景1:props 默认值(推荐使用 ?? 而不是 ||)
const props = defineProps({
count: Number,
title: String,
isActive: Boolean
})
// ✅ 正确:使用 ?? 处理默认值
const displayCount = computed(() => props.count ?? 0) // count为0时显示0,不是10
const displayTitle = computed(() => props.title ?? '无标题') // title为''时显示''
const isEnabled = computed(() => props.isActive ?? false) // isActive为false时显示false
// ❌ 错误:使用 || 会导致意外结果
const wrongCount = computed(() => props.count || 10) // count为0时错误显示10
// 场景2:配置对象合并
const defaultConfig = {
timeout: 5000,
retries: 3
}
const userConfig = {
timeout: 0, // 用户明确设置为0
retries: null
}
const finalConfig = {
timeout: userConfig.timeout ?? defaultConfig.timeout, // 0(保留用户设置)
retries: userConfig.retries ?? defaultConfig.retries // 3(使用默认值)
}
// 场景3:API 响应处理
const fetchUser = async (id) => {
const response = await fetch(`/api/users/${id}`)
const data = await response.json()
return {
name: data.name ?? '匿名用户',
age: data.age ?? 18,
score: data.score ?? 0 // score可能是0,使用??而不是||
}
}
</script>
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
3. 逻辑赋值操作符(ES2021)
结合逻辑运算符和赋值操作,简化代码。
// ========== 1. 逻辑或赋值 ||= ==========
let a = null
a ||= '默认值' // 等价于: a = a || '默认值'
console.log(a) // '默认值'
let b = '原有值'
b ||= '默认值' // 不会改变,因为b是真值
console.log(b) // '原有值'
// ========== 2. 逻辑与赋值 &&= ==========
let c = '有值'
c &&= '新值' // 等价于: c = c && '新值'
console.log(c) // '新值'
let d = null
d &&= '新值' // 不会赋值,因为d是假值
console.log(d) // null
// ========== 3. 空值合并赋值 ??= ==========
let e = null
e ??= '默认值' // 等价于: e = e ?? '默认值'
console.log(e) // '默认值'
let f = 0
f ??= 10 // 不会改变,因为f不是null/undefined
console.log(f) // 0
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
在 Vue3 中的应用:
<script setup>
import { reactive, ref } from 'vue'
// 场景1:状态初始化
const state = reactive({
user: null,
config: {}
})
// 使用 ??= 确保有默认值
state.user ??= { name: '访客', role: 'guest' }
state.config.theme ??= 'light'
// 场景2:缓存数据
const cache = ref({})
const getData = (key) => {
// 如果缓存中没有,则从API获取
cache.value[key] ??= fetchFromAPI(key)
return cache.value[key]
}
// 场景3:累加操作
const stats = reactive({
views: 0,
likes: 0
})
const incrementViews = () => {
stats.views ||= 0 // 确保是数字
stats.views++
}
</script>
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
4. 三者对比总结
| 操作符 | 触发条件 | 返回右侧值的情况 | 常用场景 |
|---|---|---|---|
| | | 左侧为假值 | false、0、''、null、undefined、NaN | 旧代码兼容 |
?? | 左侧为空值 | null、undefined | 推荐:设置默认值 |
?. | 属性不存在 | 访问路径上任意节点为 null/undefined | 推荐:安全访问 |
Vue3 最佳实践建议
- 优先使用
??而不是||来设置默认值,避免将0、''、false误判 - 使用
?.访问深层属性,避免手动检查每一层,代码更简洁安全 - 组合使用:
user?.name ?? '默认用户'(先安全访问,再设置默认值) - 响应式数据:在访问
ref时注意.value,如user.value?.name ?? '默认'
常见陷阱
// ❌ 陷阱1:混淆 || 和 ??
const count = 0
const result1 = count || 10 // 10(错误!)
const result2 = count ?? 10 // 0(正确)
// ❌ 陷阱2:可选链不能用于赋值
// obj?.prop = 'value' // SyntaxError
// ✅ 正确:先检查再赋值
if (obj) obj.prop = 'value'
// ❌ 陷阱3:可选链短路特性
let count = 0
obj?.func?.(count++) // 如果obj或func不存在,count++不会执行
console.log(count) // 可能还是0
// ❌ 陷阱4:不能与 || 和 && 直接混用
// const value = a ?? b || c // SyntaxError
const value = (a ?? b) || c // ✅ 需要加括号明确优先级
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 2.3 ES6 的解构表达式
解构赋值(Destructuring Assignment)是 ES6 提供的一种便捷语法,可以快速从数组或对象中提取值并赋给变量。它使代码更简洁、更易读。
# 2.3.1 数组解构
基本语法:
// ========== 1. 基本数组解构 ==========
let [a, b, c] = [1, 2, 3]
console.log(a) // 1
console.log(b) // 2
console.log(c) // 3
// ========== 2. 跳过元素 ==========
let [first, , third] = [1, 2, 3]
console.log(first) // 1
console.log(third) // 3
// ========== 3. 默认值 ==========
let [x, y, z = 10] = [1, 2]
console.log(z) // 10(使用默认值)
let [m, n, p = 10] = [1, 2, 3]
console.log(p) // 3(实际值覆盖默认值)
// ========== 4. 剩余元素(Rest) ==========
let [head, ...tail] = [1, 2, 3, 4, 5]
console.log(head) // 1
console.log(tail) // [2, 3, 4, 5]
// ========== 5. 交换变量 ==========
let a1 = 1, b1 = 2;
[a1, b1] = [b1, a1]
console.log(a1, b1) // 2, 1
// ========== 6. 嵌套数组解构 ==========
const matrix = [[1, 2], [3, 4]]
const [[a2, b2], [c2, d2]] = matrix
console.log(a2, b2, c2, d2) // 1 2 3 4
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
# 2.3.2 对象解构
基本语法:
// ========== 1. 基本对象解构 ==========
// 变量名必须与属性名匹配
let {name, age} = {name: '张三', age: 18}
console.log(name) // '张三'
console.log(age) // 18
// ========== 2. 重命名变量 ==========
// 语法:{ 属性名: 新变量名 }
let {name: userName, age: userAge} = {name: '李四', age: 20}
console.log(userName) // '李四'
console.log(userAge) // 20
// ========== 3. 默认值 ==========
let {x1, y1, z1 = 100} = {x1: 1, y1: 2}
console.log(z1) // 100
// 重命名 + 默认值
let {name: n = '匿名', age: a = 0} = {name: '王五'}
console.log(n) // '王五'
console.log(a) // 0
// ========== 4. 嵌套对象解构 ==========
const user = {
name: '张三',
address: {
city: '北京',
district: '朝阳区'
},
scores: {
math: 90,
english: 85
}
}
// 提取嵌套属性
const {
name,
address: { city, district },
scores: { math }
} = user
console.log(name) // '张三'
console.log(city) // '北京'
console.log(district) // '朝阳区'
console.log(math) // 90
// ⚠️ 注意:address 本身没有被解构出来
// console.log(address) // ReferenceError
// ========== 5. 剩余属性(Rest) ==========
const {a3, b3, ...rest} = {a3: 1, b3: 2, c: 3, d: 4}
console.log(a3) // 1
console.log(b3) // 2
console.log(rest) // {c: 3, d: 4}
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
# 2.3.3 函数参数解构
在 Vue3 开发中,函数参数解构非常常见,特别是在组合式 API 中。
// ========== 1. 对象参数解构(最常用) ==========
function greet({ name, age = 18 }) {
console.log(`${name} 今年 ${age} 岁`)
}
greet({ name: '张三' }) // 张三 今年 18 岁
greet({ name: '李四', age: 20 }) // 李四 今年 20 岁
// ========== 2. 数组参数解构 ==========
function sum([x, y]) {
return x + y
}
sum([10, 20]) // 30
// ========== 3. 嵌套参数解构 ==========
function displayUser({
name,
address: { city }
}) {
console.log(`${name} 来自 ${city}`)
}
displayUser({
name: '王五',
address: { city: '上海' }
}) // 王五 来自 上海
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
在 Vue3 中的应用:
<script setup>
import { ref, reactive, toRefs } from 'vue'
// ========== 场景1:解构 props(需要 toRefs 保持响应性) ==========
const props = defineProps({
title: String,
count: Number,
user: Object
})
// ❌ 错误:直接解构会失去响应性
// const { title, count } = props
// ✅ 正确:使用 toRefs 保持响应性
const { title, count, user } = toRefs(props)
// ========== 场景2:解构 reactive 对象 ==========
const state = reactive({
userName: '张三',
userAge: 18,
isLogin: false
})
// ❌ 错误:直接解构失去响应性
// const { userName, userAge } = state
// ✅ 正确:使用 toRefs
const { userName, userAge, isLogin } = toRefs(state)
// ========== 场景3:解构组合函数返回值 ==========
function useCounter() {
const count = ref(0)
const increment = () => count.value++
return { count, increment }
}
// 解构使用
const { count, increment } = useCounter()
// ========== 场景4:解构 API 响应 ==========
const fetchUser = async () => {
const response = await fetch('/api/user')
const { data, code, message } = await response.json()
if (code === 200) {
const { id, name, email } = data
console.log(id, name, email)
}
}
// ========== 场景5:v-for 中解构 ==========
// 在模板中
// <div v-for="{ id, name, age } in users" :key="id">
// {{ name }} - {{ age }}岁
// </div>
</script>
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
# 2.3.4 默认值使用场景
// ========== 1. 避免 undefined 错误 ==========
function createUser({
name = '匿名用户',
age = 0,
role = 'guest'
} = {}) { // ⚠️ 注意:= {} 避免传入 undefined 报错
return { name, age, role }
}
createUser() // {name: '匿名用户', age: 0, role: 'guest'}
createUser({ name: '张三' }) // {name: '张三', age: 0, role: 'guest'}
createUser({ name: '李四', age: 20 }) // {name: '李四', age: 20, role: 'guest'}
// ========== 2. 配置对象合并 ==========
const defaultConfig = {
timeout: 5000,
retries: 3,
cache: true
}
function request({
url,
timeout = defaultConfig.timeout,
retries = defaultConfig.retries,
cache = defaultConfig.cache
}) {
console.log({ url, timeout, retries, cache })
}
request({ url: '/api/users' })
// { url: '/api/users', timeout: 5000, retries: 3, cache: true }
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
# 2.3.5 常见陷阱与注意事项
陷阱 1:解构 undefined 或 null
// ❌ 报错:Cannot destructure property 'name' of 'undefined'
const { name } = undefined
// ✅ 解决方案1:使用默认值
const { name } = undefined || {}
// ✅ 解决方案2:可选链 + 空对象
const { name: name2 } = obj?.data ?? {}
// ✅ 解决方案3:函数参数默认值
function fn({ name } = {}) {
console.log(name)
}
fn() // undefined(不报错)
2
3
4
5
6
7
8
9
10
11
12
13
14
陷阱 2:解构失去响应性(Vue3)
// ❌ 错误:失去响应性
const state = reactive({ count: 0 })
const { count } = state // count 不再是响应式
// ✅ 正确:使用 toRefs
const { count } = toRefs(state)
// ❌ 错误:解构 ref
const count = ref(0)
const { value } = count // value 失去响应性
// ✅ 正确:直接使用 count.value
2
3
4
5
6
7
8
9
10
11
12
陷阱 3:变量名冲突
// ❌ 错误:变量名已存在
let name = '张三'
let { name } = { name: '李四' } // SyntaxError
// ✅ 解决方案:重命名
let { name: newName } = { name: '李四' }
2
3
4
5
6
陷阱 4:嵌套解构丢失中间对象
const user = {
address: { city: '北京' }
}
// ❌ 只提取了 city,address 未定义
const { address: { city } } = user
console.log(city) // '北京'
console.log(address) // ReferenceError
// ✅ 同时提取 address 和 city
const { address, address: { city: city2 } } = user
console.log(address) // { city: '北京' }
console.log(city2) // '北京'
2
3
4
5
6
7
8
9
10
11
12
13
最佳实践
- 优先使用解构:提高代码可读性
- 提供默认值:避免
undefined错误 - Vue3 中使用
toRefs:保持响应性 - 合理命名:使用
:重命名避免冲突 - 适度嵌套:过深的嵌套降低可读性
实际应用总结:
| 场景 | 示例 | 说明 |
|---|---|---|
| 交换变量 | [a, b] = [b, a] | 无需临时变量 |
| 函数多返回值 | const [data, error] = await fetch() | 类似元组 |
| 提取对象属性 | const { name, age } = user | 简化访问 |
| Vue3 props | const { title } = toRefs(props) | 保持响应性 |
| API 响应处理 | const { data, code } = res | 快速提取 |
| 配置对象 | fn({ timeout = 5000 } = {}) | 默认值 |
# 2.4 ES6 箭头函数
ES6 允许使用"箭头"(=>)定义函数,语法类似 Java 中的 Lambda 表达式。箭头函数提供了更简洁的语法和词法作用域的 this 绑定。
# 2.4.1 基本语法与简写形式
箭头函数支持多种简写形式,根据参数数量和函数体复杂度可灵活选择
// 1. 标准函数声明 vs 箭头函数
let fn1 = function(x, y) { return x + y; } // 传统函数
let fn2 = (x, y) => { return x + y; } // 箭头函数(完整形式)
// 2. 参数简写规则
let fn3 = () => {} // 无参数:必须使用 ()
let fn4 = x => {} // 单参数:可省略 ()
let fn5 = (x, y) => {} // 多参数:必须使用 ()
let fn6 = (x = 0) => {} // 默认参数:必须使用 ()
// 3. 函数体简写规则
let fn7 = x => console.log(x) // 单行语句:可省略 {} 和 return
let fn8 = x => x * 2 // 单行返回值:省略 {} 和 return
let fn9 = x => ({ name: x }) // 返回对象字面量:必须用 () 包裹
let fn10 = x => { // 多行语句:必须使用 {}
let result = x * 2;
return result;
}
// 4. 实际应用示例
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2); // [2, 4, 6, 8, 10]
const evens = numbers.filter(n => n % 2 === 0); // [2, 4]
const sum = numbers.reduce((acc, n) => acc + n, 0); // 15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
简写规则记忆
- 参数:单个参数可省略
(),其他情况必须保留 - 函数体:单行表达式可省略
{},隐式返回结果 - 对象返回:返回对象字面量时必须用
({ key: value })包裹,避免与函数体的{}混淆
# 2.4.2 this 绑定机制深度解析
核心区别:箭头函数没有自己的 this,它的 this 继承自定义时的外层作用域(词法作用域),而普通函数的 this 在调用时动态确定。
// 示例1: 对象方法中的 this 差异
let person = {
name: "张三",
age: 25,
// 普通函数: this 指向调用对象
showName: function() {
console.log(this); // { name: "张三", age: 25, ... }
console.log(this.name); // "张三"
},
// 箭头函数: this 指向定义时的外层作用域(window/global)
viewName: () => {
console.log(this); // Window {...} (浏览器环境)
console.log(this.name); // undefined (window.name 为空)
}
};
person.showName(); // 正常输出 "张三"
person.viewName(); // 输出 undefined
// 示例2: 定时器中的 this 应用(箭头函数的优势场景)
function Counter() {
this.count = 0;
// 方案1: 传统解决方案(需要保存 this 引用)
let _this = this;
setInterval(function() {
_this.count++; // 必须使用 _this
console.log(_this.count);
}, 1000);
// 方案2: 箭头函数方案(自动继承外层 this)
setInterval(() => {
this.count++; // 直接使用 this,指向 Counter 实例
console.log(this.count);
}, 1000);
}
let counter = new Counter();
// 示例3: 事件处理器中的 this
class Button {
constructor(label) {
this.label = label;
this.clickCount = 0;
}
// 普通方法: this 会丢失
handleClick1() {
this.clickCount++; // 如果作为回调,this 会指向 DOM 元素
console.log(`${this.label} clicked ${this.clickCount} times`);
}
// 箭头函数: this 绑定到类实例
handleClick2 = () => {
this.clickCount++; // this 始终指向 Button 实例
console.log(`${this.label} clicked ${this.clickCount} times`);
}
}
const btn = new Button('Submit');
document.querySelector('#myBtn').addEventListener('click', btn.handleClick2); // ✅ 正常工作
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
this 绑定陷阱
// ❌ 错误用法: 对象方法使用箭头函数
const obj = {
value: 42,
getValue: () => this.value // this 指向全局,而非 obj
};
obj.getValue(); // undefined
// ✅ 正确用法: 对象方法使用普通函数
const obj2 = {
value: 42,
getValue() { return this.value; }
};
obj2.getValue(); // 42
2
3
4
5
6
7
8
9
10
11
12
13
# 2.4.3 Vue3 中的箭头函数应用
在 Vue3 Composition API 中,箭头函数的使用场景和注意事项:
<script setup>
import { ref, reactive, computed, watch } from 'vue';
const count = ref(0);
const user = reactive({ name: '张三', age: 25 });
// ✅ 推荐: computed/watch 回调使用箭头函数
const doubleCount = computed(() => count.value * 2);
watch(() => user.name, (newName, oldName) => {
console.log(`Name changed: ${oldName} -> ${newName}`);
});
// ✅ 推荐: 事件处理器使用箭头函数
const handleClick = () => {
count.value++; // 直接访问响应式数据
};
// ✅ 推荐: 定时器/Promise 回调使用箭头函数
const fetchData = () => {
setTimeout(() => {
count.value = 100; // 自动继承外层作用域,无需 _this
}, 1000);
};
// ❌ 避免: 在 methods 对象中使用箭头函数(Vue2 Options API)
// export default {
// data() {
// return { count: 0 };
// },
// methods: {
// increment: () => {
// this.count++; // ❌ this 不指向 Vue 实例
// }
// }
// };
</script>
<template>
<button @click="handleClick">Count: {{ count }}</button>
<p>Double: {{ doubleCount }}</p>
</template>
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
Vue3 最佳实践
- Composition API:优先使用箭头函数(setup 内部无需 this)
- Options API:methods/computed/watch 中使用普通函数(需要 this 指向组件实例)
- 事件处理器:两种 API 中都推荐箭头函数,简洁且不依赖 this
# 2.4.4 箭头函数与普通函数对比
| 特性 | 箭头函数 | 普通函数 |
|---|---|---|
| this 绑定 | 词法绑定,继承外层作用域 | 动态绑定,取决于调用方式 |
| arguments 对象 | ❌ 没有(使用 rest 参数代替) | ✅ 有 |
| 构造函数 | ❌ 不能用 new 调用 | ✅ 可以作为构造函数 |
| prototype 属性 | ❌ 没有 | ✅ 有 |
| yield 关键字 | ❌ 不能用作 Generator | ✅ 可以 |
| 方法简写 | ❌ 不适合对象方法 | ✅ 适合 |
| 语法简洁性 | ✅ 更简洁 | ❌ 较冗长 |
// 1. arguments 对象差异
function normalFunc() {
console.log(arguments); // ✅ Arguments [1, 2, 3]
}
const arrowFunc = () => {
console.log(arguments); // ❌ ReferenceError: arguments is not defined
};
normalFunc(1, 2, 3);
// 箭头函数使用 rest 参数代替
const arrowFunc2 = (...args) => {
console.log(args); // ✅ [1, 2, 3]
};
arrowFunc2(1, 2, 3);
// 2. 构造函数差异
function Person(name) {
this.name = name;
}
const person1 = new Person('张三'); // ✅ 正常工作
const PersonArrow = (name) => {
this.name = name;
};
const person2 = new PersonArrow('李四'); // ❌ TypeError: PersonArrow is not a constructor
// 3. prototype 属性差异
console.log(Person.prototype); // ✅ {constructor: ƒ}
console.log(PersonArrow.prototype); // ❌ undefined
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
# 2.4.5 实践和应用场景
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>箭头函数应用示例</title>
<style>
#box{
display: inline-block;
width: 200px;
height: 200px;
background-color: red;
transition: background-color 0.3s;
}
</style>
</head>
<body>
<div id="box"></div>
<script>
let box = document.getElementById("box");
// ❌ 方案1: 传统方案(需要保存 this)
box.onclick = function(){
console.log(this); // this 是 box 元素
let _this = this;
setTimeout(function(){
console.log(this); // this 是 window
_this.style.backgroundColor = 'pink';
}, 2000);
}
// ✅ 方案2: 箭头函数方案(推荐)
box.onclick = function(){
console.log(this); // this 是 box 元素
setTimeout(() => {
console.log(this); // this 继承外层,仍是 box 元素
this.style.backgroundColor = 'pink';
}, 2000);
}
// ✅ 方案3: 全箭头函数方案(注意 this 差异)
box.addEventListener('click', (e) => {
console.log(this); // this 是 window(不是 box)
console.log(e.currentTarget); // 通过事件对象获取元素
setTimeout(() => {
e.currentTarget.style.backgroundColor = 'pink';
}, 2000);
});
</script>
</body>
</html>
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
应用场景总结
适合使用箭头函数的场景:
- 数组方法回调:
map(),filter(),reduce()等 - 定时器回调:
setTimeout(),setInterval() - Promise 链:
.then(),.catch() - 事件监听器(不需要移除时)
- Vue3 Composition API 中的各种回调
不适合使用箭头函数的场景:
- 对象方法定义(需要动态 this)
- 原型方法定义
- 需要 arguments 对象的函数
- 需要作为构造函数的函数
- Vue2 Options API 的 methods/computed/watch
# 2.4.6 rest 和 spread 参数
rest 参数:在形参上使用 ...,将多个独立参数收集为数组,类似 Java 中的可变参数。
// 1. 参数列表中多个普通参数(普通函数和箭头函数中都支持)
let fun1 = function (a, b, c, d=10) { console.log(a, b, c, d); }
let fun2 = (a, b, c, d=10) => { console.log(a, b, c, d); }
fun1(1, 2, 3); // 输出: 1 2 3 10
fun2(1, 2, 3, 4); // 输出: 1 2 3 4
// 2. ...作为参数列表,称之为 rest 参数
// 普通函数和箭头函数中都支持,因为箭头函数中无法使用 arguments,rest 是一种解决方案
let fun3 = function (...args) { console.log(args); }
let fun4 = (...args) => { console.log(args); }
fun3(1, 2, 3); // 输出: [1, 2, 3]
fun4(1, 2, 3, 4); // 输出: [1, 2, 3, 4]
// 3. rest 参数必须放在参数列表的最后一个
let fun5 = (a, b, ...args) => {
console.log('前两个参数:', a, b);
console.log('其余参数:', args);
}
fun5(1, 2, 3, 4, 5);
// 输出: 前两个参数: 1 2
// 输出: 其余参数: [3, 4, 5]
// ❌ 错误用法: 一个参数列表中只能有一个 rest 参数
// let fun6 = (...args1, ...args2) => {} // SyntaxError: Rest parameter must be last formal parameter
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
spread 参数:在实参上使用 ...,将数组展开为独立参数,是 rest 的逆向操作。
let arr = [1, 2, 3];
// 调用方法时,对 arr 进行转换(展开为 1, 2, 3)
let fun1 = (a, b, c) => {
console.log(a, b, c);
}
fun1(...arr); // 输出: 1 2 3
// 应用场景1: 合并数组
let arr2 = [4, 5, 6];
let arr3 = [...arr, ...arr2];
console.log(arr3); // [1, 2, 3, 4, 5, 6]
// 应用场景2: 数组拷贝
let arr4 = [...arr]; // 浅拷贝
console.log(arr4); // [1, 2, 3]
arr4[0] = 99;
console.log(arr); // [1, 2, 3] - 原数组未受影响
// 应用场景3: 合并对象属性
let p1 = { name: "张三" };
let p2 = { age: 10 };
let p3 = { gender: "boy" };
let person = { ...p1, ...p2, ...p3 };
console.log(person); // { name: "张三", age: 10, gender: "boy" }
// 应用场景4: 对象属性覆盖
let user = { name: "张三", age: 25 };
let updatedUser = { ...user, age: 26 }; // 后面的属性会覆盖前面的
console.log(updatedUser); // { name: "张三", age: 26 }
// 应用场景5: 函数参数传递(Math.max 示例)
let numbers = [10, 5, 20, 15];
console.log(Math.max(...numbers)); // 20
// 应用场景6: Vue3 中的 props 解构
// const props = defineProps({ user: Object });
// const updatedUser = { ...props.user, isOnline: true };
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
rest vs spread 对比
| 特性 | rest 参数 | spread 参数 |
|---|---|---|
| 使用位置 | 函数形参 | 函数实参/数组/对象 |
| 作用 | 收集多个独立参数为数组 | 展开数组/对象为独立元素 |
| 语法限制 | 必须放在参数列表最后 | 无位置限制 |
| 典型场景 | 不定数量参数处理 | 数组合并、对象合并、参数传递 |
| 示例 | (...args) => {} | fun(...arr), [...arr1, ...arr2] |
注意事项
浅拷贝问题:spread 操作符进行的是浅拷贝,嵌套对象/数组仍然是引用
let obj1 = { user: { name: '张三' } }; let obj2 = { ...obj1 }; obj2.user.name = '李四'; console.log(obj1.user.name); // "李四" - 嵌套对象被修改了1
2
3
4属性覆盖顺序:对象展开时,后面的属性会覆盖前面的同名属性
let obj = { ...defaults, ...userConfig }; // userConfig 会覆盖 defaults1性能考虑:大量数据的 spread 操作可能影响性能,考虑使用
Array.prototype.push.apply()等替代方案
# 2.5 ES6 对象创建和拷贝
ES6 引入了 class 语法糖,使面向对象编程更加直观,同时提供了多种对象拷贝方式来处理对象复制需求。
# 2.5.1 类的基本语法
ES6 的 class 语法是基于原型继承的语法糖,让代码更接近传统面向对象语言:
class Person {
// 私有属性(ES2022+,使用 # 前缀)
#name;
// 公共属性
age;
// getter 访问器
get name() {
return this.#name;
}
// setter 访问器
set name(value) {
if (value.length < 2) {
throw new Error('姓名长度至少2个字符');
}
this.#name = value;
}
// 构造器
constructor(name, age) {
this.#name = name;
this.age = age;
}
// 实例方法
eat(food) {
console.log(`${this.age}岁的${this.#name}用筷子吃${food}`);
}
// 静态方法(属于类,不属于实例)
static sum(a, b) {
return a + b;
}
// 静态属性(ES2022+)
static species = 'Homo sapiens';
}
// 使用示例
let person = new Person("张三", 25);
person.eat("火锅"); // "25岁的张三用筷子吃火锅"
console.log(person.name); // "张三"
console.log(Person.sum(10, 20)); // 30
console.log(Person.species); // "Homo sapiens"
// person.#name; // ❌ SyntaxError: 私有属性外部无法访问
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
# 2.5.2 类的继承
class Student extends Person {
grade;
score;
constructor(name, age, grade) {
super(name, age); // 必须先调用 super()
this.grade = grade;
}
// 重写父类方法
eat(food) {
super.eat(food); // 调用父类方法
console.log(`学生餐补贴已抵扣`);
}
// 新增方法
study(subject) {
console.log(`${this.name}正在学习${subject}`);
}
}
let student = new Student("李四", 18, "高三");
student.eat("米饭");
// 输出: 18岁的李四用筷子吃米饭
// 学生餐补贴已抵扣
student.study("数学"); // "李四正在学习数学"
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
class 与传统构造函数的区别
| 特性 | class 语法 | 构造函数 |
|---|---|---|
| 严格模式 | 默认启用 | 需手动开启 |
| 提升 | 不会提升(必须先定义后使用) | 会提升 |
| new 调用 | 必须使用 new | 可选(不推荐) |
| 继承 | extends/super 简洁明了 | 原型链手动设置 |
| 私有属性 | 支持 #privateProp | 需闭包模拟 |
| 可读性 | ✅ 更好 | ❌ 较差 |
// 传统构造函数写法
function PersonOld(name, age) {
this.name = name;
this.age = age;
}
PersonOld.prototype.eat = function(food) {
console.log(this.name + '吃' + food);
};
// ES6 class 写法(推荐)
class PersonNew {
constructor(name, age) {
this.name = name;
this.age = age;
}
eat(food) {
console.log(`${this.name}吃${food}`);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 2.5.3 对象拷贝方式对比
JavaScript 提供多种对象拷贝方式,需根据场景选择:
1. 浅拷贝方式
let original = {
name: "张三",
age: 25,
hobbies: ["读书", "运动"],
address: { city: "北京", district: "朝阳" }
};
// 方式1: 展开运算符(推荐)
let copy1 = { ...original };
// 方式2: Object.assign()
let copy2 = Object.assign({}, original);
// 方式3: 手动遍历
let copy3 = {};
for (let key in original) {
copy3[key] = original[key];
}
// 浅拷贝的问题:嵌套对象仍是引用
copy1.name = "李四"; // ✅ 不影响原对象
copy1.hobbies.push("游泳"); // ❌ 影响原对象(数组是引用)
copy1.address.city = "上海"; // ❌ 影响原对象(对象是引用)
console.log(original.name); // "张三" - 未受影响
console.log(original.hobbies); // ["读书", "运动", "游泳"] - 受影响
console.log(original.address.city); // "上海" - 受影响
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
2. 深拷贝方式
let original = {
name: "张三",
age: 25,
hobbies: ["读书", "运动"],
address: { city: "北京", district: "朝阳" },
createdAt: new Date()
};
// 方式1: JSON 序列化(简单场景)
let deepCopy1 = JSON.parse(JSON.stringify(original));
// ⚠️ 限制: 无法处理函数、Date、RegExp、undefined、Symbol、循环引用
// 方式2: structuredClone()(现代浏览器推荐)
let deepCopy2 = structuredClone(original);
// ✅ 支持: Date、RegExp、Map、Set、ArrayBuffer、循环引用
// ❌ 不支持: 函数、DOM节点、Error对象
// 方式3: 递归手动实现(完全控制)
function deepClone(obj, hash = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj;
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
// 处理循环引用
if (hash.has(obj)) return hash.get(obj);
let cloneObj = new obj.constructor();
hash.set(obj, cloneObj);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(obj[key], hash);
}
}
return cloneObj;
}
let deepCopy3 = deepClone(original);
// 深拷贝验证
deepCopy2.address.city = "上海";
console.log(original.address.city); // "北京" - 未受影响 ✅
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
拷贝方式选择建议
| 场景 | 推荐方式 | 原因 |
|---|---|---|
| 简单对象,无嵌套 | { ...obj } | 语法简洁,性能最佳 |
| 需要合并对象 | Object.assign() | 支持多源合并 |
| 简单深拷贝 | JSON.parse(JSON.stringify()) | 代码简单,但有限制 |
| 现代项目深拷贝 | structuredClone() | 原生支持,性能好 |
| 复杂对象深拷贝 | 手动递归或 lodash.cloneDeep | 完全控制,处理特殊类型 |
| Vue3 响应式对象 | toRaw() + 拷贝 | 先转普通对象再拷贝 |
# 2.5.4 Vue3 中的对象拷贝实践
<script setup>
import { reactive, toRaw, toRefs } from 'vue';
const state = reactive({
user: {
name: '张三',
profile: { age: 25, city: '北京' }
}
});
// ❌ 错误: 直接拷贝响应式对象会丢失响应性
let wrongCopy = { ...state.user };
wrongCopy.name = '李四'; // 不会触发视图更新
// ✅ 方案1: 使用 toRaw 获取原始对象再拷贝
let rawUser = toRaw(state.user);
let copy1 = structuredClone(rawUser);
// ✅ 方案2: 深拷贝后重新包装为响应式
let copy2 = reactive(structuredClone(toRaw(state.user)));
// ✅ 方案3: 解构赋值(仅适用于简单场景)
const { name, profile } = toRefs(state.user);
let copy3 = {
name: name.value,
profile: { ...profile.value }
};
// ✅ 方案4: 使用 JSON(快速方案,有限制)
let copy4 = JSON.parse(JSON.stringify(toRaw(state.user)));
</script>
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
最佳实践
- 优先浅拷贝:大多数场景浅拷贝已足够,性能更好
- 明确需求:拷贝前确认是否需要深拷贝,避免过度优化
- 注意响应式:Vue3 响应式对象拷贝需使用
toRaw() - 处理循环引用:深拷贝时使用 WeakMap 防止栈溢出
- 性能考虑:频繁拷贝大对象考虑使用不可变数据结构(Immutable.js)
# 2.6 ES6 模块化处理
模块化是将复杂程序拆分为独立、可复用的模块单元,每个模块负责特定功能,通过明确的接口与其他模块交互。这是现代前端工程化的核心基础。
# 2.6.1 前端模块化发展历程
JavaScript 模块化经历了从无到有、从混乱到规范的演进过程:
| 阶段 | 时间 | 方案 | 特点 | 适用场景 |
|---|---|---|---|---|
| 原始时代 | ~2009 | 全局变量 | 污染全局、命名冲突、依赖混乱 | 简单脚本 |
| 命名空间 | ~2010 | IIFE 模式 | 减少全局污染,但依赖管理困难 | 小型项目 |
| CommonJS | 2009+ | require/module.exports | 同步加载,Node.js 标准 | 服务端 |
| AMD | 2011+ | define/require | 异步加载,浏览器友好 | 大型 SPA(已淘汰) |
| CMD | 2011+ | seajs | 按需加载(国内方案) | 已淘汰 |
| UMD | 2013+ | 通用模块 | 兼容 CommonJS/AMD | 库开发 |
| ES6 Modules | 2015+ | import/export | 静态分析、Tree Shaking | ✅ 现代标准 |
各规范详细对比:
// 1. CommonJS (Node.js标准)
// 特点: 同步加载、运行时加载、值拷贝、动态引用
const fs = require('fs'); // 导入
module.exports = { name: 'module' }; // 导出
exports.helper = function() {}; // 导出简写
// 缺点: 浏览器不支持(需打包)、同步加载阻塞渲染
// 优点: 简单易用、Node.js生态丰富
// 2. AMD (Asynchronous Module Definition)
// 特点: 异步加载、依赖前置、浏览器端
define(['jquery', 'lodash'], function($, _) { // 依赖前置
return {
name: 'myModule'
};
});
require(['module'], function(module) {
// 使用模块
});
// 缺点: 语法复杂、打包体积大、已被ES6取代
// 优点: 异步加载、浏览器原生
// 3. ES6 Modules (现代标准)
// 特点: 编译时加载、静态分析、引用传递、Tree Shaking
import { name } from './module.js'; // 导入
export const PI = 3.14; // 导出
export default function() {} // 默认导出
// 优点:
// - 静态分析支持Tree Shaking
// - 浏览器和Node.js都支持
// - 更好的循环依赖处理
// - 异步加载支持(动态import)
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
模块化的核心优势
- 可维护性:代码结构清晰,职责单一,易于理解和修改
- 可复用性:模块可在不同项目间复用,减少重复开发
- 避免命名冲突:每个模块有独立作用域,避免全局污染
- 依赖管理:明确的依赖关系,便于追踪和管理
- 按需加载:支持代码分割和懒加载,优化性能
- 团队协作:模块边界清晰,便于并行开发
# 2.6.2 ES6 模块化核心概念
关键特性:
- 静态结构:import/export 必须在模块顶层,编译时确定依赖关系
- 单例模式:模块只执行一次,多次导入返回同一实例
- 引用绑定:导入的值是只读引用,不是拷贝
- 严格模式:自动启用
"use strict"
// ✅ 正确: 顶层导入
import { name } from './module.js';
// ❌ 错误: 条件导入(静态分析无法确定)
if (condition) {
import { name } from './module.js'; // SyntaxError
}
// ✅ 解决方案: 动态导入
if (condition) {
const module = await import('./module.js');
}
// 引用绑定示例
// counter.js
export let count = 0;
export function increment() {
count++;
}
// app.js
import { count, increment } from './counter.js';
console.log(count); // 0
increment();
console.log(count); // 1 (引用更新,而非拷贝)
// count = 10; // ❌ TypeError: 只读
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
导出方式总结:
ES6 提供三种导出方式,可混合使用:
| 方式 | 语法 | 特点 | 使用场景 |
|---|---|---|---|
| 命名导出 | export { a, b } | 可导出多个,导入时需匹配名称 | 工具函数、常量 |
| 默认导出 | export default obj | 每个模块只能有一个 | 主要功能、组件类 |
| 混合导出 | 两者结合 | 灵活但需谨慎使用 | 复杂模块 |
重要约定
ES6 模块导出的本质:无论何种导出方式,本质都是导出一个对象。命名导出是对象的属性,默认导出是对象的 default 属性。
// 这三种写法等价
export const PI = 3.14;
export { PI };
const PI = 3.14; export { PI };
2
3
4
# 2.6.3 命名导出与导入(Named Exports)
适用场景:模块需要导出多个独立功能(函数、常量、类等)
方式 1:分别导出(推荐,语义清晰)
// utils.js - 分别导出
export const PI = 3.14;
export function sum(a, b) {
return a + b;
}
export class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayHello() {
console.log(`Hello, I'm ${this.name}, ${this.age} years old.`);
}
}
// 也可以导出表达式
export const double = (x) => x * 2;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
方式 2:统一导出(便于管理导出列表)
// utils.js - 统一导出
const PI = 3.14;
function sum(a, b) {
return a + b;
}
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayHello() {
console.log(`Hello, I'm ${this.name}, ${this.age} years old.`);
}
}
// 统一导出(便于查看模块导出了什么)
export {
PI,
sum,
Person
};
// 支持重命名导出
export { sum as add, Person as User };
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
导入命名导出的模块:
// app.js
// 方式1: 导入所有导出(使用命名空间)
import * as utils from './utils.js';
console.log(utils.PI); // 3.14
console.log(utils.sum(10, 20)); // 30
const person = new utils.Person('张三', 25);
person.sayHello();
// 方式2: 按需导入(推荐,支持Tree Shaking)
import { PI, sum, Person } from './utils.js';
console.log(PI); // 3.14
console.log(sum(10, 20)); // 30
const person2 = new Person('李四', 30);
// 方式3: 导入时重命名(避免命名冲突)
import { PI as pi, sum as add, Person as User } from './utils.js';
console.log(pi); // 3.14
console.log(add(10, 20)); // 30
const user = new User('王五', 28);
// 方式4: 混合导入(同时使用多种方式)
import { PI, sum, PI as pi, sum as add } from './utils.js';
// 注意: 同一模块多次导入只会执行一次,返回同一实例
console.log(PI === pi); // true (同一引用)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
按需导入的优势
// ✅ 推荐: 按需导入(Tree Shaking 可移除未使用代码)
import { sum } from './utils.js'; // 只打包 sum 函数
// ❌ 避免: 导入全部再使用(无法Tree Shaking)
import * as utils from './utils.js';
utils.sum(1, 2); // 整个utils模块都会被打包
2
3
4
5
6
# 2.6.4 默认导出与导入(Default Export)
适用场景:模块只导出一个主要功能(组件类、配置对象、主函数等)
// math.js - 默认导出函数
export default function sum(a, b) {
return a + b;
}
// Button.vue - 默认导出组件(Vue常见模式)
export default {
name: 'Button',
props: ['label'],
template: '<button>{{ label }}</button>'
};
// config.js - 默认导出配置对象
export default {
apiUrl: 'https://api.example.com',
timeout: 5000
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
默认导出的导入方式:
// 方式1: 直接导入(可自定义名称)
import sum from './math.js'; // 名称可任意
import add from './math.js'; // 同一个模块,不同名称
import calculate from './math.js'; // 本质都是导入 default
console.log(sum(10, 20)); // 30
console.log(add === sum); // true (同一引用)
// 方式2: 命名导入default(较少使用)
import { default as sum } from './math.js';
console.log(sum(10, 20)); // 30
// 方式3: 与命名空间导入结合
import * as math from './math.js';
console.log(math.default(10, 20)); // 30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 2.6.5 混合导出模式(默认 + 命名)
一个模块可以同时使用默认导出和命名导出,但需谨慎使用以避免混淆:
// module.js - 混合导出
export const PI = 3.14;
export function square(x) {
return x * x;
}
export class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayHello() {
console.log(`Hello, I'm ${this.name}, ${this.age} years old.`);
}
}
// 默认导出一个主函数
function sum(a, b) {
return a + b;
}
export default sum;
// 或者统一导出
export {
PI,
square,
Person,
sum as default // 指定默认导出
};
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
混合导出的导入方式:
// app.js
// 方式1: 分别导入
import sum from './module.js'; // 默认导出
import { PI, Person } from './module.js'; // 命名导出
// 方式2: 一次性导入(推荐)
import sum, { PI, Person, square } from './module.js';
console.log(sum(10, 20)); // 30
console.log(PI); // 3.14
const person = new Person('张三', 25);
// 方式3: 命名空间导入
import * as module from './module.js';
console.log(module.default(10, 20)); // 30 (默认导出通过default访问)
console.log(module.PI); // 3.14
console.log(module.sum(10, 20)); // undefined (sum是default,不是命名导出)
// 方式4: 重命名混合导入
import { default as add, PI as pi } from './module.js';
console.log(add(10, 20)); // 30
console.log(pi); // 3.14
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
混合导出使用建议
// ✅ 推荐: 清晰的模块结构
// React组件常见模式
export default function Button() { /* 主组件 */ }
export const ButtonGroup = () => { /* 相关组件 */ };
export const useButton = () => { /* 相关Hook */ };
// ❌ 避免: 过度混合导致混乱
export default something;
export { a, b, c, d, e, f, g }; // 太多命名导出,考虑拆分模块
2
3
4
5
6
7
8
9
最佳实践:
- 优先使用命名导出:更明确、支持 Tree Shaking
- 默认导出用于主功能:组件、配置、主类等
- 避免过度混合:一个模块最多 1 个默认+少量命名导出
- 保持一致性:团队统一导出风格
# 2.6.6 在 HTML 中使用 ES6 模块
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>ES6模块示例</title>
</head>
<body>
<h1>ES6模块化示例</h1>
<div id="result"></div>
<!-- ⚠️ 关键: type="module" 启用ES6模块 -->
<script type="module">
// 导入模块(相对路径必须完整,包括.js后缀)
import { sum, PI } from './utils.js';
import Person from './Person.js';
// 使用导入的功能
document.getElementById('result').innerHTML = `
<p>PI = ${PI}</p>
<p>10 + 20 = ${sum(10, 20)}</p>
`;
const person = new Person('张三', 25);
person.sayHello();
</script>
</body>
</html>
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
type="module" 的重要特性:
<!-- ✅ ES6模块 -->
<script type="module">
// 1. 自动严格模式("use strict")
// 2. 有独立作用域(不污染全局)
// 3. 延迟执行(类似defer)
// 4. 支持import/export
// 5. 同一模块只加载一次
import { name } from './module.js';
</script>
<!-- ❌ 传统脚本 -->
<script>
// 1. 非严格模式(默认)
// 2. 全局作用域
// 3. 立即执行
// 4. 不支持import/export
</script>
<!-- 支持外部模块文件 -->
<script type="module" src="./app.js"></script>
<!-- 兼容性回退 -->
<script type="module" src="./app.js"></script>
<script nomodule src="./app-legacy.js"></script> <!-- 不支持模块的浏览器加载此脚本 -->
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 2.6.7 高级特性
1. 动态导入(Dynamic Import)
支持运行时按需加载模块,返回 Promise:
// 条件加载
if (userPreference === 'dark') {
const theme = await import('./themes/dark.js');
theme.apply();
}
// 懒加载(路由/组件按需加载)
button.addEventListener('click', async () => {
const module = await import('./heavy-module.js');
module.doSomething();
});
// 错误处理
try {
const module = await import('./module.js');
module.init();
} catch (error) {
console.error('模块加载失败:', error);
}
// Vue3路由懒加载
const routes = [
{
path: '/home',
component: () => import('./views/Home.vue') // 动态导入
}
];
// 并行加载多个模块
const [moduleA, moduleB] = await Promise.all([
import('./moduleA.js'),
import('./moduleB.js')
]);
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
2. 重新导出(Re-export)
模块可以充当中间层,整合导出其他模块:
// utils/index.js - 统一导出工具模块
export { sum, multiply } from './math.js';
export { formatDate, parseDate } from './date.js';
export { default as request } from './request.js';
// 导出全部
export * from './string.js';
// 重新导出并重命名
export { default as HttpClient } from './http.js';
export { debounce as _debounce } from './performance.js';
// 使用时
import { sum, formatDate, request } from './utils/index.js';
2
3
4
5
6
7
8
9
10
11
12
13
14
3. 循环依赖处理
ES6 模块对循环依赖有更好的处理机制:
// a.js
import { b } from './b.js';
export const a = 'A';
console.log('a.js:', b); // 可以访问(引用绑定)
// b.js
import { a } from './a.js';
export const b = 'B';
console.log('b.js:', a); // 可以访问(引用绑定)
// main.js
import './a.js';
// 输出: b.js: undefined (b模块先执行,此时a尚未初始化)
// a.js: B
2
3
4
5
6
7
8
9
10
11
12
13
14
循环依赖注意事项
// ❌ 避免: 初始化时相互依赖
// a.js
import { getValue } from './b.js';
export const value = getValue(); // b还未初始化,返回undefined
// ✅ 推荐: 函数调用时依赖
// a.js
import { getValue } from './b.js';
export function doSomething() {
return getValue(); // 调用时b已初始化
}
2
3
4
5
6
7
8
9
10
11
4. 模块元信息(import.meta)
提供模块的元数据:
console.log(import.meta.url); // file:///path/to/module.js
// Vite中常用于动态导入资源
const imagePath = new URL('./assets/logo.png', import.meta.url).href;
// 环境判断
if (import.meta.env.DEV) {
console.log('开发环境');
}
2
3
4
5
6
7
8
9
# 2.6.8 最佳实践与常见问题
最佳实践:
// ✅ 1. 模块导入放在文件顶部
import React from 'react';
import { useState } from 'react';
import './styles.css';
// ✅ 2. 按类别组织导入
// 第三方库
import React from 'react';
import axios from 'axios';
// 内部模块
import { utils } from '@/utils';
import Header from '@/components/Header';
// 样式
import './App.css';
// ✅ 3. 使用绝对路径别名(需配置)
import Button from '@/components/Button'; // 而非 '../../../components/Button'
// ✅ 4. 优先命名导出
export const config = { /* ... */ };
export function helper() { /* ... */ }
// ✅ 5. 一个文件一个组件/类
// Button.js
export default function Button() { }
// ButtonGroup.js
export default function ButtonGroup() { }
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
常见问题:
FAQ
Q1:为什么导入时必须加 .js 后缀?
// ❌ 浏览器原生模块要求完整路径
import { sum } from './utils';
// ✅ 正确
import { sum } from './utils.js';
// 注意: 打包工具(Vite/Webpack)会自动解析,可省略后缀
2
3
4
5
6
7
Q2:如何处理循环依赖?
- 优先重构代码消除循环依赖
- 使用函数而非立即执行的变量
- 考虑提取公共模块
Q3:export default 和 export 的区别?
// export default: 每个模块只能有1个,导入时可任意命名
export default function() {}
import anyName from './module.js';
// export: 可以有多个,导入时必须匹配名称或重命名
export const a = 1;
import { a } from './module.js';
import { a as b } from './module.js';
2
3
4
5
6
7
8
Q4:如何在 Node.js 中使用 ES6 模块?
// package.json添加
{
"type": "module"
}
// 或使用.mjs后缀
2
3
4
5
Q5:模块和脚本的区别?
| 特性 | <script type="module"> | <script> |
|---|---|---|
| 作用域 | 模块作用域 | 全局作用域 |
| 严格模式 | 自动启用 | 需手动开启 |
| this | undefined | window |
| 加载 | 延迟(defer) | 阻塞/立即 |
| 重复加载 | 只加载一次 | 每次都加载 |
| import/export | ✅ | ❌ |
# 三、前端工程化环境搭建
# 3.1 Node.js 简介和安装
# 3.1.1 什么是 Node.js
Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时环境,让 JavaScript 能够脱离浏览器在服务器端运行。它打破了 JavaScript 只能在浏览器中运行的限制,使其成为真正的全栈语言。
核心特性:
| 特性 | 说明 | 优势 |
|---|---|---|
| 事件驱动 | 采用事件循环机制处理请求 | 高并发处理能力 |
| 非阻塞 I/O | 异步 I/O 操作,无需等待 | 高效资源利用 |
| 单线程模型 | 主线程单线程+线程池 | 避免死锁,降低复杂度 |
| 跨平台 | Windows/Linux/macOS | 部署灵活 |
| 模块化 | CommonJS/ES Modules | 代码复用性强 |
| V8 引擎 | Google 高性能 JavaScript 引擎 | 执行速度快 |
适用场景:
// 1. Web服务器 (Express/Koa/Nest.js)
const express = require('express');
const app = express();
app.get('/', (req, res) => res.send('Hello World'));
app.listen(3000);
// 2. API接口服务
app.get('/api/users', async (req, res) => {
const users = await db.query('SELECT * FROM users');
res.json(users);
});
// 3. 实时应用 (WebSocket)
const io = require('socket.io')(server);
io.on('connection', (socket) => {
socket.on('message', (msg) => {
io.emit('message', msg); // 实时广播
});
});
// 4. 构建工具 (Webpack/Vite/Rollup)
// 5. 命令行工具 (CLI)
// 6. 桌面应用 (Electron)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Node.js vs 传统服务器
- 传统服务器(如 Apache/PHP):
- 每个请求创建新线程/进程
- 同步阻塞 I/O
- 资源消耗大,并发受限
- Node.js:
- 单线程事件循环
- 异步非阻塞 I/O
- 轻量高并发,适合 I/O 密集型应用
不适合场景:CPU 密集型任务(图像处理、视频编码等)
# 3.1.2 Node.js 版本管理
版本策略:
| 版本类型 | 说明 | 适用场景 | 示例 |
|---|---|---|---|
| LTS(Long Term Support) | 长期支持版,稳定可靠 | ✅ 生产环境 | v18.x, v20.x, V22.x, V24.x |
| Current | 最新特性版 | 实验/学习 | v25.x |
| Maintenance | 维护期版本 | 逐步迁移 | v16.x |
版本管理工具:nvm (Node Version Manager)
# Windows: 下载 nvm-windows
# https://github.com/coreybutler/nvm-windows/releases
# macOS/Linux: 安装nvm
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
# ===== 常用命令 =====
# 查看可安装版本
nvm list available # Windows
nvm ls-remote # macOS/Linux
# 安装指定版本
nvm install 18.20.0 # 安装Node.js 18.20.0
nvm install 20 # 安装最新的v20版本
nvm install --lts # 安装最新LTS版本
# 切换版本
nvm use 18.20.0 # 切换到18.20.0
nvm use 20 # 切换到v20
# 查看已安装版本
nvm list # Windows
nvm ls # macOS/Linux
# 设置默认版本
nvm alias default 18.20.0
# 查看当前版本
node -v
npm -v
# 卸载版本
nvm uninstall 16.14.0
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
版本选择建议
- 生产环境:优先使用 LTS 版本(如 v18.x, v20.x)
- 学习开发:可使用 Current 版本体验新特性
- 企业项目:团队统一版本,建议在 package.json 中指定:
{
"engines": {
"node": ">=18.0.0 <19.0.0",
"npm": ">=9.0.0"
}
}
2
3
4
5
6
# 3.1.3 Node.js 安装与验证
方式一:直接安装(适合初学者)
- 访问官网 https://nodejs.org/
- 下载 LTS 版本(推荐 v18.x 或 v20.x)
- 运行安装程序,勾选"自动安装必要工具"
- 验证安装:
node -v # 查看Node.js版本 输出: v18.20.0
npm -v # 查看npm版本 输出: 10.5.0
2
方式二:nvm 安装(推荐,便于版本管理)
# 1. 安装nvm (见3.1.2节)
# 2. 安装Node.js
nvm install 18.20.0
nvm use 18.20.0
# 3. 验证
node -v
npm -v
2
3
4
5
6
7
8
快速测试:
// app.js
function sum(a, b) {
return a + b;
}
function main() {
console.log('Hello Node.js!');
console.log('10 + 20 =', sum(10, 20));
}
main();
2
3
4
5
6
7
8
9
10
11
# 运行测试
node app.js
# 输出:
# Hello Node.js!
# 10 + 20 = 30
2
3
4
5
# 3.2 npm 配置和使用
# 3.2.1 npm 核心概念
NPM (Node Package Manager) 是 Node.js 的官方包管理工具,类似 Java 的 Maven、Python 的 pip。它是全球最大的开源软件注册表,包含超过 200 万个包。
npm 核心功能:
- 依赖管理:安装、更新、卸载项目依赖
- 版本控制:语义化版本管理
- 脚本执行:定义和运行项目脚本
- 包发布:发布自己的 npm 包供他人使用
package.json 核心字段详解:
{
// === 基本信息 ===
"name": "my-vue-app", // 包名(必需,小写,无空格)
"version": "1.0.0", // 版本号(必需,遵循semver)
"description": "我的Vue3项目", // 描述
"keywords": ["vue", "frontend"], // 关键词(便于搜索)
"author": "张三 <zhangsan@email.com>",
"license": "MIT", // 许可证
// === 入口文件 ===
"main": "index.js", // CommonJS入口
"module": "dist/index.esm.js", // ES Module入口
"types": "dist/index.d.ts", // TypeScript类型定义
// === 脚本命令 ===
"scripts": {
"dev": "vite", // 开发服务器
"build": "vite build", // 生产构建
"preview": "vite preview", // 预览构建结果
"test": "vitest", // 运行测试
"lint": "eslint src --fix", // 代码检查并修复
"format": "prettier --write .", // 代码格式化
"prepare": "husky install" // Git hooks
},
// === 依赖管理 ===
"dependencies": {
"vue": "^3.4.0", // 生产依赖(会打包到最终产物)
"vue-router": "^4.2.0",
"pinia": "^2.1.0"
},
"devDependencies": {
"vite": "^5.0.0", // 开发依赖(仅开发时使用)
"typescript": "^5.3.0",
"@vitejs/plugin-vue": "^5.0.0",
"vitest": "^1.0.0"
},
"peerDependencies": { // 对等依赖(要求宿主环境提供)
"vue": "^3.0.0"
},
// === 环境要求 ===
"engines": {
"node": ">=18.0.0", // Node.js版本要求
"npm": ">=9.0.0" // npm版本要求
},
// === 其他配置 ===
"private": true, // 防止意外发布到npm
"type": "module", // 使用ES Module(默认CommonJS)
"browserslist": [ // 浏览器兼容性
"> 1%",
"last 2 versions"
]
}
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
版本号语义(Semantic Versioning):
// 格式: 主版本.次版本.修订版本
"dependencies": {
"vue": "3.4.15", // 精确版本
"vue": "^3.4.0", // 兼容3.x.x (不升主版本)
"vue": "~3.4.0", // 兼容3.4.x (不升次版本)
"vue": ">=3.0.0 <4.0.0", // 版本范围
"vue": "*", // 任意版本(不推荐)
"vue": "latest" // 最新版本(不推荐)
}
// 符号说明:
// ^ 兼容补丁和次版本号更新 ^3.4.0 → 3.x.x
// ~ 仅兼容补丁版本更新 ~3.4.0 → 3.4.x
// >= 大于等于某版本
// < 小于某版本
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 3.2.2 npm 镜像源配置
为什么需要配置镜像源?
npm 默认使用官方源 https://registry.npmjs.org/,由于服务器在国外,国内访问速度较慢。配置国内镜像源可大幅提升下载速度。
推荐镜像源配置:
# === 淘宝镜像(npmmirror) - 推荐 ===
# 1. 查看当前镜像源
npm config get registry
# 默认输出: https://registry.npmjs.org/
# 2. 设置淘宝镜像
npm config set registry https://registry.npmmirror.com/
# 3. 验证配置
npm config get registry
# 输出: https://registry.npmmirror.com/
# === 恢复官方源 ===
npm config set registry https://registry.npmjs.org/
# === 临时使用特定镜像(单次安装) ===
npm install vue --registry=https://registry.npmmirror.com/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
镜像源管理工具 nrm
# 安装nrm(npm registry manager)
npm install -g nrm
# 列出所有可用镜像
nrm ls
# npm -------- https://registry.npmjs.org/
# yarn ------- https://registry.yarnpkg.com/
# * taobao ----- https://registry.npmmirror.com/
# cnpm ------- https://r.cnpmjs.org/
# nj --------- https://registry.nodejitsu.com/
# npmMirror -- https://skimdb.npmjs.com/registry/
# 测试所有镜像速度
nrm test
# 切换镜像
nrm use taobao # 切换到淘宝镜像
nrm use npm # 切换回官方源
# 添加企业私有源
nrm add company http://npm.company.com/
nrm use company
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
全局安装路径配置(可选):
默认情况下,npm 全局包安装在用户目录:
- Windows:
C:\Users\<用户名>\AppData\Roaming\npm - macOS/Linux:
/usr/local/lib/node_modules
如需修改(磁盘空间不足或无权限时):
# 1. 创建目录
# 示例: D:\GlobalNodeModules\node_global (全局包)
# D:\GlobalNodeModules\node_cache (缓存)
# 2. 配置路径
npm config set prefix "D:\GlobalNodeModules\node_global"
npm config set cache "D:\GlobalNodeModules\node_cache"
# 3. 验证配置
npm config get prefix
npm config get cache
# 4. 添加到系统环境变量 Path
# Windows: 系统属性 -> 高级 -> 环境变量 -> Path
# 添加: D:\GlobalNodeModules\node_global
2
3
4
5
6
7
8
9
10
11
12
13
14
15
npm 版本升级:
# 查看当前版本
npm -v
# 升级到最新版本
npm install -g npm@latest
# 升级到指定版本
npm install -g npm@9.6.6
# 查看可用版本
npm view npm versions --json
2
3
4
5
6
7
8
9
10
11
配置说明
- 镜像源:影响所有项目的依赖下载
- 全局路径:仅影响
npm install -g安装的包 - 项目配置:可在项目根目录创建
.npmrc覆盖全局配置 - 发布包:发布时务必切换回官方源
https://registry.npmjs.org/
# 3.2.3 npm 项目初始化与依赖管理
1. 项目初始化
# === 交互式初始化 ===
npm init
# 依次回答:
# - package name: (my-project) 包名
# - version: (1.0.0) 版本号
# - description: 描述
# - entry point: (index.js) 入口文件
# - test command: 测试命令
# - git repository: git仓库
# - keywords: 关键词
# - author: 作者
# - license: (ISC) 许可证
# === 快速初始化(使用默认值) ===
npm init -y
# 生成的 package.json:
# {
# "name": "my-project",
# "version": "1.0.0",
# "description": "",
# "main": "index.js",
# "scripts": { "test": "echo \"Error: no test specified\" && exit 1" },
# "keywords": [],
# "author": "",
# "license": "ISC"
# }
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
2. 安装依赖
| 命令 | 说明 | 使用场景 |
|---|---|---|
npm install 包名 | 安装生产依赖 | 运行时需要的包 |
npm install 包名 -D | 安装开发依赖 | 仅开发时需要的包 |
npm install 包名 -g | 安装全局依赖 | 命令行工具 |
npm install | 安装所有依赖 | 初始化项目时 |
# === 安装生产依赖(dependencies) ===
npm install vue
npm install vue@3.4.15 # 指定版本
npm install vue@latest # 最新版本
npm install vue axios pinia # 同时安装多个
# 简写
npm i vue
# package.json 变化:
# "dependencies": {
# "vue": "^3.4.15"
# }
# === 安装开发依赖(devDependencies) ===
npm install vite -D
npm install typescript @vitejs/plugin-vue -D
# 简写
npm i vite -D
# package.json 变化:
# "devDependencies": {
# "vite": "^5.0.0"
# }
# === 安装全局依赖 ===
npm install -g @vue/cli # Vue CLI
npm install -g create-vite # Vite脚手架
npm install -g typescript # TypeScript编译器
# 简写
npm i -g @vue/cli
# === 安装所有依赖(根据package.json) ===
npm install
# 读取 package.json 中的 dependencies 和 devDependencies
# 安装所有包到 node_modules 目录
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
dependencies vs devDependencies
dependencies(生产依赖):
- 运行时必需的包
- 会打包到最终产物
- 示例:
vue,vue-router,pinia,axios
devDependencies(开发依赖):
- 仅开发/构建时需要
- 不会打包到最终产物
- 示例:
vite,typescript,eslint,vitest
区分标准:用户使用应用时是否需要?
- 需要 → dependencies
- 不需要 → devDependencies
3. 更新依赖
# 更新单个包到最新版本
npm update vue
npm update vite
# 更新所有包
npm update
# 查看过时的包
npm outdated
# 输出示例:
# Package Current Wanted Latest Location
# vue 3.3.0 3.4.15 3.4.15 node_modules/vue
# vite 4.5.0 4.5.3 5.0.0 node_modules/vite
# 安装最新版本(忽略package.json的版本限制)
npm install vue@latest
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
4. 卸载依赖
# 卸载生产依赖
npm uninstall vue
# 卸载开发依赖
npm uninstall vite -D
# 卸载全局依赖
npm uninstall -g @vue/cli
# 简写
npm un vue
2
3
4
5
6
7
8
9
10
11
5. 查看依赖
# 查看项目依赖树
npm ls
# 输出:
# my-project@1.0.0 D:\project
# ├── vue@3.4.15
# └─┬ vite@5.0.0
# ├── esbuild@0.19.11
# └── rollup@4.9.0
# 查看指定深度
npm ls --depth=0 # 仅顶层依赖
npm ls --depth=1 # 一级依赖
# 查看全局依赖
npm ls -g --depth=0
# 查看特定包信息
npm view vue
npm view vue versions # 查看所有版本
npm view vue version # 查看最新版本
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 3.2.4 npm scripts 脚本命令
基础用法:
{
"scripts": {
"dev": "vite", // 开发服务器
"build": "vite build", // 生产构建
"preview": "vite preview", // 预览构建结果
"test": "vitest", // 运行测试
"lint": "eslint src --fix" // 代码检查
}
}
2
3
4
5
6
7
8
9
# 运行脚本
npm run dev # 启动开发服务器
npm run build # 执行生产构建
npm run test # 运行测试
# 特殊脚本(可省略 run)
npm start # 等同于 npm run start
npm test # 等同于 npm run test
npm stop # 等同于 npm run stop
2
3
4
5
6
7
8
9
生命周期钩子:
npm 会在特定命令前后自动执行钩子脚本:
{
"scripts": {
"prebuild": "echo 准备构建...", // build前自动执行
"build": "vite build",
"postbuild": "echo 构建完成!", // build后自动执行
"pretest": "npm run lint", // test前检查代码
"test": "vitest",
"prepare": "husky install" // npm install后自动执行
}
}
2
3
4
5
6
7
8
9
10
11
12
# 执行 npm run build 时,自动按顺序执行:
# 1. prebuild → echo 准备构建...
# 2. build → vite build
# 3. postbuild → echo 构建完成!
2
3
4
高级用法:
{
"scripts": {
// 1. 串行执行(&&)
"build:all": "npm run lint && npm run test && npm run build",
// 2. 并行执行(使用npm-run-all)
"dev:all": "npm-run-all --parallel dev:client dev:server",
"dev:client": "vite",
"dev:server": "nodemon server.js",
// 3. 参数传递
"dev": "vite --host 0.0.0.0 --port 3000",
"build:prod": "vite build --mode production",
"build:test": "vite build --mode test",
// 4. 环境变量
"dev:mock": "cross-env VITE_MOCK=true vite",
"build:analyze": "cross-env ANALYZE=true vite build",
// 5. 多命令组合
"clean": "rimraf dist node_modules",
"reinstall": "npm run clean && npm install",
// 6. 预提交检查
"pre-commit": "lint-staged",
"commit": "git-cz"
}
}
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
实用工具包
# 跨平台命令工具
npm install -D cross-env # 设置环境变量
npm install -D rimraf # 删除文件/目录
npm install -D npm-run-all # 并行/串行执行脚本
# 使用示例
"scripts": {
"clean": "rimraf dist",
"dev": "cross-env NODE_ENV=development vite",
"start": "npm-run-all --parallel dev:*"
}
2
3
4
5
6
7
8
9
10
11
实际案例(Vue3 项目):
{
"name": "vue3-project",
"version": "1.0.0",
"scripts": {
// 开发
"dev": "vite --open",
"dev:mock": "cross-env VITE_MOCK=true vite",
// 构建
"build": "vite build",
"build:test": "vite build --mode test",
"build:prod": "vite build --mode production",
"build:analyze": "cross-env ANALYZE=true vite build",
// 预览
"preview": "vite preview --port 5000",
// 代码质量
"lint": "eslint src --ext .js,.vue --fix",
"format": "prettier --write \"src/**/*.{js,vue,css}\"",
"type-check": "vue-tsc --noEmit",
// 测试
"test": "vitest",
"test:ui": "vitest --ui",
"coverage": "vitest run --coverage",
// Git钩子
"prepare": "husky install",
"pre-commit": "lint-staged",
// 清理
"clean": "rimraf dist node_modules",
"reinstall": "npm run clean && npm install"
},
"dependencies": {
"vue": "^3.4.0",
"vue-router": "^4.2.0",
"pinia": "^2.1.0",
"axios": "^1.6.0"
},
"devDependencies": {
"vite": "^5.0.0",
"@vitejs/plugin-vue": "^5.0.0",
"typescript": "^5.3.0",
"eslint": "^8.56.0",
"prettier": "^3.1.0",
"vitest": "^1.0.0",
"cross-env": "^7.0.3",
"rimraf": "^5.0.5",
"npm-run-all": "^4.1.5"
}
}
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
最佳实践
- 命名规范:使用
:分隔命名空间 (dev:client,build:prod) - 参数传递:通过
--传递参数npm run dev -- --port 3000 - 环境变量:使用
cross-env确保跨平台兼容 - 钩子使用:合理使用
pre/post钩子自动化任务 - 注释说明:复杂脚本添加注释说明用途
# 四、Vue3 简介和快速体验
# 4.1 Vue3 核心概念
# 4.1.1 Vue.js 框架简介

Vue (发音 /vjuː/,类似 view) 是一款用于构建用户界面的渐进式 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,提供了声明式、组件化的编程模型,帮助高效开发用户界面。官网: https://cn.vuejs.org/ (opens new window)
Vue 核心特性:
| 特性 | 说明 | 优势 |
|---|---|---|
| 声明式渲染 | 用模板语法描述 HTML 与 JS 状态的关系 | 代码直观,易维护 |
| 响应式系统 | 自动追踪状态变化并更新 DOM | 无需手动操作 DOM |
| 组件化 | 封装可复用的 UI 单元 | 提高代码复用性 |
| 渐进式 | 可逐步集成到项目中 | 灵活适配各种场景 |
| 单文件组件 | .vue文件封装 HTML/CSS/JS | 代码组织清晰 |
| 丰富的生态 | Router/Pinia/Devtools 等 | 开箱即用的工具链 |
渐进式框架含义:
// 1. 核心库使用(简单页面)
<script src="https://unpkg.com/vue@3"></script>
// 2. 单文件组件(中小型应用)
// 使用 Vite + Vue3
// 3. 完整生态(大型应用)
// Vue3 + Vue Router + Pinia + Element-plus + TypeScript
// 可根据项目需求选择集成程度,无需一次性引入所有功能
2
3
4
5
6
7
8
9
10
Vue 两大核心功能:
声明式渲染
<template> <!-- 声明式:描述"是什么",而非"怎么做" --> <h1>{{ message }}</h1> <p v-if="isVisible">条件渲染</p> <ul> <li v-for="item in list" :key="item.id">{{ item.name }}</li> </ul> </template> <script setup> import { ref } from 'vue' const message = ref('Hello Vue3') const isVisible = ref(true) const list = ref([ { id: 1, name: '张三' }, { id: 2, name: '李四' } ]) </script>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19响应式系统
// Vue2: Object.defineProperty // Vue3: Proxy (更强大、性能更好) import { ref, reactive } from 'vue' // ref: 基本类型响应式 const count = ref(0) count.value++ // 修改数据,视图自动更新 // reactive: 对象响应式 const state = reactive({ user: { name: '张三', age: 20 } }) state.user.age++ // 深层响应式1
2
3
4
5
6
7
8
9
10
11
12
13
14
Vue vs 其他框架
- Vue vs React
- Vue:模板语法,更接近传统 HTML,学习曲线平缓
- React:JSX 语法,更灵活但需适应
- Vue vs Angular
- Vue:轻量级,渐进式,易上手
- Angular:重量级,完整框架,学习成本高
选择 Vue 的理由:易学易用、文档完善(中文)、生态成熟、性能优秀
# 4.1.2 Vue3 vs Vue2 重大变化
核心对比表:
| 对比维度 | Vue2 | Vue3 | 优势 |
|---|---|---|---|
| 响应式原理 | Object.defineProperty | Proxy | 支持动态属性、数组索引、更好的性能 |
| API 风格 | Options API | Composition API | 逻辑复用、代码组织更灵活 |
| 组件定义 | export default {} | <script setup> | 更简洁,自动暴露 |
| 生命周期 | created/mounted | setup + onMounted | 统一在 setup 中管理 |
| TypeScript | 支持较弱 | 原生支持 | 类型推导更准确 |
| 包大小 | ~32KB | ~13KB (tree-shaking) | 体积减小 60% |
| 性能 | 基准 | 快 1.3-2 倍 | 虚拟 DOM 优化 |
| 多根节点 | 不支持 | 支持(Fragment) | 无需多余 wrapper |
| Teleport | 不支持 | 支持 | 跨 DOM 渲染(弹窗) |
| Suspense | 不支持 | 支持 | 异步组件加载 |
1. 响应式系统升级
// ===== Vue2 响应式限制 =====
data() {
return {
obj: { name: '张三' }
}
}
// ❌ 无法响应
this.obj.age = 20 // 新增属性无法追踪
this.$set(this.obj, 'age', 20) // 需要手动调用$set
// ❌ 数组索引修改无法响应
this.arr[0] = 'new value'
this.arr.length = 0
// ===== Vue3 Proxy完美解决 =====
import { reactive } from 'vue'
const state = reactive({
obj: { name: '张三' }
})
// ✅ 自动响应
state.obj.age = 20 // 动态属性
state.arr[0] = 'new' // 数组索引
state.arr.length = 0 // 数组长度
delete state.obj.name // 删除属性
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
2. Composition API vs Options API
<!-- ===== Vue2 Options API ===== -->
<script>
export default {
data() {
return {
count: 0,
user: { name: '张三' }
}
},
computed: {
doubleCount() {
return this.count * 2
}
},
methods: {
increment() {
this.count++
},
fetchUser() {
// 用户相关逻辑
}
},
mounted() {
this.fetchUser()
}
}
</script>
<!-- 问题:
1. 同一功能的代码分散在 data/methods/computed/lifecycle
2. 逻辑复用困难(mixin有命名冲突)
3. TypeScript支持差
-->
<!-- ===== Vue3 Composition API ===== -->
<script setup>
import { ref, computed, onMounted } from 'vue'
// 计数器功能(代码集中)
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
const increment = () => count.value++
// 用户功能(代码集中)
const user = ref({ name: '张三' })
const fetchUser = async () => {
// 用户相关逻辑
}
onMounted(() => {
fetchUser()
})
</script>
<!-- 优势:
1. 同一功能代码集中,易维护
2. 可抽取为组合式函数(Composables)复用
3. TypeScript类型推导完美
-->
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
3. <script setup> 语法糖
<!-- ===== Vue2/Vue3 标准写法 ===== -->
<script>
import { ref, computed } from 'vue'
import ChildComponent from './Child.vue'
export default {
components: { ChildComponent },
setup() {
const count = ref(0)
const double = computed(() => count.value * 2)
return {
count,
double
}
}
}
</script>
<!-- ===== Vue3 <script setup> 简化 ===== -->
<script setup>
import { ref, computed } from 'vue'
import ChildComponent from './Child.vue' // 自动注册
const count = ref(0)
const double = computed(() => count.value * 2)
// 自动暴露到模板,无需return
</script>
<!-- 优势:
1. 无需手动注册组件
2. 无需return暴露变量
3. 代码量减少40%
-->
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
4. 新增特性
<!-- 多根节点(Fragment) -->
<template>
<!-- Vue2必须有单一根节点 -->
<!-- Vue3支持多个根节点 -->
<header>头部</header>
<main>内容</main>
<footer>底部</footer>
</template>
<!-- Teleport传送门 -->
<template>
<button @click="showModal = true">打开弹窗</button>
<!-- 将弹窗渲染到body下,而非当前组件位置 -->
<Teleport to="body">
<div v-if="showModal" class="modal">
<p>弹窗内容</p>
<button @click="showModal = false">关闭</button>
</div>
</Teleport>
</template>
<!-- Suspense异步组件 -->
<template>
<Suspense>
<template #default>
<AsyncComponent /> <!-- 异步组件 -->
</template>
<template #fallback>
<div>加载中...</div> <!-- 加载状态 -->
</template>
</Suspense>
</template>
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
迁移注意事项
Vue2 → Vue3 不兼容变更:
- 过滤器(filters)已移除 → 使用 computed 或方法
$on/$off/$once移除 → 使用 mitt 等事件库.sync修饰符移除 → 统一使用v-model:propName$children移除 → 使用 ref 获取子组件- 全局 API 改为应用实例 API:
Vue.component→app.component
# 4.1.3 Vue3 核心优势
1. 性能提升
// 编译优化:静态提升(Static Hoisting)
// Vue2: 每次重新渲染都创建新的VNode
render() {
return createVNode('div', null, [
createVNode('p', null, '静态文本'),
createVNode('p', null, this.dynamicText)
])
}
// Vue3: 静态节点提升到render外
const _hoisted_1 = createVNode('p', null, '静态文本')
render() {
return createVNode('div', null, [
_hoisted_1, // 复用静态节点
createVNode('p', null, this.dynamicText)
])
}
// 结果:减少70%的虚拟DOM创建
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2. Tree-shaking 支持
// Vue2: 所有功能打包(即使未使用)
import Vue from 'vue'
// Vue3: 按需引入,未使用的功能不打包
import { ref, computed } from 'vue' // 只打包ref和computed
// 生产包体积: Vue2 32KB → Vue3 13KB
2
3
4
5
6
7
3. TypeScript 原生支持
// Vue3 对 TypeScript 的完美支持
import { ref, computed, defineComponent } from 'vue'
// 自动类型推导
const count = ref(0) // Ref<number>
const name = ref('张三') // Ref<string>
// 泛型支持
interface User {
id: number
name: string
}
const user = ref<User>({ id: 1, name: '张三' })
// 组件Props类型
defineComponent({
props: {
count: { type: Number, required: true },
user: { type: Object as () => User, required: true }
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
4. 更好的逻辑复用
// ===== 组合式函数(Composables) =====
// useCounter.js - 可复用的计数器逻辑
import { ref, computed } from 'vue'
export function useCounter(initialValue = 0) {
const count = ref(initialValue)
const doubleCount = computed(() => count.value * 2)
const increment = () => count.value++
const decrement = () => count.value--
const reset = () => count.value = initialValue
return {
count,
doubleCount,
increment,
decrement,
reset
}
}
// 在组件中使用
<script setup>
import { useCounter } from '@/composables/useCounter'
const { count, doubleCount, increment } = useCounter(10)
</script>
<template>
<div>
<p>Count: {{ count }}</p>
<p>Double: {{ doubleCount }}</p>
<button @click="increment">+1</button>
</div>
</template>
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
何时选择 Vue3?
- 强烈推荐 Vue3
- ✅ 新项目(2020 年后)
- ✅ 需要 TypeScript 支持
- ✅ 追求性能和体积优化
- ✅ 需要更好的逻辑复用
- 继续使用 Vue2
- ⚠️ 老项目维护(迁移成本高)
- ⚠️ 依赖不支持 Vue3 的库
- ⚠️ 团队不熟悉 Composition API
Vue2 官方支持至 2023 年底,建议尽快迁移 Vue3
# 4.1.4 Vue3 作者与生态
创始人: 尤雨溪 (Evan You)
- 教育背景:上海复旦附中 → Colgate University(艺术史) → Parsons 设计学院(Design & Technology 硕士)
- 职业经历:Google Creative Lab → Meteor Development Group → 全职开源 Vue.js
- 创业历程:非计算机专业,研究生期间偶遇 JavaScript,自学前端并创建 Vue.js
- 成就:Vue.js 全球 1000 万+开发者,GitHub 200K+ stars,中国最成功的开源项目之一
Vue3 生态系统
| 工具/库 | 说明 | 官网 |
|---|---|---|
| Vite | 下一代前端构建工具 | https://vitejs.dev/ (opens new window) |
| Vue Router | 官方路由管理器 | https://router.vuejs.org/ (opens new window) |
| Pinia | 官方状态管理库(替代 Vuex) | https://pinia.vuejs.org/ (opens new window) |
| Vue Devtools | 浏览器调试工具 | Chrome/Firefox 扩展 |
| VueUse | 组合式函数工具集 | https://vueuse.org/ (opens new window) |
| Nuxt 3 | Vue3 SSR 框架 | https://nuxt.com/ (opens new window) |
| Vitest | Vue 团队推荐的测试框架 | https://vitest.dev/ (opens new window) |
| Element Plus | UI 组件库 | https://element-plus.org/ (opens new window) |
| Ant Design Vue | UI 组件库 | https://antdv.com/ (opens new window) |
# 4.2 Vue3 快速体验
# 4.2.1 CDN 方式
适用场景:快速原型验证、简单页面增强、学习测试
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue3 快速体验</title>
</head>
<body>
<div id="app">
<!-- 插值表达式 -->
<h1 :style="colorStyle">{{ headline }}</h1>
<!-- v-text指令 -->
<p v-text="article"></p>
<!-- 动态属性绑定 -->
<input :type="inputType" :value="inputValue">
<!-- 事件绑定 -->
<button @click="sayHello">点击问候</button>
<button @click="changeType">切换输入类型</button>
<!-- 条件渲染 -->
<p v-if="isVisible">这是条件渲染的内容</p>
<!-- 列表渲染 -->
<ul>
<li v-for="item in list" :key="item.id">
{{ item.name }} - {{ item.age }}岁
</li>
</ul>
</div>
<!-- 引入Vue3 CDN -->
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const { createApp, ref, reactive } = Vue
const app = createApp({
setup() {
// === 响应式数据 ===
// ref: 基本类型响应式
const headline = ref('Hello Vue3!')
const article = ref('Vue3 是一个渐进式JavaScript框架')
const inputType = ref('text')
const inputValue = ref('Hello Vue3')
const isVisible = ref(true)
// reactive: 对象响应式
const colorStyle = reactive({
color: 'blue',
fontSize: '24px'
})
const list = reactive([
{ id: 1, name: '张三', age: 20 },
{ id: 2, name: '李四', age: 22 },
{ id: 3, name: '王五', age: 21 }
])
// === 方法定义 ===
const sayHello = () => {
alert(`${headline.value}`)
}
const changeType = () => {
inputType.value = inputType.value === 'text' ? 'password' : 'text'
}
// 返回给模板使用(必须return)
return {
headline,
article,
inputType,
inputValue,
isVisible,
colorStyle,
list,
sayHello,
changeType
}
}
})
// 挂载到#app元素
app.mount('#app')
</script>
</body>
</html>
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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
CDN 引入方式对比
<!-- 1. 开发版本(包含警告信息,文件较大) -->
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<!-- 2. 生产版本(优化压缩,推荐) -->
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<!-- 3. 指定版本(稳定可控) -->
<script src="https://unpkg.com/vue@3.4.15/dist/vue.global.js"></script>
<!-- 4. 本地引入(离线开发) -->
<!-- 下载vue.global.js到本地 -->
<script src="./lib/vue.global.js"></script>
2
3
4
5
6
7
8
9
10
11
12
# 4.2.2 核心概念演示
响应式数据对比:
// ===== 传统JavaScript(非响应式) =====
let count = 0
document.getElementById('count').innerText = count
function increment() {
count++
// ❌ 需要手动更新DOM
document.getElementById('count').innerText = count
}
// ===== Vue3响应式 =====
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
// ✅ 视图自动更新,无需手动操作DOM
}
// <p>{{ count }}</p>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
ref vs reactive 选择:
import { ref, reactive } from 'vue'
// === ref: 适用于基本类型 ===
const count = ref(0)
const name = ref('张三')
const isActive = ref(true)
// 访问/修改需要.value
console.log(count.value) // 0
count.value++ // 修改
// 模板中自动解包,不需要.value
// <p>{{ count }}</p>
// === reactive: 适用于对象/数组 ===
const state = reactive({
user: { name: '张三', age: 20 },
list: [1, 2, 3]
})
// 直接访问属性,无需.value
console.log(state.user.name) // '张三'
state.user.age++ // 修改
// ⚠️ 注意:不能直接替换整个对象
state = { ...newState } // ❌ 失去响应式
Object.assign(state, newState) // ✅ 正确
// === 选择建议 ===
// 基本类型 → ref
const count = ref(0)
const name = ref('张三')
// 对象/数组 → 两者都可,推荐reactive
const user = reactive({ name: '张三', age: 20 })
// 或
const user = ref({ name: '张三', age: 20 }) // 需要user.value.name
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
常用指令演示:
<template>
<!-- v-bind: 属性绑定(简写:) -->
<img v-bind:src="imageUrl" :alt="imageAlt">
<div :class="{ active: isActive }" :style="{ color: textColor }"></div>
<!-- v-on: 事件绑定(简写@) -->
<button v-on:click="handleClick">点击</button>
<button @click="count++">计数+1</button>
<input @input="handleInput" @keyup.enter="handleEnter">
<!-- v-model: 双向绑定 -->
<input v-model="message" placeholder="输入内容">
<p>输入的内容: {{ message }}</p>
<!-- v-if/v-else-if/v-else: 条件渲染 -->
<p v-if="score >= 90">优秀</p>
<p v-else-if="score >= 60">及格</p>
<p v-else>不及格</p>
<!-- v-show: 切换display属性 -->
<p v-show="isVisible">v-show控制显示</p>
<!-- v-if vs v-show: 频繁切换用v-show,条件很少改变用v-if -->
<!-- v-for: 列表渲染 -->
<ul>
<li v-for="(item, index) in list" :key="item.id">
{{ index + 1 }}. {{ item.name }}
</li>
</ul>
<!-- v-html: 渲染HTML(谨慎使用,防XSS) -->
<div v-html="htmlContent"></div>
</template>
<script setup>
import { ref } from 'vue'
const imageUrl = ref('https://example.com/img.jpg')
const imageAlt = ref('示例图片')
const isActive = ref(true)
const textColor = ref('red')
const message = ref('')
const score = ref(85)
const isVisible = ref(true)
const list = ref([
{ id: 1, name: '项目1' },
{ id: 2, name: '项目2' }
])
const htmlContent = ref('<strong>加粗文本</strong>')
const handleClick = () => console.log('按钮被点击')
const handleInput = (e) => console.log(e.target.value)
const handleEnter = () => console.log('按下回车')
</script>
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
CDN 方式的限制
不适用于生产环境的原因:
- ❌ 无法使用单文件组件(
.vue) - ❌ 无法使用构建工具(Tree-shaking、代码压缩)
- ❌ 无法使用 npm 包管理依赖
- ❌ 无法使用 TypeScript
- ❌ 无法热模块替换(HMR)
- ❌ 代码组织困难,不适合大型项目
推荐方案:学习完基础后,使用 Vite + Vue3 工程化开发