js数组过滤的常用方法


一、按方法分类的数组过滤技术

方法一:使用 filter() + Set 进行高效过滤

基本语法

const smallSet = new Set(smallArray);
const result = bigArray.filter(item => !smallSet.has(item));

纯数组示例

const bigArray = [1, 2, 3, 4, 5, 6, 7, 8];
const smallArray = [2, 4, 6, 9]; // 9不在大数组中,自动忽略

const smallSet = new Set(smallArray);
const result = bigArray.filter(item => !smallSet.has(item));
console.log(result); // [1, 3, 5, 7, 8]

对象数组示例

const bigArray = [
    { id: 1, name: "Alice" },
    { id: 2, name: "Bob" },
    { id: 3, name: "Charlie" },
    { id: 4, name: "David" },
];
const smallArray = [
    { id: 2, name: "Bob" },
    { id: 4, name: "David" },
    { id: 5, name: "Eve" }, // 不在大数组中,自动忽略
];

// 提取关键属性创建Set
const smallIds = new Set(smallArray.map(item => item.id));
const result = bigArray.filter(item => !smallIds.has(item.id));
console.log(result); // [{id: 1, name: 'Alice'}, {id: 3, name: 'Charlie'}]

方法二:使用 filter() + includes() 进行简单过滤

基本语法

const result = bigArray.filter(item => !smallArray.includes(item));

纯数组示例

const bigArray = [1, 2, 3, 4, 5];
const smallArray = [2, 4];

const result = bigArray.filter(item => !smallArray.includes(item));
console.log(result); // [1, 3, 5]

对象数组示例

const bigArray = [
    { id: 1, name: "Alice" },
    { id: 2, name: "Bob" },
    { id: 3, name: "Charlie" },
];
const smallArray = [
    { id: 2, name: "Bob" },
    { id: 4, name: "David" },
];

// 需要先提取比较的属性
const smallIds = smallArray.map(item => item.id);
const result = bigArray.filter(item => !smallIds.includes(item.id));
console.log(result); // [{id: 1, name: 'Alice'}, {id: 3, name: 'Charlie'}]

方法三:使用 filter() + find()filter() + some() 进行复杂条件过滤

基本语法

// 使用 find(找到第一个匹配项)
const result = bigArray.filter(item => !smallArray.find(smallItem =>
    /* 自定义匹配条件 */
));

// 使用 some(检查是否存在匹配项)
const result = bigArray.filter(item => !smallArray.some(smallItem =>
    /* 自定义匹配条件 */
));

纯数组示例

const bigArray = [10, 20, 30, 40, 50];
const smallArray = [20, 40];

// 使用 some(更语义化)
const result = bigArray.filter(item => !smallArray.some(smallItem => smallItem === item));
console.log(result); // [10, 30, 50]

对象数组示例

const bigArray = [
    { id: 1, name: "Alice", age: 25 },
    { id: 2, name: "Bob", age: 30 },
    { id: 3, name: "Charlie", age: 25 },
];
const smallArray = [
    { id: 2, name: "Bob" },
    { name: "David" }, // 部分匹配条件
];

// 多条件匹配
const result = bigArray.filter(
    bigItem =>
        !smallArray.some(
            smallItem =>
                (smallItem.id && smallItem.id === bigItem.id) ||
                (smallItem.name && smallItem.name === bigItem.name)
        )
);
console.log(result); // [{id: 1, name: 'Alice', age: 25}, {id: 3, name: 'Charlie', age: 25}]

方法四:使用 filter() + Map 进行高性能对象过滤

基本语法

const smallMap = new Map(smallArray.map(item => [key, item]));
const result = bigArray.filter(item => !smallMap.has(item[key]));

对象数组示例

const bigArray = [
    { id: 1, name: "Alice" },
    { id: 2, name: "Bob" },
    { id: 3, name: "Charlie" },
    { id: 4, name: "David" },
];
const smallArray = [
    { id: 2, name: "Bob" },
    { id: 4, name: "David" },
];

