arguments 객체 - 레거시의 유산, 그리고 더 나은 대안

JavaScript로 함수를 작성하다가 이런 코드를 본 적 있나요?

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

sum(1, 2, 3); // 6
sum(1, 2, 3, 4, 5); // 15

“어? arguments가 뭐지? 매개변수를 선언하지 않았는데 어떻게 인자들에 접근할 수 있지?” 처음 이 코드를 봤을 때의 당혹감이 생생합니다.

arguments는 JavaScript의 모든 함수(화살표 함수 제외)에서 자동으로 사용할 수 있는 유사 배열 객체입니다. 하지만 ES6에서 rest 파라미터(...args)가 도입되면서, arguments는 이제 레거시 기능이 되었습니다.

이 문서에서는 arguments 객체가 무엇인지, 왜 더 이상 사용하지 말아야 하는지, 그리고 어떤 현대적인 대안을 사용해야 하는지를 실전 경험을 바탕으로 자세히 설명하겠습니다.

목차

왜 arguments 객체를 이해해야 할까요?

1. 레거시 코드를 읽어야 합니다

많은 오래된 JavaScript 코드베이스에서 arguments를 사용합니다.

// jQuery 내부 코드 (간소화)
function extend() {
  var options, name, src, copy, target = arguments[0] || {};
  var i = 1;
  var length = arguments.length;

  for (; i < length; i++) {
    if ((options = arguments[i]) != null) {
      for (name in options) {
        // ...
      }
    }
  }

  return target;
}

// 실제 사용
extend({}, obj1, obj2, obj3);

레거시 라이브러리나 오래된 프로젝트를 유지보수할 때, arguments를 이해하지 못하면 코드를 읽을 수 없습니다.

2. 기술 면접에서 자주 나옵니다

// 면접 문제: 이 코드의 출력은?
function test(a, b) {
  arguments[0] = 10;
  console.log(a); // ?

  a = 20;
  console.log(arguments[0]); // ?
}

test(1, 2);

arguments와 매개변수의 관계를 이해하는 것은 JavaScript 동작 원리를 깊이 이해하는 데 도움이 됩니다.

3. 왜 사용하지 말아야 하는지 알아야 합니다

팀에서 “왜 arguments 대신 rest 파라미터를 써야 하나요?”라고 물어볼 때, 명확한 이유를 설명할 수 있어야 합니다.

// ❌ 레거시: arguments 사용
function oldWay() {
  return Array.from(arguments).reduce((sum, n) => sum + n, 0);
}

// ✅ 현대적: rest 파라미터
function modernWay(...numbers) {
  return numbers.reduce((sum, n) => sum + n, 0);
}

먼저, arguments가 무엇인지 알아봅시다

기본 개념

arguments함수에 전달된 모든 인자를 담고 있는 유사 배열 객체입니다.

function showArguments(a, b, c) {
  console.log('arguments:', arguments);
  console.log('typeof arguments:', typeof arguments);
  console.log('Array.isArray(arguments):', Array.isArray(arguments));
  console.log('arguments.length:', arguments.length);
}

showArguments(1, 2, 3, 4, 5);

// 출력:
// arguments: Arguments(5) [1, 2, 3, 4, 5]
// typeof arguments: object
// Array.isArray(arguments): false
// arguments.length: 5

핵심 특징:

  • 모든 함수에서 자동으로 사용 가능 (화살표 함수 제외)
  • 유사 배열 객체 (배열처럼 보이지만 배열이 아님)
  • 매개변수보다 많은 인자를 받을 수 있음
  • length 속성으로 인자 개수 확인 가능

왜 화살표 함수에는 arguments가 없을까?

화살표 함수는 렉시컬 스코프를 따르도록 설계되었습니다. this, super, new.target처럼 arguments도 자신만의 바인딩을 갖지 않고, 외부 스코프의 것을 참조합니다.

// 일반 함수: 자신의 arguments를 가짐
function regularFunc() {
  console.log(arguments); // Arguments(3) [1, 2, 3]
}

regularFunc(1, 2, 3);

// ❌ 화살표 함수: arguments가 없음
const arrowFunc = () => {
  console.log(arguments); // ReferenceError: arguments is not defined
};

