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 객체를 이해해야 할까요?
- 먼저, arguments가 무엇인지 알아봅시다
- arguments의 근본적인 문제점
- 현대적인 대안: rest 파라미터
- arguments를 rest 파라미터로 마이그레이션하기
- 레거시 코드에서 arguments 다루기
- 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);
설계 이유:
- this와의 일관성: 화살표 함수는 렉시컬 this를 사용하므로, arguments도 같은 방식으로 동작
- 간결함 우선: 복잡한 가변 인자가 필요하면 일반 함수나 rest 파라미터 사용 권장
- 생성자 불가: 화살표 함수는
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
성능 순위:
- rest 파라미터 + for…of (~45ms) - 가장 빠름!
- arguments + for 루프 (~50ms)
- rest 파라미터 + reduce (~60ms)
- 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는 이제 그만
핵심 요약
- arguments는 레거시입니다
- ES6 이전의 유산
- 더 나은 대안(rest 파라미터)이 존재
- 새 코드에서는 절대 사용하지 말 것
- arguments의 문제점
- 유사 배열 (배열 메소드 사용 불가)
- 화살표 함수에서 사용 불가
- 의도가 불명확
- 매개변수와 이상하게 연결
- 최적화 방해
- rest 파라미터가 더 나은 이유
- 진짜 배열 (모든 배열 메소드 사용 가능)
- 화살표 함수에서도 작동
- 함수 시그니처에 명시됨 (의도가 명확)
- 일관적인 동작
- 더 나은 성능
- 마이그레이션 가이드
- 새 코드: 항상 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 공식 문서
심화 학습
- ES6 In Depth: Rest parameters and defaults
- You Don’t Know JS: ES6 & Beyond
- JavaScript: The Good Parts - Douglas Crockford
성능과 최적화
관련 문서
- array-like-objects.md - 유사 배열 객체 상세 설명
- function-vs-arrow-function.md - 화살표 함수와 일반 함수의 차이
- foreach-vs-reduce-functional-programming.md - 함수형 프로그래밍 패턴
댓글