// 使用Map存储,便于后续获取完整对象信息
const smallMap = new Map(smallArray.map(item => [item.id, item]));
const result = bigArray.filter(item => !smallMap.has(item.id));
console.log(result); // [{id: 1, name: 'Alice'}, {id: 3, name: 'Charlie'}]

方法五:使用 reduce() 进行累积过滤

基本语法

const result = bigArray.reduce((acc, item) => {
    if (!shouldFilter(item)) {
        acc.push(item);
    }
    return acc;
}, []);

纯数组示例

const bigArray = [1, 2, 3, 4, 5];
const smallArray = [2, 4];

const result = bigArray.reduce((acc, item) => {
    if (!smallArray.includes(item)) {
        acc.push(item);
    }
    return acc;
}, []);
console.log(result); // [1, 3, 5]

对象数组示例

const bigArray = [
    { id: 1, name: "Alice" },
    { id: 2, name: "Bob" },
    { id: 3, name: "Charlie" },
];
const smallArray = [{ id: 2, name: "Bob" }];

const smallIds = new Set(smallArray.map(item => item.id));
const result = bigArray.reduce((acc, item) => {
    if (!smallIds.has(item.id)) {
        acc.push(item);
    }
    return acc;
}, []);
console.log(result); // [{id: 1, name: 'Alice'}, {id: 3, name: 'Charlie'}]

方法六:使用 for 循环(性能最优)

基本语法

function arrayDifference(bigArray, smallArray, key = null) {
    const result = [];

    // 创建过滤条件的快速查找结构
    const smallSet = createFilterSet(smallArray, key);

    // 使用传统 for 循环进行高效迭代
    for (let i = 0; i < bigArray.length; i++) {
        const item = bigArray[i];
        const compareValue = key ? item[key] : item;

        if (!smallSet.has(compareValue)) {
            result.push(item);
        }
    }

    return result;
}

function createFilterSet(array, key) {
    if (!array.length) return new Set();

    if (key) {
        // 对象数组:提取关键属性
        const values = [];
        for (let i = 0; i < array.length; i++) {
            if (array[i] && array[i][key] !== undefined) {
                values.push(array[i][key]);
            }
        }
        return new Set(values);
    } else {
        // 纯数组:直接使用
        return new Set(array);
    }
}

纯数组示例

function filterPureArrays(bigArray, smallArray) {
    const result = [];
    const smallSet = new Set(smallArray);

    // 传统 for 循环 - 最高性能
    for (let i = 0; i < bigArray.length; i++) {
        if (!smallSet.has(bigArray[i])) {
            result.push(bigArray[i]);
        }
    }
    return result;
}

// 使用示例
const bigArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const smallArray = [2, 4, 6, 8, 12]; // 12 不在大数组中,自动忽略

console.log(filterPureArrays(bigArray, smallArray));
// [1, 3, 5, 7, 9, 10]

对象数组示例

function filterObjectArrays(bigArray, smallArray, key = "id") {
    const result = [];

    // 创建 ID 查找表
    const idSet = new Set();
    for (let i = 0; i < smallArray.length; i++) {
        if (smallArray[i] && smallArray[i][key] !== undefined) {
            idSet.add(smallArray[i][key]);
        }
    }

    // 过滤大数组
    for (let i = 0; i < bigArray.length; i++) {
        const item = bigArray[i];
        if (item && item[key] !== undefined && !idSet.has(item[key])) {
            result.push(item);
        }
    }

    return result;
}

// 使用示例
const bigArray = [
    { id: 1, name: "Alice", age: 25 },
    { id: 2, name: "Bob", age: 30 },
    { id: 3, name: "Charlie", age: 35 },
    { id: 4, name: "David", age: 28 },
    { id: 5, name: "Eve", age: 32 },
];

const smallArray = [
    { id: 2, name: "Bob" },
    { id: 4, name: "David" },
    { id: 6, name: "Frank" }, // 不在大数组中,自动忽略
];