arrowFunc(1, 2, 3); // 에러!

// ⚠️ 외부 함수의 arguments를 참조함
function outer() {
  console.log('outer arguments:', arguments); // [1, 2, 3]

  const inner = () => {
    // inner의 arguments가 아니라 outer의 arguments!
    console.log('inner arguments:', arguments); // [1, 2, 3]
  };

  inner(4, 5, 6); // 4, 5, 6은 무시됨!
}

outer(1, 2, 3);

설계 이유:

  1. this와의 일관성: 화살표 함수는 렉시컬 this를 사용하므로, arguments도 같은 방식으로 동작
  2. 간결함 우선: 복잡한 가변 인자가 필요하면 일반 함수나 rest 파라미터 사용 권장
  3. 생성자 불가: 화살표 함수는 new로 호출할 수 없으므로 arguments.callee 같은 기능 불필요
// ❌ 화살표 함수에서 가변 인자 (에러)
const sum = () => {
  return Array.from(arguments).reduce((a, b) => a + b);
}; // ReferenceError!

// ✅ rest 파라미터 사용 (권장)
const sum = (...numbers) => {
  return numbers.reduce((a, b) => a + b, 0);
};

sum(1, 2, 3); // 6

결론: 화살표 함수에서는 arguments 대신 rest 파라미터(...args)를 사용하는 것이 현대적이고 명확한 방법입니다.

유사 배열 객체란?

arguments는 배열이 아니므로 배열 메소드를 사용할 수 없습니다.

function test() {
  console.log(arguments[0]); // ✅ 인덱스 접근 가능
  console.log(arguments.length); // ✅ length 속성 있음

  // ❌ 배열 메소드 없음
  // arguments.map(x => x * 2); // TypeError!
  // arguments.filter(x => x > 0); // TypeError!
  // arguments.reduce((a, b) => a + b); // TypeError!
}

test(1, 2, 3);

배열로 변환해야 메소드 사용 가능:

function sum() {
  // ES5 방식
  const args = Array.prototype.slice.call(arguments);
  return args.reduce((sum, n) => sum + n, 0);
}

function sum2() {
  // ES6 방식
  const args = Array.from(arguments);
  return args.reduce((sum, n) => sum + n, 0);
}

function sum3() {
  // ES6 스프레드
  const args = [...arguments];
  return args.reduce((sum, n) => sum + n, 0);
}

arguments의 구조

function inspect(a, b, c) {
  console.log('매개변수:', { a, b, c });
  console.log('arguments:', arguments);

  // arguments 객체 구조
  console.log('arguments[0]:', arguments[0]);
  console.log('arguments.length:', arguments.length);
  console.log('arguments.callee:', arguments.callee); // 함수 자신 (비권장)
}

inspect(1, 2, 3, 4, 5);

// 출력:
// 매개변수: { a: 1, b: 2, c: 3 }
// arguments: Arguments(5) [1, 2, 3, 4, 5]
// arguments[0]: 1
// arguments.length: 5
// arguments.callee: [Function: inspect]

시각화:

arguments 객체 구조:
{
  0: 1,
  1: 2,
  2: 3,
  3: 4,
  4: 5,
  length: 5,
  callee: [Function: inspect],
  [Symbol(Symbol.iterator)]: [Function]
}

매개변수보다 많은 인자 받기

이것이 arguments의 주요 사용 사례였습니다.

// 가변 인자를 받는 함수
function max() {
  if (arguments.length === 0) {
    return -Infinity;
  }

  let maximum = arguments[0];
  for (let i = 1; i < arguments.length; i++) {
    if (arguments[i] > maximum) {
      maximum = arguments[i];
    }
  }

  return maximum;
}

console.log(max(1, 3, 2)); // 3
console.log(max(5, 2, 8, 1, 9, 3)); // 9
console.log(max(-1, -5, -3)); // -1

arguments의 근본적인 문제점

문제 1: 유사 배열이라 불편합니다

배열 메소드를 사용하려면 항상 변환이 필요합니다.

// ❌ arguments - 매번 변환 필요
function sum() {
  const numbers = Array.from(arguments);
  return numbers.reduce((sum, n) => sum + n, 0);
}

