함수 정의 방법과 상관 없이 함수에 넘겨주는 인수의 개수엔 제약이 없다.
아래의 예시를 보면
function sum(a, b) {
return a + b;
}
console.log( sum(1, 2, 3, 4, 5) ); // 3
함수를 정의할 때는 인수를 2개만 받도록 하고, 실제 함수 호출 시 이보다 더 많은 '여분의' 인수를 전달했지만 에러는 발생하지 않았다. 단, 반환 값은 처음 2개의 인수만을 사용해 계산된다.
이렇게 여분의 매개변수는 그 값들을 담을 배열 이름을 마침표 세개...
를 뒤에 붙여주면 함수 선언부에 포함시킬 수 있다.
아래의 예시에서는 모든 인수가 배열 args
에 모이게 된다.
function sumAll(...args) { // args는 배열의 이름
let sum = 0;
for (let arg of args) sum += arg;
return sum;
}
console.log( sumAll(1) ); // 1
console.log( sumAll(1, 2) ); // 3
console.log( sumAll(1, 2, 3) ); // 6
앞부분의 매개변수는 변수로, 남아있는 매개변수들은 배열로 모을 수도 있다.
아래 예시에선 처음 두 인수는 변수에, 나머지 인수들은 title
라는 배열에 할당된다.
function showName(firstName, lastName, ...titles) {
console.log( firstName + ' ' + lastName ); // Bora Lee
// 나머지 인수들은 배열 titles의 요소가 된다.
// titles = ["Software Engineer", "Researcher"]
console.log( titles[0] ); // Software Engineer
console.log( titles[1] ); // Researcher
console.log( titles.length ); // 2
}
showName("Bora", "Lee", "Software Engineer", "Researcher");
★ 나머지 매개변수...
는 항상 마지막에 있어야 한다!
유사 배열 객체인 arguments를 사용하면 인덱스를 사용해 인수에 접근할 수 있다.
function showName() {
console.log( arguments.length );
console.log( arguments[0] );
console.log( arguments[1] );
// arguments는 이터러블 객체이기 때문에
// for(let arg of arguments) alert(arg); 를 사용해 인수를 펼칠 수 있다.
}
// 2, Hun, Seung 출력됨
showName("Hun", "Seung");
// 1, Hun, undefined가 출력됨(두 번째 인수는 없음)
showName("Hun");
나머지 매개변수는 비교적 최근에 나온 문법이다. 나머지 매개변수가 나오기 이전엔 함수의 인수 전체를 얻어내는 방법이 arguments를 사용하는 것밖에 없었다. 물론 지금도 arguments를 사용할 수 있지만 오래된 코드를 보다 보면 arguments를 만나게 된다.
arguments는 유사 배열 객체이면서 iterable(반복 가능한) 객체다. 배열이 아니므로 배열 메서드를 사용할 수 없다는 단점이 있다. 예를 들면 arguments.map (...)을 호출할 수 없다.
여기에 더하여 arguments는 인수 전체를 담기 때문에 나머지 매개변수처럼 인수의 일부만 사용할 수 없다는 단점도 있다.
따라서 배열 메서드를 사용하거나 인수 일부만 사용할 때는 나머지 매개변수를 사용하는게 좋다.
★ 화살표 함수는 arguments객체를 지원하지 않는다!
지금까지 매개변수 목록을 배열로 가져오는 방법에 대해 살펴보았다.
그런데 개발을 하다 보면 반대되는 기능이 필요할 때가 생긴다. 배열을 통째로 매개변수에 넘겨주는 것 같이 말이다.
예시를 통해 이런 경우를 살펴보자. 내장 함수 Math.max
는 인수로 받은 숫자 중 가장 큰 숫자를 반환한다.
console.log( Math.max(3, 5, 1) ); // 5
배열 [3, 5, 1]이 있고, 이 배열을 대상으로 Math.max
를 호출하고 싶다고 가정해보자.
아무런 조작 없이 배열을 ‘있는 그대로’ Math.max
에 넘기면 원하는 대로 동작하지 않는다. Math.max
는 배열이 아닌 숫자 목록을 인수로 받기 때문이다.
let arr = [3, 5, 1];
console.log( Math.max(arr) ); // NaN
Math.max (arr[0], arr[1], arr[2])
처럼 배열 요소를 수동으로 나열하는 방법도 있긴 한데, 배열 길이를 알 수 없을 때는 이마저도 불가능하다. 스크립트가 돌아갈 때 실제 넘어오는 배열의 길이는 아주 길 수도 있고, 아예 빈 배열일 수도 있기 때문이다.
스프레드 문법(spread syntax, 전개 문법) 은 이럴 때 사용하기 위해 만들어졌다. ...
를 사용하기 때문에 나머지 매개변수와 비슷해 보이지만, 스프레드 문법은 나머지 매개변수와 반대되는 역할을 한다.
함수를 호출할 때 ...arr
를 사용하면, iterable 객체 arr이 인수 목록으로 '확장’된다.
Math.max
를 사용한 예시로 다시 돌아가서 확인해보자
let arr = [3, 5, 1];
console.log( Math.max(...arr) ); // 5 (스프레드 문법이 배열을 인수 목록으로 바꿨다.)
아래와 같이 이터러블 객체 여러 개를 전달하는 것도 가능하다.
let arr1 = [1, -2, 3, 4];
let arr2 = [8, 3, -8, 1];
console.log( Math.max(...arr1, ...arr2) ); // 8
스프레드 문법을 평범한 값과 혼합해 사용하는 것도 가능하다.
let arr1 = [1, -2, 3, 4];
let arr2 = [8, 3, -8, 1];
console.log( Math.max(1, ...arr1, 2, ...arr2, 25) ); // 25
스프레드 문법은 배열을 합칠 때도 활용할 수 있다.
let arr = [3, 5, 1];
let arr2 = [8, 9, 15];
let merged = [0, ...arr, 2, ...arr2]; // 0,3,5,1,2,8,9,15 (0, arr, 2, arr2 순서로 합쳐진다.)
앞선 예시들에선 '배열’을 대상으로 스프레드 문법이 어떻게 동작하는지 살펴보았다. 그런데 배열이 아니더라도 iterable 객체이면 스프레드 문법을 사용할 수 있다.
스프레드 문법을 사용해 문자열을 문자 배열로 변환시켜 보자.
let str = "Hello";
console.log( [...str] ); // H,e,l,l,o
스프레드 문법은 for..of
와 같은 방식으로 내부에서 이터레이터(iterator, 반복자)를 사용해 요소를 수집한다.
문자열에 for..of
를 사용하면 문자열을 구성하는 문자가 반환됩니다. ...str
도 H,e,l,l,o가 되는데, 이 문자 목록은 배열 초기자(array initializer) [...str]
로 전달된다.
메서드 Array.from
은 iterable 객체인 문자열을 배열로 바꿔주기 때문에 Array.from
을 사용해도 동일한 작업을 할 수 있다.
let str = "Hello";
// Array.from은 이터러블을 배열로 바꿔준다.
console.log( Array.from(str) ); // H,e,l,l,o
[...str]
과 동일한 결과가 출력되는 것을 확인할 수 있다.
그런데 Array.from(obj)
와 [...obj]
는 다음과 같은 미묘한 차이가 있다.
Array.from
은 유사 배열 객체와 iterable 객체 둘 다에 사용할 수 있습니다.이런 이유때문에 무언가를 배열로 바꿀 때는 스프레드 문법보다 Array.from
이 보편적으로 사용된다.
Object.assign()
참조에 의한 객체 복사 챕터에서 Object.assign()
을 사용해 객체를 복사한 예시를 떠올려보자.
Object.assign()
말고도 스프레드 문법을 사용하면 배열과 객체를 복사할 수 있다.
let arr = [1, 2, 3];
let arrCopy = [...arr]; // 배열을 펼쳐서 각 요소를 분리후, 매개변수 목록으로 만든 다음에
// 매개변수 목록을 새로운 배열에 할당함
// 배열 복사본의 요소가 기존 배열 요소와 진짜 같을까?
console.log(JSON.stringify(arr) === JSON.stringify(arrCopy)); // true
// 두 배열은 같을까?
console.log(arr === arrCopy); // false (참조가 다름)
// 참조가 다르므로 기존 배열을 수정해도 복사본은 영향을 받지 않는다.
arr.push(4);
console.log(arr); // 1, 2, 3, 4
console.log(arrCopy); // 1, 2, 3
이번엔 객체를 복사하는 예시를 살펴보자.
let obj = { a: 1, b: 2, c: 3 };
let objCopy = { ...obj }; // 객체를 펼쳐서 각 요소를 분리후, 매개변수 목록으로 만든 다음에
// 매개변수 목록을 새로운 객체에 할당함
// 객체 복사본의 프로퍼티들이 기존 객체의 프로퍼티들과 진짜 같을까?
console.log(JSON.stringify(obj) === JSON.stringify(objCopy)); // true
// 두 객체는 같을까?
console.log(obj === objCopy); // false (참조가 다름)
// 참조가 다르므로 기존 객체를 수정해도 복사본은 영향을 받지 않는다.
obj.d = 4;
console.log(JSON.stringify(obj)); // {"a":1,"b":2,"c":3,"d":4}
console.log(JSON.stringify(objCopy)); // {"a":1,"b":2,"c":3}
이렇게 스프레드 문법을 사용하면 let objCopy = Object.assign({}, obj);
, let arrCopy = Object.assign([], arr);
보다 더 짧은 코드로 배열이나 객체를 복사할 수 있어서 사람들은 이 방법을 선호하는 편이다.