JS 解构赋值剩余运算符 (`…`):提取剩余属性/元素到新对象/数组

各位靓仔靓女,大家好!我是你们今天的 JavaScript 解构赋值剩余运算符 (...) 讲师——代码界的段子手。 今天咱们不搞那些虚头巴脑的,直接上干货,用最接地气的语言,把这个 ... 运算符给扒个精光!

啥是解构赋值?为啥要用剩余运算符?

首先,得知道解构赋值是干啥的。简单来说,它就是一种从对象或数组中提取数据,并赋给变量的语法。这比传统的方式更简洁,更优雅,也更装逼。

举个例子,假设我们有个对象:

const person = {
  name: '张三',
  age: 30,
  city: '北京',
  occupation: '码农',
  hobby: '写Bug'
};

以前你想拿到 nameage,可能得这么写:

const name = person.name;
const age = person.age;

现在有了结构赋值,你可以这样:

const { name, age } = person;
console.log(name, age); // 输出: 张三 30

看到了没?一行代码搞定!这就是解构赋值的魅力!

但是问题来了,如果我只想提取 nameage,剩下的属性咋办?总不能一个个手动赋值吧?这时候,剩余运算符 ... 就该闪亮登场了!

剩余运算符:打包带走剩下的

剩余运算符,顾名思义,就是把剩下的东西打包带走。它在解构赋值中,可以把对象或数组中,没有被提取的属性或元素,收集到一个新的对象或数组中。

对象解构中的剩余运算符

还是上面的 person 对象,这次我们用剩余运算符把除了 nameage 之外的属性都收集起来:

const { name, age, ...rest } = person;
console.log(name, age); // 输出: 张三 30
console.log(rest); // 输出: { city: '北京', occupation: '码农', hobby: '写Bug' }

看,rest 变量就包含了 person 对象中除了 nameage 之外的所有属性。是不是很方便?

注意:

  • 剩余运算符只能放在解构赋值的最后
  • 剩余运算符收集到的属性,会形成一个新的对象,不是原始对象的引用。 这意味着修改 rest 对象不会影响 person 对象,反之亦然。

数组解构中的剩余运算符

数组解构和对象解构类似,只不过是操作的对象变成了数组。

假设我们有这样一个数组:

const numbers = [1, 2, 3, 4, 5];

我们可以用剩余运算符把除了前两个元素之外的元素收集起来:

const [first, second, ...remaining] = numbers;
console.log(first, second); // 输出: 1 2
console.log(remaining); // 输出: [3, 4, 5]

同样,remaining 变量包含了 numbers 数组中除了前两个元素之外的所有元素,组成了一个新的数组。

注意:

  • 和对象解构一样,剩余运算符只能放在数组解构的最后
  • 剩余运算符收集到的元素,会形成一个新的数组,不是原始数组的引用

剩余运算符的常见应用场景

剩余运算符在实际开发中有很多用处,下面列举几个常见的应用场景:

1. 提取部分属性,传递剩余属性给子组件

在 React 或 Vue 等前端框架中,经常需要把组件的 props 传递给子组件。有时候我们只需要提取部分 props,把剩下的 props 传递给子组件。

// React 组件示例
function ParentComponent(props) {
  const { name, age, ...otherProps } = props;

  return (
    <div>
      <h1>{name}</h1>
      <p>Age: {age}</p>
      <ChildComponent {...otherProps} /> {/* 把剩余的 props 传递给子组件 */}
    </div>
  );
}

function ChildComponent(props) {
  return (
    <div>
      {/* 子组件可以使用父组件传递过来的 props */}
      <p>City: {props.city}</p>
      <p>Occupation: {props.occupation}</p>
    </div>
  );
}

在这个例子中,ParentComponent 提取了 nameage 属性,然后用剩余运算符把剩下的属性收集到 otherProps 对象中,最后通过 ...otherProps 的方式传递给 ChildComponent。 这样,ChildComponent 就可以使用父组件传递过来的 cityoccupation 等属性了。

2. 函数参数的处理

有时候我们需要定义一个函数,接受不定数量的参数。 剩余参数可以很方便地实现这个功能。

function sum(first, second, ...numbers) {
  let result = first + second;
  for (let number of numbers) {
    result += number;
  }
  return result;
}

console.log(sum(1, 2, 3, 4, 5)); // 输出: 15
console.log(sum(1, 2)); // 输出: 3

在这个例子中,sum 函数接受至少两个参数 firstsecond,剩下的参数都收集到 numbers 数组中。 这样,我们就可以传递任意数量的参数给 sum 函数了。

注意:

  • 剩余参数只能是函数的最后一个参数。
  • 剩余参数收集到的参数,会形成一个数组。

3. 过滤对象中的指定属性

有时候我们需要从一个对象中过滤掉一些指定的属性,只保留其他的属性。

function filterObject(obj, keysToOmit) {
  const { [keysToOmit.join(',')]: omitted, ...rest } = obj; // 这里利用了计算属性名
  return rest;
}

