前端开发中的常用工具函数(四)

目录

[🔍 九、find() 方法:在数组中查找并返回第一个满足条件的元素](#🔍 九、find() 方法:在数组中查找并返回第一个满足条件的元素)

[1. 基本语法](#1. 基本语法)

[2. 基础示例](#2. 基础示例)

[3. 企业开发中的实际应用场景](#3. 企业开发中的实际应用场景)

[4. 注意事项与常见陷阱](#4. 注意事项与常见陷阱)

[🔍 十、findIndex() 方法:告诉目标元素在数组中的位置](#🔍 十、findIndex() 方法:告诉目标元素在数组中的位置)

[1. 基本语法](#1. 基本语法)

[2. 基础示例](#2. 基础示例)

[3. 企业开发中的实际应用场景](#3. 企业开发中的实际应用场景)

[4. 注意事项与常见陷阱](#4. 注意事项与常见陷阱)

[🔍 十一、splice() 方法:实现列表"增删改"最直接的工具](#🔍 十一、splice() 方法:实现列表“增删改”最直接的工具)

[1. 基本语法](#1. 基本语法)

[2. 基础示例](#2. 基础示例)

[3. 企业开发中的实际应用场景](#3. 企业开发中的实际应用场景)

[4. 注意事项与常见陷阱](#4. 注意事项与常见陷阱)

🔍 九、find() 方法:在数组中查找并返回第一个满足条件的元素

find() 方法返回数组中满足提供的测试函数的第一个元素的值 。否则返回 undefined

简单来说,它的逻辑是:

  1. 遍历数组。
  2. 对每个元素执行一次回调函数。
  3. 如果回调函数返回 true,立即返回当前元素,停止遍历。
  4. 如果遍历结束都没有返回 true,则最终返回 undefined

1. 基本语法

javascript 复制代码
arr.find(callback(element[, index[, array]])[, thisArg])

参数解析:

  • callback :在数组每一项上执行的函数。
    • element:当前遍历到的元素(必须)。
    • index:当前遍历到的索引(可选)。
    • array:原数组本身(可选)。
  • thisArg :执行回调时用作 this 的对象(可选,箭头函数中无效)。

返回值: 数组中第一个满足测试条件的元素;如果没有满足条件的元素,则返回 undefined

2. 基础示例

(1) 查找基本数据类型

查找数组中第一个大于 10 的元素。

javascript 复制代码
const numbers = [5, 12, 8, 130, 44];

const found = numbers.find(element => element > 10);

console.log(found); // 输出: 12 (注意:不是 [12, 130, 44],只返回第一个)

(2) 查找对象数组(最常用场景)

这是前端开发中最常见的场景,例如根据 ID 查找用户信息。

javascript 复制代码
const users = [
  { id: 1, name: 'Alice', role: 'admin' },
  { id: 2, name: 'Bob', role: 'user' },
  { id: 3, name: 'Charlie', role: 'user' }
];

// 查找 ID 为 2 的用户
const user = users.find(item => item.id === 2);

console.log(user); // 输出: { id: 2, name: 'Bob', role: 'user' }

3. 企业开发中的实际应用场景

在企业级项目中,find() 的应用非常广泛,以下是几个典型场景。

场景 1:列表数据渲染中的"字典翻译"

在后台管理系统中,接口返回的状态通常是数字(如 status: 1),前端需要将其转换为文字显示。通常我们会有一个字典配置数组。

javascript 复制代码
// 字典配置
const statusOptions = [
  { value: 0, label: '禁用' },
  { value: 1, label: '启用' },
  { value: 2, label: '审核中' }
];

// 接口返回的原始数据
const rowData = { id: 101, name: '商品A', status: 1 };

// 渲染时翻译
const currentStatus = statusOptions.find(opt => opt.value === rowData.status);
console.log(currentStatus?.label); // 输出: '启用'

场景 2:表单编辑时的数据回填

点击"编辑"按钮,需要在一个列表中找到当前行的详细数据,并填充到弹窗表单中。

javascript 复制代码
const handleEdit = (id) => {
  // 从列表中查找对应数据
  const record = tableData.value.find(item => item.id === id);
  
  if (record) {
    // 进行表单回填
    formData.value = { ...record };
    showDialog.value = true;
  } else {
    message.error('未找到对应数据');
  }
};

场景 3:权限控制(查找匹配路由)

在前端路由守卫中,判断用户是否有权限访问某个页面,可能需要在权限列表中查找是否存在匹配项。

javascript 复制代码
const whiteList = ['/login', '/register', '/404'];

// 如果在白名单中找到路径,则允许通过
const canAccess = whiteList.find(path => to.path === path);
// 或者更常用的 some(),但在需要获取匹配项具体内容时用 find()

4. 注意事项与常见陷阱

虽然 find() 好用,但在实际开发中如果不注意细节,可能会引发 Bug。

(1) 区分 findfilter

这是新手最容易混淆的点:

  • filter :返回数组,包含所有满足条件的元素。
  • find :返回元素本身,只返回第一个满足条件的元素。

如果你期望结果是数组(如渲染一个筛选后的列表),请使用 filter。如果你只需要一个结果(如查 ID),请使用 find,性能更好。

(2) 返回 undefined 的处理

find() 找不到元素时会返回 undefined。如果直接解构或访问属性,可能会报错。

错误示范:

javascript 复制代码
const user = users.find(u => u.id === 999); // 返回 undefined
console.log(user.name); // 报错: Cannot read property 'name' of undefined

正确做法(可选链):

javascript 复制代码
const user = users.find(u => u.id === 999);
console.log(user?.name); // 输出: undefined (不报错)

(3) 引用类型数据的"副作用"

find() 返回的是数组中元素的引用,而不是副本。这意味着如果你修改了返回的对象,原数组中的数据也会被修改。

javascript 复制代码
const products = [{ id: 1, price: 100 }];
const product = products.find(p => p.id === 1);

product.price = 200; 

console.log(products[0].price); // 输出: 200 (原数组被修改了!)

在企业开发中(特别是 Vue 响应式系统),这可能导致难以排查的数据污染问题。如果不想影响原数组,请使用深拷贝或 { ...product } 创建副本。

(4) 性能考量

find() 在找到第一个匹配项后就会立即停止遍历。相比于 filter() 会遍历整个数组,find() 在处理大量数据且目标靠前时,性能优势明显。

(5) findIndex 的补充

如果你需要的是元素的位置(索引)而不是元素本身,请使用 findIndex()。这在需要删除某个元素(通过索引 splice)时非常有用。

javascript 复制代码
const index = users.findIndex(u => u.id === 2);
if (index !== -1) {
  users.splice(index, 1); // 删除该元素
}

🔍 十、findIndex() 方法:告诉目标元素在数组中的位置

findIndex() 方法返回数组中满足提供的测试函数的第一个元素的索引 。若没有找到对应元素,则返回 -1

它的核心逻辑如下:

  1. 从左到右遍历数组。
  2. 对每个元素执行回调函数。
  3. 一旦回调函数返回 true,立即停止遍历,并返回当前元素的下标
  4. 如果遍历结束所有回调都返回 false,则最终返回 -1

1. 基本语法

javascript 复制代码
arr.findIndex(callback(element[, index[, array]])[, thisArg])

参数解析:

  • callback :针对数组中每个元素执行的测试函数。
    • element:当前元素(必须)。
    • index:当前索引(可选)。
    • array:调用 findIndex 的数组(可选)。
  • thisArg :执行回调时用作 this 的对象(可选)。

返回值:

数组中通过测试的第一个元素的索引。如果均未通过测试,则返回 -1

2. 基础示例

(1) 基础类型查找

查找数组中第一个大于 10 的元素的位置。

javascript 复制代码
const numbers = [5, 12, 8, 130, 44];

const index = numbers.findIndex(element => element > 10);

console.log(index); // 输出: 1 (因为数字 12 在索引 1 的位置)

(2) 对象数组查找

查找数组中特定属性的对象索引。

javascript 复制代码
const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  { id: 3, name: 'Charlie' }
];

// 查找 id 为 3 的用户索引
const index = users.findIndex(user => user.id === 3);

console.log(index); // 输出: 2

(3)未找到的情况

javascript 复制代码
const index = users.findIndex(user => user.id === 99);
console.log(index); // 输出: -1

3. 企业开发中的实际应用场景

findIndex() 最核心的价值在于精准删除定点更新,特别是在 Vue 或 React 的响应式开发中。

场景 1:列表数据的删除操作

这是 findIndex 最经典的应用场景。当后端返回删除成功后,我们需要从前端的列表状态中移除该条数据。

javascript 复制代码
const handleDelete = (id) => {
  // 1. 找到要删除数据的索引
  const index = tableData.value.findIndex(item => item.id === id);
  
  // 2. 判断是否找到
  if (index !== -1) {
    // 3. 使用 splice 进行原地删除
    tableData.value.splice(index, 1);
    message.success('删除成功');
  } else {
    message.error('未找到对应数据');
  }
};

注:为什么不推荐使用 filter?因为 filter 会返回一个新数组,在某些深层响应式场景下,可能需要重新赋值整个数组,而 splice 配合 findIndex 是一种更轻量的原地操作。

场景 2:列表数据的局部更新

假设我们需要在列表中修改某一条数据的属性(例如点击"展开/折叠"按钮),我们需要先找到它的索引。

javascript 复制代码
const toggleExpand = (id) => {
  const index = listData.value.findIndex(item => item.id === id);
  
  if (index !== -1) {
    // 直接通过索引修改,Vue3 中配合 reactive 或 ref 能够触发响应式更新
    listData.value[index].isExpanded = !listData.value[index].isExpanded;
  }
};

场景 3:去重逻辑判断

在手动实现数组去重或判断数据是否已存在(如购物车添加商品)时,用于检测索引。

javascript 复制代码
const addToCart = (product) => {
  // 查找购物车中是否已有该商品
  const existIndex = cartList.value.findIndex(item => item.id === product.id);
  
  if (existIndex !== -1) {
    // 已存在,数量 +1
    cartList.value[existIndex].count += 1;
  } else {
    // 不存在,新增
    cartList.value.push({ ...product, count: 1 });
  }
};

4. 注意事项与常见陷阱

在使用 findIndex 时,有几个关键点需要牢记,以避免产生 Bug。

(1) 返回值的判断:-1 陷阱

这是最容易出错的地方。

  • find() 找不到返回 undefined
  • findIndex() 找不到返回 -1

if 判断时,千万不能直接写 if (index),因为 index 可能是 0(第一个元素),而 0 是 falsy 值,会导致逻辑错误。

错误示范:

javascript 复制代码
const index = list.findIndex(item => item.id === targetId);
if (index) { ... } // 如果 index 是 0,条件不成立,逻辑错误!

正确示范:

javascript 复制代码
if (index !== -1) { ... } // 标准写法

(2) 区分 findIndexindexOf

  • indexOf :通过全等匹配 (===) 查找值,适用于基本数据类型(字符串、数字)。
javascript 复制代码
['a', 'b'].indexOf('a'); // 0
  • findIndex :通过回调函数查找,适用于复杂数据类型(对象)或复杂条件。
javascript 复制代码
    [{id:1}].findIndex(item => item.id === 1); // 0
    [{id:1}].indexOf({id:1}); // -1 (对象引用不同)

(3) 性能与短路特性

findIndex 在找到第一个匹配项后会立即停止遍历。如果你的数据量很大(如数千条),且目标靠前,它的性能远优于 forEachfilter

(4) 不要混淆 findfindIndex

  • 想要数据本身 :用 find()
  • 想要操作位置 (删除、替换、插入):用 findIndex()

🔍 十一、splice() 方法:实现列表"增删改"最直接的工具

splice() 方法通过删除或替换现有元素 或者在原位置添加新元素来修改数组,并以数组形式返回被修改的内容。

核心特点:

  • 会改变原数组
  • 返回值是被删除的元素组成的数组,如果没有删除元素,则返回空数组 []

1. 基本语法

javascript 复制代码
array.splice(start[, deleteCount[, item1[, item2[, ...]]]])

参数解析:

  1. start (必须):指定修改的开始位置。
    • 如果超出了数组的长度,则从数组末尾开始添加内容。
    • 如果是负数,则从数组末尾开始计算(如 -1 指最后一个元素)。
  2. deleteCount (可选):整数,表示要移除的数组元素的个数。
    • 如果为 0,则不删除元素(常用于插入操作)。
    • 如果省略或大于 start 之后的元素总数,则删除从 start 开始到数组末尾的所有元素。
  3. item1, item2, ... (可选):要添加进数组的元素。
    • 如果不指定,则 splice() 只执行删除操作。

2. 基础示例

为了方便记忆,我们可以将其用法归纳为三种模式:删、增、改。

(1) 删除元素

从索引 2 开始删除 1 个元素。

javascript 复制代码
let months = ['Jan', 'Feb', 'Mar', 'Apr'];
let deleted = months.splice(2, 1); 

console.log(months);  // 输出: ['Jan', 'Feb', 'Apr']
console.log(deleted); // 输出: ['Mar']

(2) 插入元素

在索引 2 的位置,删除 0 个元素,并插入新元素。

javascript 复制代码
let months = ['Jan', 'Feb', 'Apr'];
months.splice(2, 0, 'Mar'); 

console.log(months); // 输出: ['Jan', 'Feb', 'Mar', 'Apr']

(3) 替换元素

从索引 0 开始,删除 1 个元素,并插入 'May'。

javascript 复制代码
let months = ['Jan', 'Feb'];
months.splice(0, 1, 'May');

console.log(months); // 输出: ['May', 'Feb']

3. 企业开发中的实际应用场景

在企业级项目中,splice() 配合 findIndex() 是处理列表数据的标准范式。

场景 1:表格数据的删除操作

这是最经典的业务场景。用户点击"删除"按钮,前端直接操作数据列表,无需重新请求后端接口刷新全量数据。

javascript 复制代码
const handleDelete = (id) => {
  // 1. 找到索引
  const index = tableData.value.findIndex(item => item.id === id);
  
  if (index !== -1) {
    // 2. 原地删除,界面会自动响应更新
    tableData.value.splice(index, 1);
    message.success('删除成功');
  }
};

场景 2:列表数据的拖拽排序

在拖拽组件(如 vuedraggable)的回调中,经常需要将一个元素从旧位置移除,并插入到新位置。

javascript 复制代码
const onDragEnd = ({ oldIndex, newIndex }) => {
  if (oldIndex === newIndex) return;

  // 1. 从旧位置取出该元素(splice 返回的是数组,所以取 [0])
  const [movedItem] = list.value.splice(oldIndex, 1);
  
  // 2. 将该元素插入到新位置
  list.value.splice(newIndex, 0, movedItem);
  
  // 此时 list.value 已经完成了位置互换
};

场景 3:状态管理中的定点更新

当需要替换数组中某个特定对象的所有属性时。

javascript 复制代码
const updateUser = (userId, newData) => {
  const index = users.value.findIndex(u => u.id === userId);
  if (index !== -1) {
    // 替换掉索引处的旧对象
    users.value.splice(index, 1, newData);
  }
};

4. 注意事项与常见陷阱

虽然 splice() 功能强大,但如果使用不当,很容易引发严重的 Bug。

(1) 原地修改的副作用

splice 是"破坏性"方法,它直接修改原数组。

  • 陷阱 :如果你使用了 Vue 或 React,且直接修改了被 Object.freeze 冻结的数组,或者修改了非响应式数组的副本,视图可能不会更新,甚至报错。
  • 建议 :在函数式编程或需要保留原始数据的场景(如"撤销/重做"功能),请使用 toSpliced()(ES2023 新增,不修改原数组)或 [...arr.slice(0, index), ...arr.slice(index)] 这种展开运算符方式。

(2) 不要在 forEachfor...of 循环中使用 splice

这是一个经典的"灾难现场"。在遍历过程中修改数组长度或索引,会导致索引错乱,跳过某些元素。

错误示范:

javascript 复制代码
// 想要删除所有小于 3 的数
let arr = [1, 2, 3, 4];
arr.forEach((item, index) => {
  if (item < 3) {
    arr.splice(index, 1); // 删除后,后面的元素前移,索引错位
  }
});
console.log(arr); // 输出: [2, 3, 4],数字 2 被跳过了!

正确做法:

  • 使用 filter 代替(推荐)。
  • 如果非要用 splice,请使用倒序遍历 for (let i = arr.length - 1; i >= 0; i--)

(3) 返回值是被删除的元素

很多人误以为 splice 返回修改后的新数组。

javascript 复制代码
let arr = [1, 2, 3];
let result = arr.splice(0, 1); 
console.log(result); // 输出: [1] (是被删掉的元素)
console.log(arr);    // 输出: [2, 3] (这才是修改后的数组)

(4) splice vs slice 傻傻分不清楚

这是面试和工作中最常见的混淆:

  • splice :有 p,可以想象成"剪贴板",剪下来贴上去,改变原数组
  • slice :只有切的动作,像切蛋糕,切下一块给你,原数组不变
相关推荐
星空露珠2 小时前
迷你世界UGC3.0脚本Wiki生物模块管理接口 Monster
开发语言·数据结构·算法·游戏·lua
星空露珠2 小时前
迷你世界UGC3.0脚本Wiki世界模块管理接口 World
开发语言·数据库·算法·游戏·lua
格林威2 小时前
工业相机彩色图像采集:为什么我的图是绿色的?附海康/Basler/堡盟相机设置
开发语言·人工智能·数码相机·opencv·计算机视觉·c#·工业相机
爱写bug的野原新之助2 小时前
爬虫之补环境脚本:脱环境
javascript·爬虫·原型模式
阿贵---2 小时前
C++中的装饰器模式
开发语言·c++·算法
加密狗复制模拟2 小时前
软件加密狗中时间限制机制的破解
开发语言·网络·安全·php·软件工程·个人开发
setmoon2142 小时前
C++中的装饰器模式高级应用
开发语言·c++·算法
m0_528174452 小时前
C++中的装饰器模式实战
开发语言·c++·算法
阿贵---2 小时前
实时信号处理库
开发语言·c++·算法