每天 Shaarli

一天内的所有链接,汇聚在一个页面上。

September 19, 2024

Note: 活用 ... 展开运算符

要活用 ... 展开运算符,关键是理解它在不同场景下的灵活应用。以下是一些常见的用法场景和技巧,帮助你更好地掌握它的使用。


当你需要更新或合并两个对象时,展开运算符非常方便。例如:

在对象中合并和更新数据

更新对象:
const user = { name: 'Alice', age: 25 };
const updatedUser = { ...user, age: 26 };  // 覆盖 age
console.log(updatedUser);  // { name: 'Alice', age: 26 }

用展开运算符创建一个新对象,其中后面的值会覆盖前面的。

合并多个对象:
const address = { city: 'New York', zip: '10001' };
const profile = { name: 'Bob', age: 30, ...address };
console.log(profile);  // { name: 'Bob', age: 30, city: 'New York', zip: '10001' }

合并多个对象时,先展开基础对象,然后可以覆盖或添加额外属性。

浅拷贝对象或数组

当你需要创建一个对象或数组的副本时,展开运算符能非常方便地实现浅拷贝:

对象浅拷贝:
const original = { a: 1, b: 2 };
const copy = { ...original };
console.log(copy);  // { a: 1, b: 2 }
数组浅拷贝:
const numbers = [1, 2, 3];
const copyNumbers = [...numbers];
console.log(copyNumbers);  // [1, 2, 3]

使用展开运算符进行浅拷贝,避免直接修改原对象或数组。

数组合并

展开运算符不仅用于对象,也可以用来将多个数组合并:

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const mergedArray = [...arr1, ...arr2];
console.log(mergedArray);  // [1, 2, 3, 4, 5, 6]

在处理多个数组时,用展开运算符可以简洁地合并它们,而不需要 concat 方法。

函数参数解构

你可以用展开运算符传递可变数量的参数给函数,或者将数组展开为独立参数:

将数组展开为独立参数:
function sum(x, y, z) {
    return x + y + z;
}
const numbers = [1, 2, 3];
console.log(sum(...numbers));  // 6

当函数参数列表是固定的,但你有一个数组时,展开数组作为独立参数非常方便。

传递不定数量的参数(rest 参数):
function sumAll(...args) {
    return args.reduce((acc, val) => acc + val, 0);
}
console.log(sumAll(1, 2, 3, 4));  // 10

使用 ...args(rest 参数)接收不定数量的参数,然后在函数内部处理它们。

去重数组元素

展开运算符可以结合 Set 对象,快速去重数组元素:

const nums = [1, 2, 2, 3, 4, 4];
const uniqueNums = [...new Set(nums)];
console.log(uniqueNums);  // [1, 2, 3, 4]

用 Set 去重后,用展开运算符将结果转回数组。

条件更新或过滤属性

展开运算符还可以在有条件的情况下更新对象属性,或者从对象中过滤掉某些不需要的字段:

有条件地添加属性:
const obj = {
    name: 'John',
    ...(true && { age: 30 })  // 根据条件动态添加 age 属性
};
console.log(obj);  // { name: 'John', age: 30 }
从对象中过滤掉某些属性:
const { age, ...rest } = { name: 'Alice', age: 25, city: 'LA' };
console.log(rest);  // { name: 'Alice', city: 'LA' }

用解构赋值结合展开运算符,可以从对象中移除不需要的属性。



嵌套解构和展开

当你处理嵌套对象或数组时,可以结合展开运算符和解构赋值,操作特定的嵌套结构:

对象嵌套解构与更新:
const user = {
    name: 'Alice',
    address: {
        city: 'New York',
        zip: '10001'
    }
};

const updatedUser = {
    ...user,
    address: {
        ...user.address,
        zip: '10002'  // 更新嵌套的 zip 属性
    }
};

console.log(updatedUser); 
// 输出: { name: 'Alice', address: { city: 'New York', zip: '10002' } }

在嵌套结构中,先展开外层对象,再进一步操作内层对象,保证不改变原始对象的深层次结构。

数组嵌套解构:
const arr = [[1, 2], [3, 4], [5, 6]];
const [first, ...rest] = arr;
console.log(first);  // [1, 2]
console.log(rest);   // [[3, 4], [5, 6]]