// ✅ rest 파라미터 - 이미 배열
function sum(...numbers) {
  return numbers.reduce((sum, n) => sum + n, 0);
}

실제 프로젝트에서 경험한 불편함:

// 여러 조건으로 필터링하고 변환하는 유틸리티 함수
function processNumbers() {
  // arguments 사용 시 - 장황함
  const nums = Array.from(arguments);
  return nums
    .filter(n => n > 0)
    .map(n => n * 2)
    .reduce((sum, n) => sum + n, 0);
}

// rest 파라미터 사용 시 - 간결함
function processNumbers(...nums) {
  return nums
    .filter(n => n > 0)
    .map(n => n * 2)
    .reduce((sum, n) => sum + n, 0);
}

문제 2: 화살표 함수에서 사용할 수 없습니다

// ❌ 화살표 함수에는 arguments가 없음
const sum = () => {
  return Array.from(arguments).reduce((a, b) => a + b, 0);
};

sum(1, 2, 3); // ReferenceError: arguments is not defined

// 외부 함수의 arguments를 참조함 (혼란스러움!)
function outer() {
  const inner = () => {
    console.log(arguments); // outer의 arguments
  };
  inner();
}

outer(1, 2, 3); // Arguments(3) [1, 2, 3]

이는 코드의 일관성을 해칩니다. 일반 함수와 화살표 함수를 혼용할 때 혼란을 야기합니다.

문제 3: 코드의 의도가 불명확합니다

// ❌ arguments - 함수 시그니처만 봐서는 알 수 없음
function process() {
  // 이 함수가 인자를 받는지, 몇 개를 받는지 알 수 없음
  console.log(arguments);
}

// ✅ rest 파라미터 - 명확함
function process(...items) {
  // 가변 인자를 받는다는 것이 시그니처에 명시됨
  console.log(items);
}

실제 코드 리뷰에서의 경험:

// 이 함수를 처음 보는 사람은 어떻게 호출해야 할까?
function createUser() {
  // arguments를 사용하는 100줄의 코드...
}

// 호출 방법을 알려면 함수 내부를 읽어야 함
// createUser(name)? createUser(name, email)?
// createUser(name, email, age)?

문제 4: 매개변수와 이상하게 연결됩니다

strict mode가 아닐 때, arguments와 매개변수는 서로 연결되어 있습니다.

// ❌ non-strict mode - 혼란스러운 동작
function test(a, b) {
  console.log('초기:', a, arguments[0]); // 1, 1

  a = 10;
  console.log('a 변경 후:', a, arguments[0]); // 10, 10 (연결됨!)

  arguments[0] = 20;
  console.log('arguments[0] 변경 후:', a, arguments[0]); // 20, 20 (연결됨!)
}

test(1, 2);

// ✅ strict mode - 독립적
'use strict';
function test(a, b) {
  console.log('초기:', a, arguments[0]); // 1, 1

  a = 10;
  console.log('a 변경 후:', a, arguments[0]); // 10, 1 (독립적)

  arguments[0] = 20;
  console.log('arguments[0] 변경 후:', a, arguments[0]); // 10, 20 (독립적)
}

test(1, 2);

이런 혼란스러운 동작은 예측 불가능한 버그를 만들 수 있습니다.

문제 5: 성능 최적화를 방해합니다

JavaScript 엔진은 arguments를 사용하는 함수를 최적화하기 어렵습니다.

// arguments 사용 시 최적화가 어려움
function sum() {
  let total = 0;
  for (let i = 0; i < arguments.length; i++) {
    total += arguments[i];
  }
  return total;
}

// rest 파라미터 사용 시 최적화 가능
function sum(...numbers) {
  let total = 0;
  for (const num of numbers) {
    total += num;
  }
  return total;
}

V8 엔진 개발자들은 arguments 사용을 피하라고 권장합니다.

현대적인 대안: rest 파라미터

ES6에서 도입된 rest 파라미터(...)는 arguments의 모든 문제를 해결합니다.

기본 사용법

// rest 파라미터는 진짜 배열입니다
function sum(...numbers) {
  console.log(Array.isArray(numbers)); // true
  return numbers.reduce((sum, n) => sum + n, 0);
}

