在 Vue 开发中,处理列表数据是再常见不过的场景。无论是购物车、商品列表还是用户管理,我们都需要对数组进行过滤、转换、聚合等操作。很多初学者习惯使用传统的 for 循环来完成这些任务,但这种方式往往代码冗长、可读性差且容易出错。
本文将带你深入理解 JavaScript 数组的高阶函数(如 filter、map、reduce 等),并展示如何在 Vue 项目中优雅地运用它们,让你的代码更加简洁、高效和易于维护。
为什么不用 for 循环?
让我们先看一个典型的购物车总价计算场景:
1// 传统 for 循环方式
2let totalPrice = 0;
3for (let i = 0; i < this.cartItems.length; i++) {
4 if (this.cartItems[i].isChecked) {
5 totalPrice += this.cartItems[i].price * this.cartItems[i].quantity;
6 }
7}
这段代码存在几个问题:
- 命令式编程:你需要告诉计算机每一步该做什么
- 易错:索引边界、变量作用域等问题容易导致 bug
- 难以复用:逻辑耦合在一起,无法轻松提取复用
- 可读性差:需要仔细阅读才能理解意图
相比之下,使用高阶函数的方式:
1// 函数式编程方式
2const totalPrice = this.cartItems
3 .filter(item => item.isChecked)
4 .reduce((sum, item) => sum + item.price * item.quantity, 0);
这段代码的优势显而易见:
- 声明式编程:你只描述"要什么",而不是"怎么做"
- 链式调用:逻辑清晰,步骤分明
- 不可变性:不修改原数组,避免意外副作用
- 高可读性:一眼就能看出是在"筛选已选商品并计算总价"
Vue 中的核心数组方法详解
1. filter() - 数据筛选
filter() 方法创建一个新数组,包含所有通过测试函数的元素。
Vue 应用场景:
-
显示已选中的商品
-
搜索过滤功能
-
状态筛选(如:只显示已完成的任务)
1// 在 computed 中使用
2computed: {
3 // 获取所有已选中的商品
4 selectedItems() {
5 return this.fruitList.filter(item => item.isChecked);
6 },
7
8 // 搜索过滤
9 filteredFruits() {
10 return this.fruitList.filter(item =>
11 item.name.toLowerCase().includes(this.searchQuery.toLowerCase())
12 );
13 }
14}
2. map() - 数据转换
map() 方法创建一个新数组,其结果是原数组中每个元素调用提供的函数后的返回值。
Vue 应用场景:
-
格式化显示数据
-
提取特定字段
-
创建派生数据结构
1// 格式化价格显示
2computed: {
3 formattedItems() {
4 return this.fruitList.map(item => ({
5 ...item,
6 displayPrice:¥${item.price.toFixed(2)},
7 totalPrice: item.price * item.num
8 }));
9 }
10}
3. reduce() - 数据聚合
reduce() 方法对数组中的每个元素执行 reducer 函数,将其结果汇总为单个返回值。
Vue 应用场景:
-
计算总价、总数量
-
统计分类数据
-
将数组转换为对象
1// 计算已选商品总价
2computed: {
3 totalPrice() {
4 return this.fruitList.reduce((sum, item) => {
5 return item.isChecked ? sum + (item.price * item.num) : sum;
6 }, 0);
7 },
8
9 // 统计各分类商品数量
10 categoryCount() {
11 return this.products.reduce((acc, product) => {
12 acc[product.category] = (acc[product.category] || 0) + 1;
13 return acc;
14 }, {});
15 }
16}
4. every() 和 some() - 条件判断
every(): 检测数组中所有元素是否都满足条件some(): 检测数组中是否有至少一个元素满足条件
Vue 应用场景:
-
实现全选/取消全选功能
-
表单验证
-
状态检查
1// 全选功能
2computed: {
3 isAllChecked: {
4 get() {
5 // 注意:空数组时 every 返回 true,但业务上可能需要特殊处理
6 return this.fruitList.length > 0 &&
7 this.fruitList.every(item => item.isChecked);
8 },
9 set(value) {
10 this.fruitList.forEach(item => item.isChecked = value);
11 }
12 },
13
14 // 检查是否有商品被选中(用于结算验证)
15 hasSelectedItems() {
16 return this.fruitList.some(item => item.isChecked);
17 }
18}
5. find() - 查找元素
find() 方法返回数组中第一个满足条件的元素。
Vue 应用场景:
-
根据 ID 查找特定商品
-
查找默认选中项
1methods: {
2 getItemById(id) {
3 return this.fruitList.find(item => item.id === id);
4 },
5
6 editItem(id) {
7 const item = this.getItemById(id);
8 if (item) {
9 // 执行编辑逻辑
10 this.currentEditItem = { ...item };
11 }
12 }
13}
Vue 2 响应式系统的注意事项
在 Vue 2 中使用这些数组方法时,需要注意响应式系统的限制:
正确做法:替换整个数组
由于 filter()、map() 等方法返回新数组,我们应该用新数组替换原数组来触发视图更新:
1// 删除商品
2methods: {
3 removeItem(id) {
4 // 正确:替换整个数组
5 this.fruitList = this.fruitList.filter(item => item.id !== id);
6 }
7}
对象属性变更
对于对象内部属性的修改(如 item.isChecked = true),只要对象本身是在 data 中声明的,Vue 2 就能正常追踪。
深度监听复杂数据
当需要监听嵌套对象的变化时,记得在 watch 中启用 deep: true:
1watch: {
2 fruitList: {
3 handler(newVal) {
4 localStorage.setItem('cart', JSON.stringify(newVal));
5 },
6 deep: true // 必须启用深度监听
7 }
8}
深度监听想要获取对象变化前后值
需要写一个computed的计算属性,用于获取对象的新旧副本,然后对这个计算属性进行监听
computed:{
newObj:{
return JSON.parse(JSON.stringify(this.obj));
}
}
实现全选按钮
可以使用v-model将全选按钮与计算其他选项全选状态的计算属性进行绑定,编写get和set方法
html
isAll:{
get(){return this.list.every(item=>item.isChecked)},
set(value){
this.list.forEach(element => {
element.isChecked = value
});
}
}
实现长期存储
使用localStorage.setItem(key,value)和localStorage.getItem(key)进行互动操作;
移除list的中的元素可以使用filter
类似于:
html
del(id){
this.fruitList=this.fruitList.filter(item=>item.id!=id)
},