用解构和展开运算符提取嵌套数组中的一部分,特别适合需要处理矩阵或多维数组时。

重新排序或分组数组

你可以使用展开运算符来灵活重新排序或分组数组元素:

将第一个元素移到最后:
const numbers = [1, 2, 3, 4];
const reordered = [...numbers.slice(1), numbers[0]];
console.log(reordered);  // [2, 3, 4, 1]

使用 slice 和展开运算符轻松调整数组顺序。

简化参数传递(函数柯里化)

当你需要逐步传递参数给函数时,可以利用展开运算符简化函数柯里化的过程:

参数累积:
function curry(func, ...args) {
    return function(...moreArgs) {
        return func(...args, ...moreArgs);  // 累积所有参数并调用
    };
}

function sum(a, b, c) {
    return a + b + c;
}

const curriedSum = curry(sum, 1, 2);
console.log(curriedSum(3));  // 6

通过 ...args 和 ...moreArgs 轻松传递和累积函数的参数,使代码更简洁、灵活。

减少数据嵌套的层次

当你有嵌套的数组或对象结构时,展开运算符可以帮助你快速"展平"它们:

合并嵌套数组:
const nestedArr = [1, [2, 3], [4, 5]];
const flattened = [].concat(...nestedArr);
console.log(flattened);  // [1, 2, 3, 4, 5]

展开嵌套数组中的每一个子数组,并将它们与父数组合并为一个单层数组。虽然不能完全代替 flat,但对于简单嵌套,展开运算符是一种方便的方式。

替代 Object.assign()

通常在对象合并时,会使用 Object.assign(),但展开运算符是更简洁的替代方案:

const obj1 = { a: 1 };
const obj2 = { b: 2 };
const merged = { ...obj1, ...obj2 };
console.log(merged);  // { a: 1, b: 2 }

展开运算符比 Object.assign() 更简洁,且更易读。特别是在多个对象合并时,展开运算符更灵活。

数组插入元素

用展开运算符可以轻松地在数组中间插入新元素,而不必破坏原有数组结构:

在第2个位置插入:
const arr = [1, 2, 4];
const newArr = [...arr.slice(0, 2), 3, ...arr.slice(2)];
console.log(newArr);  // [1, 2, 3, 4]

利用 slice 提取出数组的前后部分,使用展开运算符插入新元素,保持代码简洁。

动态构建对象

当你需要动态构建对象时,可以用展开运算符避免重复的代码逻辑:

function createUser(name, age, city) {
    return {
        ...(name && { name }), 
        ...(age && { age }), 
        ...(city && { city })
    };
}

console.log(createUser('Alice', 25)); 
// { name: 'Alice', age: 25 }

根据传入参数的情况,动态生成对象属性,减少条件判断代码的冗余。

实现不可变更新(Immutable Updates)

在函数式编程或 React 中,数据的不可变性非常重要。展开运算符可以轻松实现不可变更新:

const state = { count: 1, user: { name: 'Alice' } };
const newState = { ...state, count: state.count + 1 };
console.log(newState);  // { count: 2, user: { name: 'Alice' } }

当更新嵌套状态时,确保通过展开运算符复制并更新状态,避免直接修改原始数据。




深拷贝对象和数组(结合递归)

展开运算符只进行浅拷贝,当对象或数组是嵌套结构时,如果你要进行深层次拷贝,必须结合递归。

对象深拷贝:
function deepClone(obj) {
    return Object.keys(obj).reduce((acc, key) => {
        acc[key] = typeof obj[key] === 'object' && obj[key] !== null ? deepClone(obj[key]) : obj[key];
        return acc;
    }, {});
}

const original = { a: 1, b: { c: 2 } };
const cloned = deepClone(original);
cloned.b.c = 3;

console.log(original.b.c);  // 输出: 2 (原对象没有改变)
console.log(cloned.b.c);    // 输出: 3

在嵌套对象或数组场景下,使用递归和展开运算符结合,生成真正的深拷贝,避免对嵌套结构的引用问题。

基于条件的动态参数传递

在某些场景下,你可能需要动态地控制传递给函数的参数数量和顺序,展开运算符可以帮助你灵活处理这一问题:

动态构建函数参数:
function buildQuery(...conditions) {
    return conditions.filter(Boolean).join(' AND ');
}

const includeCondition1 = true;
const includeCondition2 = false;

const query = buildQuery(
    ...(includeCondition1 ? ['name = "Alice"'] : []),
    ...(includeCondition2 ? ['age > 20'] : []),
    'status = "active"'
);

console.log(query);  // 输出: "name = "Alice" AND status = "active""

根据条件动态构建参数数组,利用展开运算符简化逻辑,减少条件判断的冗余。

处理树形或递归数据结构

当你处理树状结构的数据(例如 DOM 树、文件目录树等)时,展开运算符可以帮助你简洁地遍历和合并数据。

遍历和合并树形数据:
const tree = {
    value: 1,
    children: [
        { value: 2, children: [{ value: 4 }, { value: 5 }] },
        { value: 3 }
    ]
};

function flattenTree(node) {
    return [
        node.value,
        ...node.children?.map(flattenTree).flat() ?? []  // 使用展开运算符和递归
    ];
}

console.log(flattenTree(tree));  // 输出: [1, 2, 4, 5, 3]

在处理树形结构的数据时,结合展开运算符和递归,你可以方便地对每个节点进行操作,并构建新的平坦结构。

性能优化:批量操作的分割和合并

当你需要在数据量较大的数组中进行批量操作时,展开运算符可以帮助你快速分割数据,进行局部操作再合并。

批量更新和合并:
function batchUpdate(arr, batchSize) {
    for (let i = 0; i < arr.length; i += batchSize) {
        const batch = arr.slice(i, i + batchSize);
        console.log('Processing batch:', batch);
        // 假设这里进行批量更新操作
    }
}

const data = [...Array(100).keys()];  // [0, 1, 2, ..., 99]
batchUpdate(data, 20);  // 每次处理20个数据块

使用 slice() 和展开运算符,可以将大数据分割成小批量进行处理,适合性能优化场景,如分页加载数据等。

惰性求值(Lazy Evaluation)结合生成器

展开运算符也能与生成器函数一起使用,结合惰性求值(只在需要时生成值),处理大量数据时更为高效。

使用生成器与展开运算符:
function* generateSequence(start, end) {
    for (let i = start; i <= end; i++) {
        yield i;
    }
}

const sequence = [...generateSequence(1, 10)];
console.log(sequence);  // 输出: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

生成器和展开运算符的结合,适合处理大规模的数据流或按需生成的序列。

与 Proxy 结合进行拦截和修改

如果你在使用代理模式(Proxy)来监控对象操作,展开运算符可以帮助你拦截对象中的动态操作,并在应用层进行修改或添加逻辑。

代理对象操作
const target = { name: 'Alice', age: 25 };
const handler = {
    get(obj, prop) {
        console.log(`Getting property ${prop}`);
        return prop in obj ? obj[prop] : 'Unknown';
    },
    set(obj, prop, value) {
        console.log(`Setting property ${prop} to ${value}`);
        obj[prop] = value;
        return true;
    }
};

const proxy = new Proxy(target, handler);
const updated = { ...proxy, city: 'New York' };  // 结合展开运算符进行拦截

console.log(updated);  // 输出代理对象操作日志,同时返回合并后的新对象

将展开运算符与 Proxy 结合,可以在对象操作的过程中实现拦截,提供灵活的调试、数据验证或安全控制。

动态解构和映射复杂数据

在处理 REST API 响应或者一些需要动态映射的场景中,展开运算符能够结合解构赋值,动态解析和组合数据。

动态解构和重组:
const apiResponse = {
    data: { id: 1, name: 'Alice', age: 25 },
    meta: { timestamp: '2023-08-25T12:00:00Z' }
};

const { data: { id, ...restData }, meta: { timestamp } } = apiResponse;
const transformedResponse = { id, timestamp, ...restData };

console.log(transformedResponse);
// 输出: { id: 1, timestamp: '2023-08-25T12:00:00Z', name: 'Alice', age: 25 }

使用解构结合展开运算符,从复杂的数据结构中提取需要的信息并重组,特别适用于 API 数据解析和转换。