const myObject = { a: 1, b: 2, c: 3, d: 4 };
const filteredObject = filterObject(myObject, ['b', 'd']);
console.log(filteredObject); // 输出: { a: 1, c: 3 }

这个例子中,filterObject 函数接受一个对象 obj 和一个需要过滤的属性名数组 keysToOmit。 利用计算属性名和剩余运算符,我们可以很方便地过滤掉指定的属性。

解释一下这行代码 const { [keysToOmit.join(',')]: omitted, ...rest } = obj;

  • keysToOmit.join(','): 将数组 keysToOmit 中的元素用逗号连接成一个字符串。 例如,['b', 'd'].join(',') 的结果是 "b,d"
  • [keysToOmit.join(',')]: 这里使用了计算属性名。 这意味着我们不是直接使用字符串 "b,d" 作为属性名,而是计算出一个属性名。 但是,由于JavaScript对象的键只能是字符串或Symbol类型,直接这样写并不能达到目的,因为不会根据逗号分隔来删除对应的属性。实际上,它会尝试删除名为 "b,d" 的属性(如果存在的话)。 这种写法本身并不能直接删除多个属性,我们需要结合其他方式来实现。
  • omitted: 这是解构赋值的目标变量名。 但是在这个例子中,omitted 变量实际上并没有被使用,因为我们的目的是过滤掉指定的属性,而不是提取它们。
  • ...rest: 剩余运算符,将 obj 对象中除了 keysToOmit.join(',') 对应的属性(实际上是尝试删除 "b,d" 属性)之外的所有属性,收集到一个新的对象 rest 中。

更合适的实现方式

上面的代码并不能正确删除指定的多个属性,下面提供一个更合适的实现方式:

function filterObject(obj, keysToOmit) {
  const newObj = { ...obj }; // 创建原始对象的副本,防止修改原始对象
  for (const key of keysToOmit) {
    delete newObj[key]; // 删除指定的属性
  }
  return newObj;
}

const myObject = { a: 1, b: 2, c: 3, d: 4 };
const filteredObject = filterObject(myObject, ['b', 'd']);
console.log(filteredObject); // 输出: { a: 1, c: 3 }
console.log(myObject); // 输出: { a: 1, b: 2, c: 3, d: 4 } (原始对象未被修改)

这个版本使用 delete 运算符来删除对象中的属性,并且首先创建了原始对象的副本,以避免修改原始对象。

4. 克隆对象

虽然剩余运算符不能完全深度克隆对象(对于嵌套对象,它仍然是浅拷贝),但在某些情况下,它可以作为一种快速的克隆对象的方式。

const originalObject = { a: 1, b: 2, c: { d: 3 } };
const clonedObject = { ...originalObject };

console.log(clonedObject); // 输出: { a: 1, b: 2, c: { d: 3 } }

clonedObject.a = 10;
console.log(originalObject.a); // 输出: 1 (originalObject 未被修改)

clonedObject.c.d = 30; // 修改嵌套对象
console.log(originalObject.c.d); // 输出: 30 (originalObject 也被修改了,因为是浅拷贝)

可以看到,修改 clonedObjecta 属性不会影响 originalObject,但是修改 clonedObjectc.d 属性会影响 originalObject,因为 c 属性是一个对象,剩余运算符只是浅拷贝了 c 属性的引用。

5. 合并对象

剩余运算符可以用于合并多个对象。

const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, d: 4 };
const mergedObject = { ...obj1, ...obj2 };

console.log(mergedObject); // 输出: { a: 1, b: 2, c: 3, d: 4 }

如果多个对象中有相同的属性,后面的对象会覆盖前面的对象。

const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };
const mergedObject = { ...obj1, ...obj2 };

console.log(mergedObject); // 输出: { a: 1, b: 3, c: 4 } (obj2 的 b 属性覆盖了 obj1 的 b 属性)

总结

总而言之,剩余运算符 ... 是 JavaScript 解构赋值中一个非常强大的工具,它可以帮助我们更方便地提取对象或数组中的数据,并把剩下的数据打包带走。 掌握了剩余运算符,可以写出更简洁,更优雅,更装逼的代码!

为了方便大家理解和查阅,下面用一个表格总结一下剩余运算符的特性:

特性 对象解构 数组解构 函数参数
作用 收集对象中未被提取的属性 收集数组中未被提取的元素 收集函数中剩余的参数
位置 只能放在解构赋值的最后 只能放在解构赋值的最后 只能是函数的最后一个参数
类型 生成一个新的对象 生成一个新的数组 生成一个数组
引用 不是原始对象的引用 (浅拷贝) 不是原始数组的引用
应用场景 提取部分属性,传递剩余属性给子组件、过滤属性等 提取部分元素、处理不定数量的元素等 处理不定数量的参数等

希望今天的讲座对大家有所帮助。 记住,多敲代码,多练习,才能真正掌握这些技巧! 祝大家早日成为代码界的弄潮儿! 咱们下期再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注