console.log(filterObjectArrays(bigArray, smallArray, "id"));
// 输出: [
//   {id: 1, name: 'Alice', age: 25},
//   {id: 3, name: 'Charlie', age: 35},
//   {id: 5, name: 'Eve', age: 32}
// ]

二、Set.has() vs Array.includes() 深度对比

1.性能测试代码

function performanceTest() {
    // 创建测试数据
    const sizes = [100, 1000, 10000, 100000];

    sizes.forEach(size => {
        console.log(`\n=== 数据量: ${size} 个元素 ===`);

        const bigArray = Array.from({ length: size }, (_, i) => i);
        const smallArray = bigArray.filter((_, i) => i % 3 === 0); // 取1/3作为过滤数组

        // Set方法测试
        console.time("Set方法");
        const smallSet = new Set(smallArray);
        const result1 = bigArray.filter(item => !smallSet.has(item));
        console.timeEnd("Set方法");

        // includes方法测试
        console.time("includes方法");
        const result2 = bigArray.filter(item => !smallArray.includes(item));
        console.timeEnd("includes方法");

        // 验证结果一致性
        console.log("结果一致:", JSON.stringify(result1) === JSON.stringify(result2));
    });
}

performanceTest();

2.性能差异分析表

数据量 Set.has() 时间 Array.includes() 时间 性能差异
100 元素 ~0.1ms ~0.3ms 3 倍
1000 元素 ~0.2ms ~8ms 40 倍
10000 元素 ~0.5ms ~250ms 500 倍

3.时间复杂度对比

方法 平均时间复杂度 最坏情况 空间复杂度
Array.includes() O(n) O(n) O(1)
Set.has() O(1) O(n) O(n)

4.底层实现原理

Array.includes() 实现原理

// 简化的 includes 实现
Array.prototype.includes = function (searchElement) {
    for (let i = 0; i < this.length; i++) {
        if (this[i] === searchElement) {
            return true;
        }
    }
    return false;
};
  • 线性搜索算法
  • 需要遍历整个数组
  • 性能与数组长度成正比

Set.has() 实现原理

// 简化的 Set 实现(基于哈希表)
class SimpleSet {
    constructor() {
        this.buckets = [];
        this.bucketCount = 16;
    }

    add(value) {
        const bucketIndex = this.getBucketIndex(value);
        if (!this.buckets[bucketIndex]) {
            this.buckets[bucketIndex] = [];
        }
        this.buckets[bucketIndex].push(value);
    }

    has(value) {
        const bucketIndex = this.getBucketIndex(value);
        const bucket = this.buckets[bucketIndex];
        if (!bucket) return false;

        return bucket.some(item => item === value);
    }

    getBucketIndex(value) {
        const hash = this.hashFunction(value);
        return hash % this.bucketCount;
    }

    hashFunction(value) {
        // 简化的哈希函数
        return JSON.stringify(value).length;
    }
}
  • 哈希表数据结构
  • 通过哈希函数直接定位
  • 平均情况下常数时间访问,查找时间与数据量无关

5.最佳实践选择指南

选择 Set.has() 的情况:

  1. 数据量较大(> 50 个元素)
  2. 需要频繁过滤操作
  3. 性能敏感的应用场景
  4. 多次使用同一个过滤条件
// ✅ 场景1:大数据量频繁查找
const largeDataset = Array.from({ length: 100000 }, (_, i) => i);
const searchSet = new Set(largeDataset);

// 多次查找性能极佳
for (let i = 0; i < 1000; i++) {
    searchSet.has(Math.random() * 100000);
}

// ✅ 场景2:数组去重
const duplicateArray = [1, 2, 2, 3, 4, 4, 5];
const uniqueArray = [...new Set(duplicateArray)];

// ✅ 场景3:交集、并集、差集运算
const setA = new Set([1, 2, 3]);
const setB = new Set([2, 3, 4]);

