阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > rest参数与arguments对象的区别

rest参数与arguments对象的区别

作者:陈川 阅读数:12794人阅读 分类: JavaScript

rest参数的基本概念与语法

ECMAScript 6引入的rest参数为函数处理可变数量参数提供了更优雅的方式。通过在函数参数前添加三个点(...)前缀,可以将剩余的参数收集到一个数组中。这种语法不仅简洁,而且解决了传统arguments对象的一些痛点。

function sum(...numbers) {
  return numbers.reduce((acc, curr) => acc + curr, 0);
}

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

rest参数必须是函数参数列表中的最后一个参数,否则会抛出语法错误。与arguments对象不同,rest参数是真正的Array实例,可以直接使用数组的所有方法。

// 错误的用法 - rest参数不在最后
function invalidExample(a, ...b, c) {
  // SyntaxError: Rest parameter must be last formal parameter
}

arguments对象的特点与局限

在ES6之前,JavaScript函数内部通过arguments对象访问所有传入的参数。这个类数组对象包含函数被调用时传递的所有参数,不论是否在形参列表中定义。

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

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

arguments对象有几个显著特点:

  1. 它是类数组对象,不是真正的数组,不能直接使用数组方法
  2. 包含所有传入参数,包括超出形参数量的部分
  3. 与形参存在动态绑定关系(在非严格模式下)
  4. 不能用于箭头函数

类型差异与可用方法

rest参数和arguments对象最根本的区别在于它们的类型。rest参数是真正的Array实例,而arguments只是类数组对象。

function typeCheck(...rest) {
  console.log(Array.isArray(rest)); // true
  console.log(Array.isArray(arguments)); // false
}

typeCheck(1, 2, 3);

这种类型差异导致它们在方法使用上有很大不同:

function example(a, b) {
  // arguments需要转换才能使用数组方法
  const argsArray = Array.prototype.slice.call(arguments);
  console.log(argsArray.map(x => x * 2)); // [2, 4, 6]
  
  // rest参数直接可用
  function inner(...rest) {
    console.log(rest.map(x => x * 2)); // [2, 4, 6]
  }
  inner(a, b, 3);
}

example(1, 2);

箭头函数中的表现差异

箭头函数没有自己的arguments对象,但可以使用rest参数:

const arrowWithRest = (...args) => {
  console.log(args); // [1, 2, 3]
};

arrowWithRest(1, 2, 3);

const arrowWithArguments = () => {
  console.log(arguments); // ReferenceError: arguments is not defined
};

// arrowWithArguments(1, 2, 3); // 这会抛出错误

在箭头函数中访问arguments会引用外层函数的arguments对象,如果不存在则报错。这种行为使得rest参数成为箭头函数中处理可变参数的唯一选择。

严格模式下的行为变化

严格模式对arguments对象的行为有显著影响,但对rest参数没有影响:

function strictExample(a, b) {
  'use strict';
  a = 42;
  console.log(a, arguments[0]); // 42, 1 - 无关联
  console.log(arguments.callee); // TypeError
}

function nonStrictExample(a, b) {
  a = 42;
  console.log(a, arguments[0]); // 42, 42 - 动态关联
}

strictExample(1, 2);
nonStrictExample(1, 2);

rest参数在任何模式下都保持相同行为,不受严格模式影响:

function strictWithRest(...rest) {
  'use strict';
  rest[0] = 42;
  console.log(rest); // [42, 2]
}

strictWithRest(1, 2);

性能考量与优化

从性能角度考虑,rest参数通常比转换arguments对象更高效。现代JavaScript引擎对rest参数有专门优化,而arguments到数组的转换需要额外操作。

// 较慢的方式
function slowSum() {
  const args = Array.prototype.slice.call(arguments);
  return args.reduce((a, b) => a + b, 0);
}

// 较快的方式
function fastSum(...args) {
  return args.reduce((a, b) => a + b, 0);
}

在热代码路径(频繁执行的代码)中,这种差异可能变得明显。不过对于大多数应用场景,这种性能差异可以忽略不计。

实际应用场景对比

rest参数在多种现代JavaScript模式中表现出色:

  1. 函数组合
function compose(...fns) {
  return fns.reduce((f, g) => (...args) => f(g(...args)));
}

const add5 = x => x + 5;
const multiply3 = x => x * 3;
const transform = compose(add5, multiply3);
console.log(transform(2)); // 11
  1. 参数转发
class EventEmitter {
  constructor() {
    this.listeners = [];
  }
  
  addListener(...args) {
    this.listeners.push(args);
  }
  
  emit(...args) {
    this.listeners.forEach(listener => listener(...args));
  }
}

arguments对象在维护旧代码或需要访问所有参数(包括在形参列表中定义的)时仍有价值:

function legacyLogger(prefix) {
  // 需要包含prefix在内的所有参数
  const messages = Array.prototype.slice.call(arguments, 1);
  console.log(`[${prefix}]`, messages.join(' '));
}

legacyLogger('DEBUG', 'Something happened', 'at', new Date());

默认参数交互

rest参数与默认参数结合使用时表现直观:

function withDefaults(a = 1, b = 2, ...rest) {
  console.log(a, b, rest);
}

withDefaults(); // 1, 2, []
withDefaults(undefined, null, 3, 4); // 1, null, [3, 4]

arguments对象在默认参数存在时的行为可能令人困惑:

function confusingExample(a = 1) {
  console.log(a, arguments[0]);
}

confusingExample(); // 1, undefined
confusingExample(undefined); // 1, undefined
confusingExample(null); // null, null

解构赋值的结合使用

rest参数可以与解构赋值完美配合:

const [first, ...rest] = [1, 2, 3, 4];
console.log(first, rest); // 1, [2, 3, 4]

const {a, ...others} = {a: 1, b: 2, c: 3};
console.log(a, others); // 1, {b: 2, c: 3}

arguments对象无法直接用于解构赋值,需要先转换为数组:

function destructureArguments() {
  const [first, ...rest] = Array.from(arguments);
  console.log(first, rest);
}

destructureArguments(1, 2, 3); // 1, [2, 3]

TypeScript中的类型支持

在TypeScript中,rest参数有完整的类型支持,而arguments对象缺乏类型安全性:

function typedRest(...args: number[]): number {
  return args.reduce((a, b) => a + b, 0);
}

function untypedArguments(): number {
  const args: number[] = Array.prototype.slice.call(arguments);
  return args.reduce((a, b) => a + b, 0);
}

TypeScript能对rest参数进行类型检查,但对arguments对象无能为力,后者需要显式类型断言。

浏览器兼容性与转译

rest参数需要ES6支持,在不兼容的环境中需要通过Babel等工具转译:

原始代码:

function example(...args) {
  console.log(args);
}

转译后的代码大致如下:

function example() {
  var args = Array.prototype.slice.call(arguments);
  console.log(args);
}

arguments对象在所有JavaScript环境中都可用,包括最古老的浏览器。在需要广泛兼容性的场景中,arguments可能仍是必要选择。

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