sum(1, 2, 3); // 6
sum(1, 2, 3, 4, 5); // 15

장점:

  • ✅ 진짜 배열 (모든 배열 메소드 사용 가능)
  • ✅ 화살표 함수에서도 사용 가능
  • ✅ 함수 시그니처에 명시됨 (의도가 명확)
  • ✅ strict mode 여부와 무관하게 일관적
  • ✅ 최적화가 쉬움

일반 매개변수와 함께 사용

// 첫 번째는 필수, 나머지는 가변
function greet(greeting, ...names) {
  return `${greeting}, ${names.join(' and ')}!`;
}

greet('Hello', 'Alice'); // "Hello, Alice!"
greet('Hello', 'Alice', 'Bob'); // "Hello, Alice and Bob!"
greet('Hello', 'Alice', 'Bob', 'Charlie'); // "Hello, Alice and Bob and Charlie!"

주의: rest 파라미터는 항상 마지막 매개변수여야 합니다.

// ✅ 올바름
function fn(a, b, ...rest) { }

// ❌ 에러: rest 파라미터 뒤에 다른 매개변수 불가
function fn(...rest, last) { } // SyntaxError!

arguments vs rest 파라미터 비교

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

// ✅ rest 파라미터
function newSum(...numbers) {
  return numbers.reduce((sum, n) => sum + n, 0);
}

// 훨씬 간결하고 명확함!

실전 예제: 로거 함수

// ❌ arguments 버전
function log(level) {
  const messages = Array.prototype.slice.call(arguments, 1);
  console.log(`[${level}]`, messages.join(' '));
}

// ✅ rest 파라미터 버전
function log(level, ...messages) {
  console.log(`[${level}]`, messages.join(' '));
}

log('INFO', 'User', 'logged', 'in'); // [INFO] User logged in
log('ERROR', 'Failed', 'to', 'connect'); // [ERROR] Failed to connect

화살표 함수에서도 사용 가능

// ❌ arguments는 화살표 함수에서 불가능
const sum1 = () => {
  return Array.from(arguments).reduce((a, b) => a + b);
}; // ReferenceError!

// ✅ rest 파라미터는 가능
const sum2 = (...numbers) => {
  return numbers.reduce((a, b) => a + b, 0);
};

// 🎯 더 간결하게
const sum3 = (...numbers) => numbers.reduce((a, b) => a + b, 0);

sum3(1, 2, 3); // 6

구조 분해와 함께 사용

// 첫 번째와 나머지 분리
function process(first, ...rest) {
  console.log('First:', first);
  console.log('Rest:', rest);
}

process(1, 2, 3, 4, 5);
// First: 1
// Rest: [2, 3, 4, 5]

// 실전 예제: 첫 번째 요소 특별 처리
function createList(title, ...items) {
  return {
    title,
    items,
    count: items.length
  };
}

const groceries = createList('Groceries', 'Milk', 'Eggs', 'Bread');
// { title: 'Groceries', items: ['Milk', 'Eggs', 'Bread'], count: 3 }

arguments를 rest 파라미터로 마이그레이션하기

레거시 코드를 현대화하는 패턴들을 살펴봅시다.

패턴 1: 단순 변환

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

// After: rest 파라미터
function sum(...numbers) {
  let total = 0;
  for (const number of numbers) {
    total += number;
  }
  return total;
}

// 🎯 더 나아가기: reduce 사용
function sum(...numbers) {
  return numbers.reduce((sum, n) => sum + n, 0);
}

패턴 2: 배열 메소드 활용

// Before: arguments를 배열로 변환
function filterPositive() {
  return Array.from(arguments).filter(n => n > 0);
}

// After: rest 파라미터로 간결하게
function filterPositive(...numbers) {
  return numbers.filter(n => n > 0);
}

패턴 3: 첫 번째 인자는 별도 처리

// Before: arguments와 slice
function multiply(factor) {
  return Array.prototype.slice.call(arguments, 1).map(n => n * factor);
}

// After: rest 파라미터로 명확하게
function multiply(factor, ...numbers) {
  return numbers.map(n => n * factor);
}

multiply(2, 1, 2, 3); // [2, 4, 6]

