JS 剩余参数 (`…`):将不定数量的参数收集为数组

嘿,各位代码界的弄潮儿们,今天咱们来聊聊 JavaScript 里一个既实用又好玩的东西:剩余参数 (...)。别害怕,这玩意儿一点都不神秘,用好了能让你的代码更简洁、更灵活,还能在面试的时候小小地秀一把操作!

开场白:参数的烦恼

想象一下,你正在写一个函数,用来计算一堆数字的总和。如果数字的数量是固定的,那没问题,直接在函数定义里写死参数个数:

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

console.log(sum(1, 2, 3)); // 输出 6

但如果数字的数量是不确定的呢?你可能会想到 arguments 对象,这是 JavaScript 早期处理不定数量参数的老办法。

function sum() {
  let total = 0;
  for (let i = 0; i < arguments.length; i++) {
    total += arguments[i];
  }
  return total;
}

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

arguments 对象确实能解决问题,但它有个缺点:它不是一个真正的数组。它是一个类数组对象,这意味着你不能直接使用数组的方法,比如 mapfilterreduce 等等。而且,它的行为在严格模式下还有些怪异。

这时候,剩余参数就闪亮登场了!

主角登场:剩余参数 (...)

剩余参数允许你将不定数量的参数收集到一个数组中。它的语法很简单,就是在函数定义中的最后一个参数前面加上三个点 (...)。

function sum(a, b, ...rest) {
  console.log("第一个参数:", a);
  console.log("第二个参数:", b);
  console.log("剩余参数:", rest);
  let total = a + b;
  for (let num of rest) {
    total += num;
  }
  return total;
}

console.log(sum(1, 2, 3, 4, 5)); // 输出:第一个参数: 1, 第二个参数: 2, 剩余参数: [3, 4, 5], 15

在这个例子中,ab 分别接收了前两个参数,而 ...rest 则将剩余的参数收集到了一个名为 rest 的数组中。现在,你可以像操作普通数组一样操作 rest 了!

剩余参数的优点

  • 它是一个真正的数组: 你可以随意使用数组的方法。
  • 更清晰的参数定义: 明确指定了哪些参数是必须的,哪些是可选的。
  • 可读性更好: 代码意图更明确。
  • 与解构赋值配合使用: 可以更方便地提取参数。

剩余参数的规则

  • 只能作为最后一个参数: 剩余参数必须是函数定义中的最后一个参数。否则,JavaScript 引擎就不知道哪些参数应该被收集到剩余参数中了。
    // 错误示例
    // function sum(...rest, a) { // SyntaxError: Rest parameter must be last formal parameter
    //   return rest.reduce((acc, val) => acc + val, 0) + a;
    // }
  • 每个函数只能有一个剩余参数: 不能定义多个剩余参数。
    // 错误示例
    // function sum(...rest1, ...rest2) { // SyntaxError: Rest parameter must be last formal parameter
    //   // ...
    // }
  • 不能在 setter 函数中使用: setter 函数只能接收一个参数。
    // 错误示例
    // const obj = {
    //   set values(...vals) { // SyntaxError: Setter function argument must not be a rest parameter
    //     this.data = vals;
    //   }
    // };

剩余参数的用例

  1. 简化函数参数处理:

    假设你需要一个函数,接收一个字符串和一个分隔符,然后将字符串分割成数组。

    function splitString(str, ...separators) {
      let result = [str];
      for (const separator of separators) {
        let temp = [];
        for (const item of result) {
          temp.push(...item.split(separator));
        }
        result = temp;
      }
      return result;
    }
    
    console.log(splitString("hello world. how are you?", " ", "."));
    // 输出: [ 'hello', 'world', '', 'how', 'are', 'you?' ]
  2. 与解构赋值结合:

    从参数列表中提取特定的参数。

    function processData(name, age, ...details) {
      const [city, country] = details;
      console.log("Name:", name);
      console.log("Age:", age);
      console.log("City:", city);
      console.log("Country:", country);
    }
    
    processData("Alice", 30, "New York", "USA", "Engineer");
    // 输出:
    // Name: Alice
    // Age: 30
    // City: New York
    // Country: USA
  3. 实现高阶函数:

    编写更灵活的函数组合工具。

    function compose(...fns) {
      return function(x) {
        return fns.reduceRight((acc, fn) => fn(acc), x);
      };
    }
    
    function addOne(x) {
      return x + 1;
    }
    
    function multiplyByTwo(x) {
      return x * 2;
    }
    
    const composedFunction = compose(multiplyByTwo, addOne);
    console.log(composedFunction(3)); // 输出: 8 (先加1,再乘以2)
  4. 处理回调函数参数

有时候,我们需要传递回调函数,并且回调函数需要接收一些参数。剩余参数可以方便地处理这些参数。

function doSomething(callback, ...args) {
  console.log("Doing something...");
  callback(...args);
}

function myCallback(arg1, arg2, arg3) {
  console.log("Callback called with:", arg1, arg2, arg3);
}

doSomething(myCallback, "hello", 123, true); // 输出:Doing something..., Callback called with: hello 123 true

剩余参数 vs. arguments 对象

特性 剩余参数 (...) arguments 对象
数据类型 数组 类数组对象
数组方法 支持 不支持
参数定义 明确 隐式
严格模式行为 一致 怪异
可读性 更高 较低

总的来说,剩余参数是 arguments 对象的一个更现代、更方便的替代品。在 ES6 及以后的版本中,建议使用剩余参数来处理不定数量的参数。

实战演练:一个更复杂的例子

让我们来写一个函数,它可以接收任意数量的字符串,并将它们连接成一个句子,并在句子末尾添加一个可选的标点符号。

function createSentence(...words) {
  let punctuation = "."; // 默认标点符号

  // 检查最后一个参数是否是标点符号
  if (typeof words[words.length - 1] === "string" && [".", "!", "?"].includes(words[words.length - 1])) {
    punctuation = words.pop(); // 移除并获取最后一个参数
  }

  const sentence = words.join(" ") + punctuation;
  return sentence;
}

console.log(createSentence("This", "is", "a", "sentence")); // 输出: This is a sentence.
console.log(createSentence("This", "is", "an", "exclamatory", "sentence", "!")); // 输出: This is an exclamatory sentence!
console.log(createSentence("Is", "this", "a", "question", "?")); // 输出: Is this a question?

在这个例子中,我们首先假设句子的默认标点符号是句号 (.)。然后,我们检查最后一个参数是否是标点符号。如果是,我们就将它从 words 数组中移除,并更新 punctuation 变量。最后,我们将剩余的单词连接成一个句子,并在末尾添加标点符号。

总结:剩余参数的魅力

剩余参数是 JavaScript 中一个强大的工具,它可以让你编写更灵活、更可读的代码。它不仅简化了函数参数的处理,还提高了代码的可维护性。掌握了剩余参数,你就掌握了一种处理不定数量参数的优雅方式。

记住,编程的乐趣在于不断学习和探索。多练习,多尝试,你就能发现剩余参数的更多妙用!希望今天的讲解对你有所帮助,祝你编程愉快!

发表回复

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