JavaScript中filter方法的详解与实战记录
作者:BUG 饲养员
一、filter 方法核心概念
1.1 定义与本质
filter 是 JavaScript 数组的内置迭代方法,属于 ES5 标准(2009 年发布),用于创建一个新数组,其元素是原数组中满足指定条件的所有元素。它的核心本质是 “筛选与过滤”,通过回调函数对数组元素进行条件判断,保留符合条件的元素并返回新数组。
1.2 核心特性
纯函数特性:filter 不会修改原数组,所有筛选操作均基于原数组的副本进行,最终返回全新数组
迭代性:遍历原数组的每一个元素,执行相同的条件判断逻辑
返回新数组:即使筛选结果为空,也会返回空数组而非 undefined 或 null
回调执行时机:仅对数组中已初始化的索引元素执行回调,未初始化的空槽(empty)会被跳过
1.3 应用场景
filter 是前端开发中最常用的数组方法之一,典型应用场景包括:
数据筛选(如表格数据过滤、搜索结果匹配)
数据清洗(如去除空值、过滤无效数据)
数组提纯(如提取特定类型元素、去重处理)
条件分组(如按状态拆分数组、按类型分类数据)
二、filter 语法与参数解析
2.1 基本语法
// 基础语法 const newArray = array.filter(callback(element[, index[, array]])[, thisArg]);
2.2 参数详解
2.2.1 回调函数(callback)
必须参数,用于定义筛选条件的函数,返回一个布尔值:
返回
true:当前元素会被保留到新数组中返回
false:当前元素会被排除
回调函数接收三个参数:
element:当前正在处理的数组元素(必须)
index:当前元素的索引值(可选)
array:调用 filter 方法的原数组(可选)
2.2.2 thisArg(可选)
可选参数,指定回调函数执行时的 this 指向。若不提供此参数,回调函数中的 this 在非严格模式下指向全局对象(window/global),严格模式下为 undefined。
2.3 返回值
返回一个新的数组,包含所有通过回调函数筛选的元素。新数组的长度由符合条件的元素数量决定,元素顺序与原数组保持一致。
2.4 语法示例
// 完整参数使用示例
const numbers = [10, 20, 30, 40, 50];
const context = { threshold: 30 };
// 使用thisArg指定回调中的this
const filtered = numbers.filter(function(element, index, array) {
console.log(`当前元素:${element},索引:${index},原数组:${array}`);
return element > this.threshold; // this指向context对象
}, context);
console.log(filtered); // 输出:[40, 50]三、filter 基础用法全解析
3.1 过滤基本数据类型数组
3.1.1 过滤数字数组
// 示例1:筛选偶数 const nums = [1, 2, 3, 4, 5, 6, 7, 8]; const evenNums = nums.filter(num => num % 2 === 0); console.log(evenNums); // 输出:[2, 4, 6, 8] // 示例2:筛选指定范围的数字 const scores = [85, 92, 78, 65, 98, 59, 88]; const passScores = scores.filter(score => score >= 80); console.log(passScores); // 输出:[85, 92, 98, 88] // 示例3:筛选非NaN的数字 const mixedNums = [12, NaN, 34, NaN, 56, undefined, null]; const validNums = mixedNums.filter(num => !isNaN(num) && num !== null && num !== undefined); console.log(validNums); // 输出:[12, 34, 56]
3.1.2 过滤字符串数组
// 示例1:筛选长度大于3的字符串
const words = ["apple", "cat", "banana", "dog", "grape"];
const longWords = words.filter(word => word.length > 3);
console.log(longWords); // 输出:["apple", "banana", "grape"]
// 示例2:筛选包含指定字符的字符串
const names = ["张三", "李四", "王五", "张晓明", "赵丽"];
const zhangNames = names.filter(name => name.includes("张"));
console.log(zhangNames); // 输出:["张三", "张晓明"]
// 示例3:筛选非空字符串
const mixedStrs = ["hello", "", "world", " ", "javascript", null];
const validStrs = mixedStrs.filter(str => typeof str === "string" && str.trim() !== "");
console.log(validStrs); // 输出:["hello", "world", "javascript"]3.2 过滤对象数组
对象数组是开发中最常用的场景,filter 可基于对象的任意属性进行筛选。
// 数据源:用户列表
const users = [
{ id: 1, name: "张三", age: 25, gender: "male", role: "admin" },
{ id: 2, name: "李四", age: 17, gender: "female", role: "user" },
{ id: 3, name: "王五", age: 32, gender: "male", role: "admin" },
{ id: 4, name: "赵六", age: 28, gender: "male", role: "user" },
{ id: 5, name: "钱七", age: 16, gender: "female", role: "user" }
];
// 示例1:筛选成年用户(age >= 18)
const adultUsers = users.filter(user => user.age >= 18);
console.log(adultUsers); // 输出:id为1、3、4的用户
// 示例2:筛选管理员用户(role = "admin")
const adminUsers = users.filter(user => user.role === "admin");
console.log(adminUsers); // 输出:id为1、3的用户
// 示例3:多条件筛选(男性且成年)
const adultMaleUsers = users.filter(user => user.gender === "male" && user.age >= 18);
console.log(adultMaleUsers); // 输出:id为1、3、4的用户
// 示例4:基于嵌套属性筛选(假设有地址属性)
const usersWithAddress = [
{ id: 1, name: "张三", address: { province: "广东", city: "深圳" } },
{ id: 2, name: "李四", address: { province: "广东", city: "广州" } },
{ id: 3, name: "王五", address: { province: "浙江", city: "杭州" } }
];
const guangdongUsers = usersWithAddress.filter(user => user.address.province === "广东");
console.log(guangdongUsers); // 输出:id为1、2的用户3.3 过滤特殊值与空值
处理数组中的 null、undefined、空对象等特殊值是数据清洗的常见需求。
// 数据源:包含特殊值的混合数组
const mixedArray = [
123,
null,
"hello",
undefined,
{ name: "张三" },
"",
0,
NaN,
[],
{},
function() {},
true
];
// 示例1:筛选非null且非undefined的值
const nonNullUndefined = mixedArray.filter(item => item !== null && item !== undefined);
console.log(nonNullUndefined);
// 输出:[123, "hello", { name: "张三" }, "", 0, NaN, [], {}, function() {}, true]
// 示例2:筛选有效数字(排除NaN、0)
const validNumbers = mixedArray.filter(item => typeof item === "number" && !isNaN(item) && item !== 0);
console.log(validNumbers); // 输出:[123]
// 示例3:筛选非空对象(排除空对象、数组)
const nonEmptyObjects = mixedArray.filter(item =>
typeof item === "object" &&
item !== null &&
Object.keys(item).length > 0
);
console.log(nonEmptyObjects); // 输出:[{ name: "张三" }]
// 示例4:筛选真值(排除falsy值)
const truthyValues = mixedArray.filter(Boolean);
console.log(truthyValues);
// 输出:[123, "hello", { name: "张三" }, function() {}, true]3.4 过滤类数组对象
filter 是数组的方法,但可通过 Array.prototype.filter.call() 或 Array.from() 应用于类数组对象(如 arguments、NodeList 等)。
// 示例1:处理arguments对象
function filterEvenNumbers() {
// 将arguments转换为数组后使用filter
return Array.from(arguments).filter(num => num % 2 === 0);
}
const evenNums = filterEvenNumbers(1, 2, 3, 4, 5, 6);
console.log(evenNums); // 输出:[2, 4, 6]
// 示例2:处理DOM元素集合(NodeList)
// 实际环境中需在浏览器端运行
// 获取所有按钮元素,筛选出禁用的按钮
// const buttons = document.querySelectorAll("button");
// const disabledButtons = Array.prototype.filter.call(buttons, btn => btn.disabled);
// console.log(disabledButtons); // 输出:所有禁用的按钮元素
// 示例3:处理字符串(字符串也是类数组)
const str = "javascript";
const vowels = Array.from(str).filter(char => ["a", "e", "i", "o", "u"].includes(char));
console.log(vowels); // 输出:["a", "a", "i"]四、filter 高级使用技巧
4.1 结合其他数组方法链式调用
filter 常与 map、reduce、sort 等方法结合,实现复杂的数据处理逻辑。
// 数据源:商品列表
const products = [
{ id: 1, name: "手机", category: "电子", price: 3999, stock: 50 },
{ id: 2, name: "衬衫", category: "服装", price: 199, stock: 120 },
{ id: 3, name: "电脑", category: "电子", price: 6999, stock: 30 },
{ id: 4, name: "裤子", category: "服装", price: 299, stock: 80 },
{ id: 5, name: "耳机", category: "电子", price: 499, stock: 100 }
];
// 示例1:filter + map:筛选电子类商品并提取名称和价格
const electronicProducts = products
.filter(product => product.category === "电子")
.map(product => ({ name: product.name, price: product.price }));
console.log(electronicProducts);
// 输出:[{name: "手机", price: 3999}, {name: "电脑", price: 6999}, {name: "耳机", price: 499}]
// 示例2:filter + reduce:计算服装类商品的总库存
const clothingStockTotal = products
.filter(product => product.category === "服装")
.reduce((total, product) => total + product.stock, 0);
console.log(clothingStockTotal); // 输出:200
// 示例3:filter + sort + map:筛选价格>1000的商品,按价格排序,提取名称
const expensiveProducts = products
.filter(product => product.price > 1000)
.sort((a, b) => a.price - b.price)
.map(product => product.name);
console.log(expensiveProducts); // 输出:["手机", "电脑"]4.2 实现数组去重
利用 filter 结合 indexOf 或 includes 可实现数组去重,适用于简单数据类型数组。
// 示例1:基础去重(利用indexOf)
const duplicateNums = [1, 2, 2, 3, 3, 3, 4, 5, 5];
const uniqueNums1 = duplicateNums.filter((num, index, array) => {
// 只保留第一次出现的元素(indexOf返回第一次出现的索引)
return array.indexOf(num) === index;
});
console.log(uniqueNums1); // 输出:[1, 2, 3, 4, 5]
// 示例2:去重优化(利用includes)
const duplicateStrs = ["a", "b", "a", "c", "b", "d"];
const uniqueStrs = duplicateStrs.filter((str, index, array) => {
// 检查当前元素是否已在前面出现过
return !array.slice(0, index).includes(str);
});
console.log(uniqueStrs); // 输出:["a", "b", "c", "d"]
// 示例3:对象数组去重(基于id属性)
const duplicateUsers = [
{ id: 1, name: "张三" },
{ id: 2, name: "李四" },
{ id: 1, name: "张三" },
{ id: 3, name: "王五" }
];
const uniqueUsers = duplicateUsers.filter((user, index, array) => {
// 收集前面已出现的id,判断当前id是否存在
const existingIds = array.slice(0, index).map(item => item.id);
return !existingIds.includes(user.id);
});
console.log(uniqueUsers); // 输出:id为1、2、3的用户4.3 嵌套数组的过滤
处理多维数组时,可结合递归或 flat 方法与 filter 配合使用。
// 示例1:二维数组过滤(直接遍历)
const twoDArray = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
// 筛选所有子数组中的偶数
const evenNumbers = twoDArray.map(subArray =>
subArray.filter(num => num % 2 === 0)
);
console.log(evenNumbers); // 输出:[[2], [4, 6], [8]]
// 示例2:二维数组扁平过滤(先扁平再过滤)
const flatEvenNumbers = twoDArray
.flat() // 先将二维数组转为一维数组
.filter(num => num % 2 === 0);
console.log(flatEvenNumbers); // 输出:[2, 4, 6, 8]
// 示例3:深层嵌套数组过滤(递归实现)
const deepArray = [
1,
[2, [3, 4], 5],
[6, [7, [8, 9]]]
];
// 递归扁平化函数
function flattenArray(arr) {
return arr.reduce((acc, item) =>
Array.isArray(item) ? acc.concat(flattenArray(item)) : acc.concat(item), []);
}
// 筛选大于5的数字
const filteredDeepNums = flattenArray(deepArray).filter(num => num > 5);
console.log(filteredDeepNums); // 输出:[6, 7, 8, 9]4.4 动态条件筛选
通过动态生成筛选条件,实现灵活的多条件组合筛选。
// 数据源:图书列表
const books = [
{ id: 1, title: "JavaScript高级程序设计", category: "编程", price: 99, publishYear: 2020 },
{ id: 2, title: "深入理解计算机系统", category: "计算机", price: 128, publishYear: 2018 },
{ id: 3, title: "React设计模式与最佳实践", category: "编程", price: 79, publishYear: 2021 },
{ id: 4, title: "算法导论", category: "计算机", price: 118, publishYear: 2019 },
{ id: 5, title: "TypeScript实战", category: "编程", price: 89, publishYear: 2022 }
];
// 动态筛选函数:接收筛选条件对象,返回符合条件的图书
function filterBooks(books, conditions) {
return books.filter(book => {
// 遍历所有筛选条件,全部满足才返回true
return Object.entries(conditions).every(([key, value]) => {
// 若条件值为数组,判断是否包含(适用于多选项)
if (Array.isArray(value)) {
return value.includes(book[key]);
}
// 若条件值为函数,执行函数判断
if (typeof value === "function") {
return value(book[key]);
}
// 默认精确匹配
return book[key] === value;
});
});
}
// 示例1:筛选编程类且价格<100的图书
const condition1 = {
category: "编程",
price: price => price < 100
};
const result1 = filterBooks(books, condition1);
console.log(result1); // 输出:id为1、3、5的图书
// 示例2:筛选2020年后出版的计算机或编程类图书
const condition2 = {
category: ["计算机", "编程"],
publishYear: year => year >= 2020
};
const result2 = filterBooks(books, condition2);
console.log(result2); // 输出:id为1、3、5的图书4.5 在 TypeScript 中使用 filter
TypeScript 中使用 filter 时,可通过类型守卫实现更精确的类型推断。
// 示例1:基础类型过滤
const mixedValues: (number | string | boolean)[] = [1, "2", 3, "4", true, 5];
// 筛选数字类型,TypeScript可自动推断结果为number[]
const numbersOnly = mixedValues.filter(value => typeof value === "number");
// 示例2:对象类型过滤(类型守卫)
interface User {
id: number;
name: string;
role: "admin" | "user" | "guest";
}
interface Product {
id: number;
name: string;
price: number;
}
// 联合类型数组
const items: (User | Product)[] = [
{ id: 1, name: "张三", role: "admin" },
{ id: 2, name: "手机", price: 3999 },
{ id: 3, name: "李四", role: "user" },
{ id: 4, name: "电脑", price: 6999 }
];
// 自定义类型守卫函数
function isUser(item: User | Product): item is User {
return "role" in item;
}
function isProduct(item: User | Product): item is Product {
return "price" in item;
}
// 筛选用户类型,结果类型为User[]
const users = items.filter(isUser);
// 筛选商品类型,结果类型为Product[]
const products = items.filter(isProduct);4.6 利用 thisArg 绑定上下文
当筛选逻辑需要访问外部对象的属性时,可通过 thisArg 参数绑定上下文。
// 示例:根据配置对象筛选数组
const config = {
minScore: 60,
maxScore: 100,
subject: "math"
};
// 学生成绩数据
const studentScores = [
{ name: "张三", math: 85, english: 72 },
{ name: "李四", math: 58, english: 88 },
{ name: "王五", math: 92, english: 65 },
{ name: "赵六", math: 45, english: 90 }
];
// 筛选指定科目成绩在[minScore, maxScore]范围内的学生
const qualifiedStudents = studentScores.filter(function(student) {
const score = student[this.subject];
return score >= this.minScore && score <= this.maxScore;
}, config); // 绑定this为config对象
console.log(qualifiedStudents); // 输出:张三、王五(math成绩合格)五、filter 实战开发案例
5.1 实战案例 1:前端表格数据筛选
实现一个支持多条件筛选的商品表格,包含价格区间、分类筛选、库存状态筛选。
<!-- HTML结构(仅作演示,实际需结合前端框架) -->
<!--
<div class="filter-panel">
<select id="category-filter">
<option value="all">所有分类</option>
<option value="electronics">电子产品</option>
<option value="clothing">服装</option>
<option value="home">家居用品</option>
</select>
<input type="number" id="min-price" placeholder="最低价格">
<input type="number" id="max-price" placeholder="最高价格">
<select id="stock-filter">
<option value="all">所有库存</option>
<option value="in-stock">有库存</option>
<option value="out-stock">无库存</option>
</select>
<button id="filter-btn">筛选</button>
</div>
<table id="product-table">
<!-- 表格内容动态生成 -->
</table>
-->
<script>
// 1. 模拟商品数据
const productData = [
{ id: 1, name: "智能手机", category: "electronics", price: 3999, stock: 50, brand: "小米" },
{ id: 2, name: "纯棉衬衫", category: "clothing", price: 199, stock: 0, brand: "优衣库" },
{ id: 3, name: "笔记本电脑", category: "electronics", price: 6999, stock: 20, brand: "苹果" },
{ id: 4, name: "沙发", category: "home", price: 2999, stock: 8, brand: "宜家" },
{ id: 5, name: "牛仔裤", category: "clothing", price: 299, stock: 35, brand: "李维斯" },
{ id: 6, name: "台灯", category: "home", price: 99, stock: 0, brand: "飞利浦" },
{ id: 7, name: "耳机", category: "electronics", price: 499, stock: 100, brand: "华为" },
{ id: 8, name: "床单", category: "home", price: 159, stock: 42, brand: "水星家纺" }
];
// 2. 筛选逻辑实现
function filterProducts(products, filters) {
return products.filter(product => {
// 分类筛选
if (filters.category!== "all" && product.category!== filters.category) {
return false;
}
// 价格区间筛选
if (filters.minPrice!== "" && product.price < Number(filters.minPrice)) {
return false;
}
if (filters.maxPrice!== "" && product.price > Number(filters.maxPrice)) {
return false;
}
// 库存筛选
if (filters.stock === "in-stock" && product.stock === 0) {
return false;
}
if (filters.stock === "out-stock" && product.stock > 0) {
return false;
}
return true;
});
}
// 3. 绑定筛选事件(模拟)
function handleFilter() {
// 模拟获取筛选条件(实际从DOM元素获取)
const filters = {
category: "electronics", // 从下拉框获取
minPrice: "1000", // 从输入框获取
maxPrice: "8000", // 从输入框获取
stock: "in-stock" // 从下拉框获取
};
// 执行筛选
const filteredProducts = filterProducts(productData, filters);
// 渲染筛选结果(实际需生成表格HTML)
console.log("筛选结果:", filteredProducts);
// 输出:[{id:1,...}, {id:3,...}, {id:7,...}]
}
// 模拟点击筛选按钮
handleFilter();
</script>5.2 实战案例 2:接口返回数据清洗
处理后端接口返回的复杂数据,筛选有效数据并格式化输出。
// 1. 模拟后端接口返回数据
const apiResponse = {
code: 200,
message: "success",
data: {
users: [
{ id: 1, name: "张三", age: 25, email: "zhangsan@example.com", status: 1 },
{ id: 2, name: "", age: 0, email: "lisi@example.com", status: 0 },
{ id: 3, name: "王五", age: -5, email: "wangwu.example.com", status: 1 },
{ id: 4, name: "赵六", age: 32, email: "zhaoliu@example.com", status: 1 },
{ id: null, name: "钱七", age: 28, email: "", status: 2 },
{ id: 6, name: "孙八", age: 45, email: "sunba@example.com", status: 1 }
],
total: 6
}
};
// 2. 数据清洗工具函数
const emailRegex = /^[^s@]+@[^s@]+.[^s@]+$/;
function cleanUserData(rawData) {
// 边界判断:确保数据存在
if (!rawData || rawData.code!== 200 ||!rawData.data?.users) {
return { cleanedUsers: [], count: 0 };
}
const cleanedUsers = rawData.data.users
// 筛选有效用户:id存在且为数字、name非空、age为正整数、email格式正确、status为1
.filter(user =>
typeof user.id === "number" &&
user.id > 0 &&
typeof user.name === "string" &&
user.name.trim()!== "" &&
typeof user.age === "number" &&
user.age > 0 &&
emailRegex.test(user.email) &&
user.status === 1
)
// 格式化用户数据
.map(user => ({
userId: user.id,
userName: user.name.trim(),
userAge: user.age,
userEmail: user.email.toLowerCase()
}));
return {
cleanedUsers,
count: cleanedUsers.length
};
}
// 3. 执行数据清洗
const { cleanedUsers, count } = cleanUserData(apiResponse);
console.log(`有效用户数:${count}`); // 输出:3
console.log("清洗后的用户数据:", cleanedUsers);
// 输出:id为1、4、6的用户(已格式化)5.3 实战案例 3:数组结构转换与筛选
将扁平数组转换为树形结构,并筛选出包含指定节点的子树。
// 1. 模拟扁平结构的部门数据
const departments = [
{ id: 1, name: "技术部", parentId: 0 },
{ id: 2, name: "产品部", parentId: 0 },
{ id: 3, name: "前端开发", parentId: 1 },
{ id: 4, name: "后端开发", parentId: 1 },
{ id: 5, name: "UI设计", parentId: 2 },
{ id: 6, name: "产品经理", parentId: 2 },
{ id: 7, name: "React开发", parentId: 3 },
{ id: 8, name: "Vue开发", parentId: 3 },
{ id: 9, name: "Java开发", parentId: 4 },
{ id: 10, name: "测试部", parentId: 0 }
];
// 2. 扁平转树形结构函数
function buildTree(nodes, parentId = 0) {
return nodes
// 筛选当前父节点的子节点
.filter(node => node.parentId === parentId)
// 递归构建子树
.map(node => ({
...node,
children: buildTree(nodes, node.id)
}));
}
// 3. 筛选包含指定节点的子树
function filterTreeByNode(tree, targetNodeId) {
// 遍历树节点
return tree.filter(node => {
// 若当前节点是目标节点,保留
if (node.id === targetNodeId) {
return true;
}
// 若当前节点有子节点,递归筛选子节点
if (node.children && node.children.length > 0) {
const filteredChildren = filterTreeByNode(node.children, targetNodeId);
// 若子节点中包含目标节点,更新子节点并保留当前节点
if (filteredChildren.length > 0) {
node.children = filteredChildren;
return true;
}
}
// 既不是目标节点,子节点也不包含目标节点,排除
return false;
});
}
// 4. 执行转换与筛选
// 构建完整树形结构
const fullTree = buildTree(departments);
console.log("完整部门树:", fullTree);
// 筛选包含"React开发"(id=7)的子树
const filteredTree = filterTreeByNode(fullTree, 7);
console.log("筛选后的部门树:", filteredTree);
// 输出:技术部 -> 前端开发 -> React开发 的层级结构六、filter 常见问题与坑点
6.1 原数组不变的特性
filter 不会修改原数组,而是返回新数组。若试图通过 filter 修改原数组元素,会导致意外结果。
// 错误示例:试图通过filter修改原数组
const numbers = [1, 2, 3, 4, 5];
const filtered = numbers.filter(num => {
num *= 2; // 修改原数组元素
return num > 5;
});
console.log(filtered); // 输出:[3, 4, 5](实际是6、8、10的判断结果)
console.log(numbers); // 输出:[1, 2, 6, 8, 10](原数组被修改,造成副作用)
// 正确做法:筛选与修改分离
const correctFiltered = numbers
.filter(num => num > 2.5) // 先筛选
.map(num => num * 2); // 再修改
console.log(correctFiltered); // 输出:[12, 16, 20]
console.log(numbers); // 输出:[1, 2, 6, 8, 10](原数组保持不变)6.2 this 指向问题
若未指定 thisArg,回调函数中的 this 指向可能不符合预期,尤其是在箭头函数与普通函数混用的场景。
// 问题示例:this指向错误
const filterConfig = {
threshold: 5
};
const numbers = [1, 3, 5, 7, 9];
// 普通函数作为回调,this指向全局对象
const filtered1 = numbers.filter(function(num) {
return num > this.threshold; // this.threshold 为undefined
});
console.log(filtered1); // 输出:[](错误结果)
// 解决方案1:指定thisArg参数
const filtered2 = numbers.filter(function(num) {
return num > this.threshold;
}, filterConfig);
console.log(filtered2); // 输出:[7, 9](正确结果)
// 解决方案2:使用箭头函数(继承外部this)
const filtered3 = numbers.filter(num => num > filterConfig.threshold);
console.log(filtered3); // 输出:[7, 9](正确结果)6.3 空数组与 undefined 的处理
filter 对空数组返回空数组,对包含 undefined 的数组会将其视为 falsy 值过滤。
// 示例1:空数组处理 const emptyArray = []; const filteredEmpty = emptyArray.filter(item => item > 0); console.log(filteredEmpty); // 输出:[](无错误,返回空数组) // 示例2:包含undefined的数组 const arrayWithUndefined = [1, undefined, 3, undefined, 5]; // 直接筛选会排除undefined const filteredUndefined1 = arrayWithUndefined.filter(item => item); console.log(filteredUndefined1); // 输出:[1, 3, 5] // 如需保留undefined,需显式判断 const filteredUndefined2 = arrayWithUndefined.filter(item => item!== undefined); console.log(filteredUndefined2); // 输出:[1, 3, 5](同上,因为undefined !== undefined为false) // 如需筛选出undefined,需反向判断 const onlyUndefined = arrayWithUndefined.filter(item => item === undefined); console.log(onlyUndefined); // 输出:[undefined, undefined]
6.4 NaN 的过滤问题
由于 NaN !== NaN,直接判断 NaN 会导致筛选失败,需使用 isNaN 函数。
// 问题示例:直接判断NaN失败 const arrayWithNaN = [1, 2, NaN, 3, NaN, 4]; // 错误:NaN !== NaN,无法筛选出NaN const filteredNaN1 = arrayWithNaN.filter(item => item === NaN); console.log(filteredNaN1); // 输出:[] // 正确示例:使用isNaN函数 const filteredNaN2 = arrayWithNaN.filter(item => isNaN(item)); console.log(filteredNaN2); // 输出:[NaN, NaN] // 筛选非NaN值 const nonNaNValues = arrayWithNaN.filter(item => !isNaN(item)); console.log(nonNaNValues); // 输出:[1, 2, 3, 4]
6.5 与 find 方法的区别
filter 与 find 容易混淆,核心区别在于返回值:filter 返回所有符合条件的元素数组,find 返回第一个符合条件的元素。
const users = [
{ id: 1, name: "张三", age: 25 },
{ id: 2, name: "李四", age: 30 },
{ id: 3, name: "王五", age: 25 }
];
// 使用filter:返回所有25岁的用户(数组)
const usersAge25 = users.filter(user => user.age === 25);
console.log(usersAge25); // 输出:[{id:1,...}, {id:3,...}]
// 使用find:返回第一个25岁的用户(对象)
const firstUserAge25 = users.find(user => user.age === 25);
console.log(firstUserAge25); // 输出:{id:1, name: "张三", age:25}
// 性能差异:find找到第一个匹配项后停止遍历,filter需遍历整个数组
// 如需获取单个元素,优先使用find6.6 稀疏数组的处理
filter 会跳过数组中的空槽(empty),只处理已初始化的元素。
// 创建稀疏数组(索引1和3为空槽)
const sparseArray = [1,, 3,, 5];
console.log(sparseArray.length); // 输出:5
// filter跳过空槽
const filteredSparse = sparseArray.filter(item => item > 2);
console.log(filteredSparse); // 输出:[3, 5](空槽被跳过)
// 验证空槽是否被处理
const checkedSparse = sparseArray.filter((item, index) => {
console.log(`索引${index}:${item}`);
return true;
});
// 输出:
// 索引0:1
// 索引2:3
// 索引4:5
// 空槽索引1和3未被处理七、filter 性能优化策略
7.1 避免在回调中执行复杂操作
回调函数中的复杂计算会增加迭代成本,尤其是大数据量数组。应将复杂操作移到 filter 外部。
// 优化前:回调中执行复杂计算
const largeArray = Array.from({ length: 100000 }, (_, i) => i);
// 回调中包含复杂的数学计算
console.time("optimize-before");
const resultBefore = largeArray.filter(num => {
// 复杂计算:质数判断
if (num <= 1) return false;
for (let i = 2; i <= Math.sqrt(num); i++) {
if (num % i === 0) return false;
}
return true;
});
console.timeEnd("optimize-before"); // 耗时较长
// 优化后:将复杂计算封装为纯函数,减少回调复杂度(对性能影响较小,但代码更清晰)
// 进一步优化:使用缓存或提前计算(若计算结果可复用)
function isPrime(num) {
if (num <= 1) return false;
for (let i = 2; i <= Math.sqrt(num); i++) {
if (num % i === 0) return false;
}
return true;
}
console.time("optimize-after");
const resultAfter = largeArray.filter(isPrime);
console.timeEnd("optimize-after"); // 耗时与优化前相近,但代码可维护性更高7.2 大数据量下的筛选优化
当数组长度超过 10 万时,可考虑分批次筛选或结合其他方法优化。
// 大数据量筛选优化:分批次处理(避免阻塞主线程)
async function filterLargeArray(largeArray, filterFn, batchSize = 10000) {
const result = [];
const totalLength = largeArray.length;
for (let i = 0; i < totalLength; i += batchSize) {
// 分批次处理
const batch = largeArray.slice(i, i + batchSize);
const filteredBatch = batch.filter(filterFn);
result.push(...filteredBatch);
// 每处理一批次,让出主线程(避免UI阻塞)
await new Promise(resolve => setTimeout(resolve, 0));
}
return result;
}
// 使用示例
const hugeArray = Array.from({ length: 500000 }, (_, i) => i);
filterLargeArray(hugeArray, num => num % 100 === 0)
.then(filteredResult => {
console.log(`筛选结果数量:${filteredResult.length}`); // 输出:5000
});7.3 提前退出筛选(替代方案)
filter 必须遍历整个数组,若只需找到部分符合条件的元素,可使用 for 循环提前退出。
// 场景:从大数据量中筛选前100个符合条件的元素
const largeArray = Array.from({ length: 100000 }, (_, i) => i);
// 方案1:使用filter(需遍历整个数组)
console.time("filter-full");
const filterResult = largeArray.filter(num => num % 100 === 0).slice(0, 100);
console.timeEnd("filter-full"); // 遍历10万次
// 方案2:使用for循环(找到100个后提前退出)
console.time("for-early-exit");
const forResult = [];
for (let i = 0; i < largeArray.length; i++) {
if (largeArray[i] % 100 === 0) {
forResult.push(largeArray[i]);
// 找到100个后退出循环
if (forResult.length === 100) break;
}
}
console.timeEnd("for-early-exit"); // 仅遍历10000次左右,性能提升显著7.4 避免不必要的类型转换
在回调中频繁进行类型转换会影响性能,应提前统一转换类型。
// 优化前:回调中频繁转换类型
const stringNumbers = Array.from({ length: 100000 }, (_, i) => i.toString());
console.time("convert-in-callback");
const convertedResult1 = stringNumbers.filter(str => {
const num = Number(str); // 每次回调都进行类型转换
return num > 50000;
});
console.timeEnd("convert-in-callback");
// 优化后:提前转换类型,再筛选
console.time("convert-before-filter");
const numbers = stringNumbers.map(str => Number(str)); // 一次性转换
const convertedResult2 = numbers.filter(num => num > 50000);
console.timeEnd("convert-before-filter"); // 性能更优,尤其是大数据量八、filter 与相似方法的区别
8.1 filter vs map
filter:用于筛选元素,返回符合条件的元素组成的新数组,数组长度可能减少
map:用于转换元素,返回与原数组长度相同的新数组,元素值被转换
const numbers = [1, 2, 3, 4, 5]; // filter:筛选偶数(长度减少) const filtered = numbers.filter(num => num % 2 === 0); console.log(filtered); // 输出:[2, 4] // map:将数字翻倍(长度不变) const mapped = numbers.map(num => num * 2); console.log(mapped); // 输出:[2, 4, 6, 8, 10] // 组合使用:先筛选再转换 const combined = numbers.filter(num => num % 2 === 0).map(num => num * 2); console.log(combined); // 输出:[4, 8]
8.2 filter vs find
filter:返回所有符合条件的元素数组(可能为空)
find:返回第一个符合条件的元素(无符合条件元素时返回 undefined)
const users = [
{ id: 1, name: "张三", age: 25 },
{ id: 2, name: "李四", age: 30 },
{ id: 3, name: "王五", age: 25 }
];
// filter:返回所有25岁用户
const allAge25 = users.filter(user => user.age === 25);
console.log(allAge25); // 输出:[{id:1,...}, {id:3,...}]
// find:返回第一个25岁用户
const firstAge25 = users.find(user => user.age === 25);
console.log(firstAge25); // 输出:{id:1,...}
// 无符合条件时的返回值
const allAge40 = users.filter(user => user.age === 40);
console.log(allAge40); // 输出:[]
const firstAge40 = users.find(user => user.age === 40);
console.log(firstAge40); // 输出:undefined8.3 filter vs reduce
filter:专注于筛选元素,功能单一
reduce:功能更强大,可实现筛选、汇总、转换等多种功能
const numbers = [1, 2, 3, 4, 5, 6];
// 使用filter筛选偶数
const filterEven = numbers.filter(num => num % 2 === 0);
console.log(filterEven); // 输出:[2, 4, 6]
// 使用reduce实现筛选偶数
const reduceEven = numbers.reduce((acc, num) => {
if (num % 2 === 0) {
acc.push(num);
}
return acc;
}, []);
console.log(reduceEven); // 输出:[2, 4, 6]
// reduce的额外能力:筛选并计算总和
const evenSum = numbers.reduce((sum, num) => {
return num % 2 === 0? sum + num : sum;
}, 0);
console.log(evenSum); // 输出:12(2+4+6)8.4 filter vs some/every
filter:返回符合条件的元素数组
some:判断是否存在符合条件的元素,返回布尔值(找到第一个即停止)
every:判断所有元素是否都符合条件,返回布尔值(找到第一个不符合即停止)
const scores = [85, 92, 78, 65, 98]; // filter:返回所有及格分数(>=60) const passedScores = scores.filter(score => score >= 60); console.log(passedScores); // 输出:[85, 92, 78, 65, 98] // some:判断是否有满分(100分) const hasPerfectScore = scores.some(score => score === 100); console.log(hasPerfectScore); // 输出:false // every:判断是否所有分数都及格 const allPassed = scores.every(score => score >= 60); console.log(allPassed); // 输出:true // 性能差异:some/every可提前退出,filter需遍历全部
总结
JavaScript 的 filter 方法是数组处理的核心工具之一,其纯函数特性、简洁语法和强大的筛选能力使其在前端开发中应用广泛。本文从基础概念出发,详细解析了 filter 的语法参数、基础用法、高级技巧,并通过实战案例展示了其在实际开发中的应用,同时总结了常见问题与性能优化策略。
掌握 filter 方法的关键在于:
理解其 “筛选 - 返回新数组” 的核心逻辑
熟练结合其他数组方法实现复杂数据处理
注意 this 指向、空值处理等常见坑点
根据场景选择合适的优化策略
合理运用 filter 方法可以大幅简化数据筛选代码,提高开发效率和代码可读性。在实际开发中,应结合具体需求,灵活搭配其他数组方法,构建高效、可维护的前端数据处理逻辑。
到此这篇关于JavaScript中filter方法的详解与实战记录的文章就介绍到这了,更多相关JS中filter方法详解内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