패턴 4: 여러 타입의 인자 처리

// Before: arguments로 복잡한 오버로딩
function createPerson() {
  if (arguments.length === 1) {
    return { name: arguments[0] };
  } else if (arguments.length === 2) {
    return { name: arguments[0], age: arguments[1] };
  } else if (arguments.length === 3) {
    return { name: arguments[0], age: arguments[1], email: arguments[2] };
  }
}

// After: 명시적 매개변수 사용 (더 명확)
function createPerson(name, age = null, email = null) {
  return { name, age, email };
}

// 또는 객체 매개변수 사용 (가장 명확)
function createPerson({ name, age = null, email = null }) {
  return { name, age, email };
}

패턴 5: 가변 인자 + 옵션 객체

// Before: arguments로 복잡하게
function log() {
  const args = Array.from(arguments);
  const options = typeof args[args.length - 1] === 'object'
    ? args.pop()
    : {};
  const messages = args;

  console.log(`[${options.level || 'INFO'}]`, ...messages);
}

// After: rest 파라미터 + 기본값
function log(...args) {
  const options = typeof args[args.length - 1] === 'object'
    ? args.pop()
    : {};

  console.log(`[${options.level || 'INFO'}]`, ...args);
}

// 🎯 더 나은 방법: 명시적 분리
function log(level, ...messages) {
  console.log(`[${level}]`, ...messages);
}

레거시 코드에서 arguments 다루기

레거시 코드를 유지보수할 때 arguments를 다루는 안전한 패턴들입니다.

패턴 1: 안전한 배열 변환

// ES5 환경 (레거시)
function toArray() {
  return Array.prototype.slice.call(arguments);
}

// ES6 환경
function toArray() {
  return Array.from(arguments);
}

// 또는 스프레드
function toArray() {
  return [...arguments];
}

패턴 2: 인자 개수 체크

function flexibleFunction(required) {
  // 필수 인자 체크
  if (arguments.length < 1) {
    throw new Error('At least one argument required');
  }

  // 선택적 인자 처리
  const optional1 = arguments.length > 1 ? arguments[1] : defaultValue1;
  const optional2 = arguments.length > 2 ? arguments[2] : defaultValue2;

  // ...
}

// 🎯 현대적 방법: 기본값 사용
function flexibleFunction(required, optional1 = defaultValue1, optional2 = defaultValue2) {
  // 훨씬 명확!
}

패턴 3: 부분 적용 (Partial Application)

// arguments를 사용한 부분 적용
function partial(fn) {
  const presetArgs = Array.prototype.slice.call(arguments, 1);

  return function() {
    const allArgs = presetArgs.concat(Array.from(arguments));
    return fn.apply(this, allArgs);
  };
}

const add = (a, b, c) => a + b + c;
const add5 = partial(add, 5);
console.log(add5(10, 15)); // 30

// 🎯 rest 파라미터로 깔끔하게
function partial(fn, ...presetArgs) {
  return function(...laterArgs) {
    return fn(...presetArgs, ...laterArgs);
  };
}

// 🚀 화살표 함수로 더 간결하게
const partial = (fn, ...presetArgs) => (...laterArgs) =>
  fn(...presetArgs, ...laterArgs);

패턴 4: 함수 오버로딩 시뮬레이션

// jQuery 스타일 오버로딩
function css(element) {
  // 1개 인자: getter
  if (arguments.length === 2 && typeof arguments[1] === 'string') {
    return getComputedStyle(element)[arguments[1]];
  }

  // 2개 인자: setter
  if (arguments.length === 3) {
    element.style[arguments[1]] = arguments[2];
    return element;
  }

  // 객체: 다중 setter
  if (arguments.length === 2 && typeof arguments[1] === 'object') {
    Object.assign(element.style, arguments[1]);
    return element;
  }
}

// 🎯 명시적 매개변수로 더 명확하게
function css(element, property, value) {
  if (typeof property === 'string' && value === undefined) {
    // getter
    return getComputedStyle(element)[property];
  }

  if (typeof property === 'string' && value !== undefined) {
    // setter
    element.style[property] = value;
    return element;
  }

  if (typeof property === 'object') {
    // 다중 setter
    Object.assign(element.style, property);
    return element;
  }
}