// 并集
const union = new Set([...setA, ...setB]);
// 交集
const intersection = new Set([...setA].filter(x => setB.has(x)));
// 差集
const difference = new Set([...setA].filter(x => !setB.has(x)));

选择 Array.includes() 的情况:

  1. 数据量很小(< 20 个元素)
  2. 单次或极少次操作
  3. 代码简洁性优先
  4. 过滤条件简单且不重复使用
// ✅ 场景1:小数据量(< 50个元素)
const smallArray = ["apple", "banana", "orange"];
if (smallArray.includes("apple")) {
    console.log("找到苹果!");
}

// ✅ 场景2:单次或极少次查找
const config = ["debug", "test", "production"];
const currentEnv = "production";
if (config.includes(currentEnv)) {
    // 执行环境特定代码
}

// ✅ 场景3:代码简洁性优先
const validStatuses = ["pending", "processing", "completed"];
function isValidStatus(status) {
    return validStatuses.includes(status); // 代码更易读
}

选择 find()/some() 的情况:

  1. 需要复杂匹配条件
  2. 对象的多属性匹配
  3. 动态过滤条件
// ✅ 复杂条件匹配
const users = [
    { id: 1, role: "admin" },
    { id: 2, role: "user" },
];
const forbiddenConditions = [{ role: "admin" }, { id: 2 }];

const allowedUsers = users.filter(
    user =>
        !forbiddenConditions.some(condition =>
            Object.keys(condition).every(key => user[key] === condition[key])
        )
);

三、通用过滤函数封装

/**
 * 通用数组过滤函数
 * @param {Array} mainArray 主数组
 * @param {Array} filterArray 过滤数组
 * @param {String|Function} [keyOrFn] 键名或自定义匹配函数
 * @param {Object} [options] 配置选项
 * @returns {Array} 过滤后的数组
 */
function filterArray(mainArray, filterArray, keyOrFn = null, options = {}) {
    const { useSet = true } = options;

    if (!mainArray.length) return [];
    if (!filterArray.length) return [...mainArray];

    // 基本类型数组
    if (keyOrFn === null || typeof mainArray[0] !== "object") {
        return useSet && filterArray.length > 20
            ? filterWithSet(mainArray, filterArray)
            : filterWithIncludes(mainArray, filterArray);
    }

    // 对象数组,使用键名
    if (typeof keyOrFn === "string") {
        const key = keyOrFn;
        return useSet && filterArray.length > 10
            ? filterObjectsWithSet(mainArray, filterArray, key)
            : filterObjectsWithIncludes(mainArray, filterArray, key);
    }

    // 自定义匹配函数
    if (typeof keyOrFn === "function") {
        return mainArray.filter(item => !filterArray.some(filterItem => keyOrFn(item, filterItem)));
    }

    return mainArray;
}

// 辅助函数
function filterWithSet(mainArray, filterArray) {
    const filterSet = new Set(filterArray);
    return mainArray.filter(item => !filterSet.has(item));
}

function filterWithIncludes(mainArray, filterArray) {
    return mainArray.filter(item => !filterArray.includes(item));
}

function filterObjectsWithSet(mainArray, filterArray, key) {
    const filterSet = new Set(filterArray.map(item => item[key]));
    return mainArray.filter(item => !filterSet.has(item[key]));
}

function filterObjectsWithIncludes(mainArray, filterArray, key) {
    const filterValues = filterArray.map(item => item[key]);
    return mainArray.filter(item => !filterValues.includes(item[key]));
}

// 使用示例
const numbers = [1, 2, 3, 4, 5];
const toRemove = [2, 4];
console.log(filterArray(numbers, toRemove)); // [1, 3, 5]

const users = [
    { id: 1, name: "A" },
    { id: 2, name: "B" },
    { id: 3, name: "C" },
];
const toRemoveUsers = [{ id: 2 }, { id: 3 }];
console.log(filterArray(users, toRemoveUsers, "id")); // [{id: 1, name: 'A'}]

文章作者: 弈心
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 弈心 !
评论
  目录