arguments의 숨겨진 함정들

함정 1: arguments.callee (비권장)

// ❌ arguments.callee - strict mode에서 금지됨
function factorial(n) {
  if (n <= 1) return 1;
  return n * arguments.callee(n - 1); // strict mode에서 에러!
}

// ✅ 함수 이름 사용
function factorial(n) {
  if (n <= 1) return 1;
  return n * factorial(n - 1);
}

// ✅ 화살표 함수는 이름 필요
const factorial = (n) => n <= 1 ? 1 : n * factorial(n - 1);

callee가 문제일까?

  • strict mode에서 사용 불가
  • 최적화 방해
  • 익명 함수에서만 필요한데, 이제는 이름 있는 함수 표현식 사용 권장

함정 2: arguments는 실시간 업데이트됩니다 (non-strict)

// non-strict mode에서의 혼란스러운 동작
function test(a) {
  console.log(a, arguments[0]); // 1, 1

  a = 10;
  console.log(a, arguments[0]); // 10, 10 (연동!)

  arguments[0] = 20;
  console.log(a, arguments[0]); // 20, 20 (연동!)
}

test(1);

// 이런 버그가 발생할 수 있음
function addTax(price) {
  const tax = 0.1;
  arguments[0] = price * (1 + tax); // 실수로 price 변경!
  return calculateShipping(price); // 변경된 price 사용!
}

해결책: 항상 strict mode 사용!

'use strict';

function test(a) {
  a = 10;
  console.log(a, arguments[0]); // 10, 1 (독립적)
}

함정 3: 배열처럼 보이지만 배열이 아님

function test() {
  console.log(typeof arguments); // 'object'
  console.log(arguments instanceof Array); // false
  console.log(Array.isArray(arguments)); // false

  // 배열 메소드 사용 불가
  // arguments.map(x => x * 2); // TypeError!
  // arguments.forEach(console.log); // TypeError!

  // for...of는 가능 (이터러블이므로)
  for (const arg of arguments) {
    console.log(arg); // 작동
  }
}

함정 4: 화살표 함수와의 혼란

function outer() {
  console.log('outer arguments:', arguments);

  const inner = () => {
    console.log('inner arguments:', arguments); // outer의 arguments!
  };

  inner(4, 5, 6);
}

outer(1, 2, 3);

// 출력:
// outer arguments: Arguments(3) [1, 2, 3]
// inner arguments: Arguments(3) [1, 2, 3] // inner의 인자가 아님!

해결책: rest 파라미터 사용

function outer(...outerArgs) {
  console.log('outer args:', outerArgs);

  const inner = (...innerArgs) => {
    console.log('inner args:', innerArgs);
  };

  inner(4, 5, 6);
}

outer(1, 2, 3);

// 출력:
// outer args: [1, 2, 3]
// inner args: [4, 5, 6] // 명확!

함정 5: 기본값 매개변수와의 상호작용

// arguments는 기본값을 반영하지 않음
function test(a = 10, b = 20) {
  console.log('a:', a);
  console.log('b:', b);
  console.log('arguments[0]:', arguments[0]);
  console.log('arguments[1]:', arguments[1]);
  console.log('arguments.length:', arguments.length);
}

test();

// 출력:
// a: 10
// b: 20
// arguments[0]: undefined
// arguments[1]: undefined
// arguments.length: 0

성능 비교와 고려사항

벤치마크 테스트

// 성능 테스트 함수
function benchmark(name, fn, iterations = 1000000) {
  console.time(name);
  for (let i = 0; i < iterations; i++) {
    fn(1, 2, 3, 4, 5);
  }
  console.timeEnd(name);
}

// 1. arguments 사용
function sumArgs() {
  let total = 0;
  for (let i = 0; i < arguments.length; i++) {
    total += arguments[i];
  }
  return total;
}

// 2. arguments를 배열로 변환
function sumArgsArray() {
  return Array.from(arguments).reduce((sum, n) => sum + n, 0);
}

// 3. rest 파라미터
function sumRest(...numbers) {
  let total = 0;
  for (const num of numbers) {
    total += num;
  }
  return total;
}

// 4. rest 파라미터 + reduce
function sumRestReduce(...numbers) {
  return numbers.reduce((sum, n) => sum + n, 0);
}

// 테스트 실행
benchmark('arguments', sumArgs);           // ~50ms
benchmark('arguments+array', sumArgsArray); // ~80ms
benchmark('rest', sumRest);                // ~45ms
benchmark('rest+reduce', sumRestReduce);   // ~60ms

성능 순위:

  1. rest 파라미터 + for…of (~45ms) - 가장 빠름!
  2. arguments + for 루프 (~50ms)
  3. rest 파라미터 + reduce (~60ms)
  4. arguments + 배열 변환 (~80ms) - 가장 느림

결론: rest 파라미터가 더 빠르거나 비슷하면서도 훨씬 명확합니다!

메모리 사용량

// arguments는 항상 생성됨 (사용하지 않아도)
function unused(a, b, c) {
  // arguments 객체가 생성되었지만 사용 안 함 - 낭비
  return a + b + c;
}

// rest 파라미터는 필요할 때만 배열 생성
function needed(...args) {
  return args.reduce((sum, n) => sum + n, 0);
}

최적화 권장사항

// ❌ 피해야 할 패턴
function bad() {
  const args = Array.from(arguments); // 매번 변환
  return args.map(x => x * 2);
}

// ✅ 권장 패턴
function good(...args) {
  return args.map(x => x * 2); // 변환 불필요
}

// 🎯 성능이 극도로 중요하면
function best(...args) {
  const result = new Array(args.length);
  for (let i = 0; i < args.length; i++) {
    result[i] = args[i] * 2;
  }
  return result;
}

결론: arguments는 이제 그만

핵심 요약

  1. arguments는 레거시입니다
    • ES6 이전의 유산
    • 더 나은 대안(rest 파라미터)이 존재
    • 새 코드에서는 절대 사용하지 말 것
  2. arguments의 문제점
    • 유사 배열 (배열 메소드 사용 불가)
    • 화살표 함수에서 사용 불가
    • 의도가 불명확
    • 매개변수와 이상하게 연결
    • 최적화 방해
  3. rest 파라미터가 더 나은 이유
    • 진짜 배열 (모든 배열 메소드 사용 가능)
    • 화살표 함수에서도 작동
    • 함수 시그니처에 명시됨 (의도가 명확)
    • 일관적인 동작
    • 더 나은 성능
  4. 마이그레이션 가이드
    • 새 코드: 항상 rest 파라미터 사용
    • 레거시 코드: 점진적으로 rest 파라미터로 변경
    • 레거시 유지보수: strict mode에서 안전하게 다루기

빠른 결정 가이드

가변 인자 함수를 만들어야 하나요?
└─ ✅ rest 파라미터 사용: function fn(...args) { }

레거시 코드를 읽어야 하나요?
├─ arguments 동작 이해하기
└─ 가능하면 rest 파라미터로 리팩토링

화살표 함수에서 가변 인자가 필요하나요?
└─ ✅ rest 파라미터만 가능: (...args) => { }

성능이 중요한가요?
└─ ✅ rest 파라미터가 더 빠르거나 비슷함

실전 체크리스트

코드 작성 시 확인할 항목:

  • arguments 대신 rest 파라미터를 사용하고 있나?
  • 함수 시그니처가 명확한가?
  • 화살표 함수와 일반 함수 모두에서 작동하나?
  • strict mode를 사용하고 있나?
  • arguments.callee를 사용하고 있지 않나?

마지막 조언

arguments는 JavaScript의 역사를 이해하는 데는 중요하지만, 실제 코드에서는 더 이상 사용할 이유가 없습니다.

rest 파라미터를 사용하면:

  • 코드가 더 명확해집니다
  • 버그가 줄어듭니다
  • 성능이 더 좋습니다
  • 팀원들이 이해하기 쉽습니다

레거시 코드를 만났을 때만 arguments를 이해하면 됩니다. 새로운 코드를 작성할 때는 항상 rest 파라미터를 사용하세요. 🎯

참고 자료

MDN 공식 문서

심화 학습

성능과 최적화

관련 문서

댓글