본문 바로가기

Languages/JavaScript

[JavaScript] 나머지 매개변수(...), arguments 객체, 스프레드 문법

728x90

   

 

   목차 

1. 나머지 매개변수 (rest parameter)

2. arguments 객체

3. 스프레드 문법 (전개구문, spread syntax)

4. 점 삼총사 간단 비교 (구조분해할당, 나머지 매개변수, 스프레드문법)

 

 

 

정해지지 않은 수의 인수를 받는 방법과 함수의 매개변수에 배열을 전달하는 방법을 알아보자!

 

 


 

 

 나머지 매개변수 

 

나머지 매개변수에 대해서 설명하기 전에, 우선 나머지 매개변수가 왜 필요한지에 대해 알아보자.

아래의 예시와 같이 함수에 넘겨주는 인수의 개수엔 제약이 없다. 

 

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

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

 

함수를 정의할 때는 인수를 두개(a, b)만 받도록 하고, 실제 함수를 호출할 때는 이보다 더 많은 '여분의' 인수(3 ,4, 5)를 전달했지만 에러가 발생하지 않는다.

다만, 반환값은 처음 2개의 인수만을 사용해 계산된다. ( 1, 2 만 들어가서 3이 나옴)

 

하지만, 내가 인수를 몇개를 전달하든지간에, 알아서 매개변수가 다 받게끔 하고 싶을 때는 어떻게 해야할까?

가령, 내가 함수에 인수를 몇개를 전달할 지 모르는 상황이기 때문에 매개변수의 개수를 특정할 수 없는 상황이라면?

만약 받는 인수가 100개가 넘는다면, 매개변수에다가 a,b,c,d,e,f...... 를 전부 다 쓸 수는 없는 노릇이다.

그래서 나온 문법이 '나머지 매개변수(rest parameter)' 문법이다.

 

function f(a, b, ...theArgs) {
  // ...
}

 

위와 같이 함수의 마지막 매개변수 앞에 '...'를 붙이면 모든 후속 매개변수를 배열에 넣도록 지정할 수 있다.
여기서 theArgs는 배열의 이름이며 (당연히 이름은 변경 가능하다), 
마침표 세개(...)는 "남아 있는 인수들을 한데 모아 배열(theArgs)에 집어넣어라"를 의미한다. 

 

 

*주의사항

 

1) 함수의 정의에는 하나의 ...만 존재 할 수 있다.

foo(...one, ...wrong, ...wrong) // 이렇게 쓰면 안됨


2) 나머지 매개변수는 반드시 함수 정의의 마지막 매개변수여야 한다. (= 나머지 매개변수는 항상 마지막에 있어야 한다.)

foo(...wrong, arg2, arg3) // 이렇게 쓰면 안됨

foo(arg1, arg2, ...correct) // 이렇게 써야함

 

 

 

 나머지 매개변수의 예시 1 

 매개변수 2개 제공 

function myFun(a, b, ...Args) {
  console.log(a) // one
  console.log(b) // two
  console.log(Args) // []  <-- 빈 배열이 나온다.
}

myFun("one", "two")

 

 

 

 나머지 매개변수의 예시 2 

매개변수 3개 제공

function myFun(a, b, ...Args) {
  console.log(a) // one
  console.log(b) // two
  console.log(Args) // ['three']  <-- 요소가 하나지만 배열임
}

myFun("one", "two", "three")

 

 

 

 나머지 매개변수의 예시 3 

매개변수 6개 제공

function myFun(a, b, ...Args) {
  console.log(a) // one
  console.log(b) // two
  console.log(Args) // (4) ['three', 'four', 'five', 'six']
}

myFun("one", "two", "three", "four", "five", "six")

 

 

 

 나머지 매개변수의 예시 4 

배열 내 특정 요소를 얻기, 길이 알아내기

function showName(firstName, lastName, ...titles) {
  alert( firstName ); // Haein
  alert( lastName ); // Hwang

// 나머지 인수들은 배열 titles의 요소가 된다. // titles = ["Engineer", "Researcher"]
  alert(titles); // Engineer,Researcher
  alert( titles[0] ); // Engineer
  alert( titles[1] ); // Researcher
  alert( titles.length ); // 2  // titles가 배열이므로, length 속성을 사용하면 길이를 알아낼 수 있다.
}

showName("Haein", "Hwang", "Engineer", "Researcher");

 

 

 

 나머지 매개변수의 예시 5 

나머지 매개변수를 일반 매개변수와 함께 사용하기 (map 돌려보기)
: 첫 번째 이후의 모든 매개변수를 배열에 모은 후, 각각의 값을 첫 번째 매개변수로 곱한 결과를 반환한다.

function multiply(multiplier, ...theArgs) {
  return theArgs.map(element => {
    return multiplier * element
  })
}

let arr = multiply(2, 15, 25, 42)
console.log(arr)  // [30, 50, 84]
< 과정 > 

- 함수(function multiply~)를 만나면, 함수 호출부로 이동한다. → multiplay ( 2, 15, 25, 42 );
-  multiplay ( 2, 15, 25, 42 )에서 인수 2가 첫번째 매개변수 multiplier에 들어가고, 나머지 인수 15, 25, 42가 매개변수 theArgs에 들어가서 [15, 25, 42]가 된다. 
- theArgs 배열에 map을 돌린다. (map은 callback 함수를 각각의 요소에 대해 한번씩 순서대로 불러 그 함수의 반환값으로 새로운 배열을 만든다.)
- 여기서 element에는 15, 25, 42가 차례대로 들어가게 될 것이다.
- 첫번째로 multiplier * element 이므로, 2 x 15 라서 30이 리턴된다.
- 두번째로 multiplier * element 이므로, 2 x 25 라서 50이 리턴된다.
- 세번째로 multiplier * element 이므로, 2 x 42 라서 84가 리턴된다.
- 최종적으로 새로운 배열 [30, 50, 84]가 리턴되고, 이 배열이 변수 arr에 담긴다. // let arr = multiply(2, 15, 25, 42)
- arr을 콘솔에 찍으면 [30, 50, 84]가 나온다. // console.log(arr)  // [30, 50, 84]

 

 

 

 나머지 매개변수의 예시 6 

for...of문 돌려보기

function sumAll(...args) { // args는 배열의 이름이다.
  let sum = 0;

  for (let arg of args) sum += arg;

  return sum;
}

alert( sumAll(1) ); // 1
alert( sumAll(1, 2) ); // 3
alert( sumAll(1, 2, 3) ); // 6
< 과정 >

- 함수(function sumAll~)를 만나면, 함수 호출부로 이동한다. → alert (sumAll(1) );
- sumAll(1) 에서 인수 1이 매개변수 args로 들어가서 [1] 이 된다.
- for...of문에서 args에 [1]이 들어가서 반복문을 돌게된다.
- sum=sum+arg 이므로, sum=0+1이므로 sum=1이고, sum이 1로 최종 리턴된다. 
- alert( sumAll(1) ); // 1

- sumAll(1, 2) 에서 인수 1 ,2가 매개변수 args로 들어가서 [1, 2] 이 된다.
- for...of문에서 args에 [1,2]이 들어가서 반복문을 돌게된다.- sum=sum+arg 이므로, sum=0+1이므로 sum=1이고, sum이 1로 리턴된다.
- 다시 sum=sum+arg 이므로, sum=1+2이므로 sum=3이고, sum이 3으로 최종 리턴된다. 
- alert( sumAll(1, 2) ); // 3

- sumAll(1, 2, 3) 에서 인수 1, 2, 3이 매개변수 args로 들어가서 [1, 2, 3]이 된다.
- for...of문에서 args에 [1, 2, 3]이 들어가서 반복문을 돌게된다.- sum=sum+arg 이므로, sum=0+1이므로 sum=1이고, sum이 1로 리턴된다.
- 다시 sum=sum+arg 이므로, sum=1+2이므로 sum=3이고, sum이 3으로 리턴된다. 
- 다시 sum=sum+arg 이므로, sum=3+3이므로 sum=6이고, sum이 6으로 리턴된다. 
- alert( sumAll(1, 2, 3) ); // 6




 

 

 arguments 객체 

 

arguments 객체는 함수에 전달된 인수에 해당하는 Array 형태의 객체(유사 배열 객체(array-like object))이다.

"Array 형태"란 arguments가 length속성과 더불어 0부터 인덱스 된 다른 속성을 가지고 있지만, Array의 forEach, map과 같은 내장 메서드를 가지고 있지 않다는 뜻이다.

즉, arguments 객체는 Array가 아니다. Array와 비슷하지만, length 빼고는 pop()과 같은 어떤 Array 속성도 없다. 그러나 실제 Array로 변환할 수는 있다. (Array로의 변환은 하단에 서술 예정)

 

 

 

 arguments 객체의 속성 

 

 

callee 현재 실행 중인 함수를 가리킨다. showName()
length 함수에 전달된 인수의 수를 가리킨다. 2 (Bora, Lee라서 총 2개)
iterator arguments의 각 인덱스 값을 포함하는 새로운 Array Iterator 객체를 반환한다. values

 

 

arguments 객체를 사용하면 길이를 알아낼 수 있으며, 인덱스를 사용해 인수에 접근할 수 있다.

function showName() {
  alert( arguments.length );
  alert( arguments[0] );
  alert( arguments[1] );
}

showName("Bora", "Lee"); // 2, Bora, Lee가 출력됨

showName("Bora"); // 1, Bora, undefined가 출력됨(두 번째 인수는 없음)

 

 

각 인수를 설정하거나 재할당할 수도 있다.

function showName() {
arguments[0] = "value1";
arguments[1] = "value2";
console.log( arguments[0], arguments[1] );
}

showName("Bora", "Lee"); // value1 value2

 

 

 

 유사배열인 arguments 객체를 실제 배열로 변환하기 

 

 1) Array.prototype.slice.call() 사용 

let args = Array.prototype.slice.call(arguments);
let args = [].slice.call(arguments);

 

 

 2) Array.from() 사용 

let args = Array.from(arguments);

 

 

 3) 스프레드 연산자 사용 

let args = [...arguments];

 

 

 

 나머지 매개변수와 arguments 객체의 차이 



1. arguments는 오래 전부터 나온 문법으로 함수의 인수 전체를 알아내는 방법으로 쓰였고, 나머지 매개변수는 비교적 최신에 나온 문법이다. 

 

2. 나머지 매개변수는 진짜 배열인데, argument 객체는 실제 배열이 아니고 유사배열객체이면서, 이터러블(반복 가능한)객체이다. 따라서 나머지 매개변수는 Array 인스턴스이므로 sort, map, forEach, pop 등의 메서드를 직접 적용할 수 있다.

*참고) arguments는 이터러블 객체이기 때문에 for...of문을 돌릴 수는 있다. 즉,  for...of를 사용해 인수를 펼칠 수 있다.

 

2-1. 나머지 매개변수에 배열 메서드 sort()를 써 본 예시 

function sortRestArgs(...theArgs) {
  let sortedArgs = theArgs.sort()
  return sortedArgs
}

console.log( sortRestArgs(5, 3, 7, 1) ) // (4) [1, 3, 5, 7]
< 과정 >

- 함수(function sortRestArgs)를 만나면, 함수 호출부로 이동한다. → console.log( sortRestArgs(5, 3, 7, 1) );
-  sortRestArgs(5, 3, 7, 1) 에서 인수 5, 3, 7, 1이 매개변수 theArgs로 들어가서 [5, 3, 7, 1]이 된다.
- theArgs에 sort() 배열 메서드를 사용한다. 
- 참고로 sort() 메서드는 배열의 요소를 재정렬해서 배열 자체를 변경시키는 메서드이다. (모든 요소는 문자형으로 변환되된 이후에 재정렬된다. (사전편집순으로)
- 따라서 sort()를 사용하면 [5, 3, 7, 1] 이 [1, 3, 5, 7]로 재정렬된다. - [1, 3, 5, 7]을 변수 sortedArgs에 할당하고, sortedArgs를 리턴한다. - 따라서 sortedArgs를 콘솔에 출력하면 [1, 3, 5, 7] 이 뜬다. // console.log( sortRestArgs(5, 3, 7, 1) ) // [1, 3, 5, 7]

 

 

2-2. arguments 객체에 배열 메서드(sort())를 써 본 예시 

function sortArguments() {
  let sortedArgs = arguments.sort()
  return sortedArgs
}

console.log(sortArguments(5, 3, 7, 1))
// TypeError 발생 (arguments.sort is not a function)
< 과정 >

- 함수(function sortArguments)를 만나면, 함수 호출부로 이동한다. → console.log(sortArguments(5, 3, 7, 1) );
- arguments 객체로  [5, 3, 7, 1]이 들어간다.
- arguments 객체에 sort() 배열 메서드를 사용한다. 
- arguments 객체는 유사배열이기 때문에 직접적으로 배열 메서드를 쓸 수 없다.
- 따라서 sort()를 사용하면 에러가 발생한다. 

 

 

3. 나머지변수는 인수의 일부만 사용할 수 있지만, arguments는 인수 전체를 담기 때문에 인수의 일부만 사용할 수 없다. 

→ 따라서 배열 메서드를 사용하거나 인수 일부만 사용할 때는 나머지 매개변수를 사용하는게 좋다.

 

 


 

 

 스프레드 문법 

 

앞에서는 '나머지 매개변수'를 이용해서 '매개변수 목록을 배열로 가져오는 방법'을 알아보았다.

아래 예시에서 "three", "four", "five", "six" (매개변수 목록)을 ...Args를 통해  ['three', 'four', 'five', 'six'] (배열)로 가져오는 것 처럼 말이다.

function myFun(a, b, ...Args) {
  console.log(a) // one
  console.log(b) // two
  console.log(Args) // (4) ['three', 'four', 'five', 'six']
}

myFun("one", "two", "three", "four", "five", "six")

 

만약 이렇게 여러 매개변수를 배열에 넣고 싶은게 아니라,
반대로 배열을 통째로 매개변수로 넘겨주고 싶을 때는 어떻게 해야할까?
즉, 배열을 인수목록으로 바꾸고 싶은 것이다.

이 말이 무슨 의미인지 잘 와닿지 않았는데,

나머지 매개변수는 [ ] 를 만들어준다. (=인수에 [ ] 를 붙여서 배열로 만들어준다)

스프레드 연산자는 [ ]을 없애준다. (=배열에서 [ ] 를 떼서 인수목록으로 만들어준다)

 

라고 이해하니 쉽게 이해가 됐다.

 

그렇다면 [ ]를 떼야하는 상황이 어떤게 있을지 알아보자.

배열 [3, 5, 1]이 있고, 이 배열을 대상으로 Math.max를 호출하고 싶다고 가정해보자.

아무런 조작 없이 배열을 ‘있는 그대로’ Math.max에 넘기면 원하는 대로 동작하지 않는다. 

Math.max는 배열이 아닌 숫자 목록을 인수로 받기 때문이다.

let arr = [3, 5, 1];

alert( Math.max(arr) ); // NaN

 

*참고) Math.max는 인수로 받은 숫자 중 가장 큰 숫자를 반환한다.

alert( Math.max(3, 5, 1) );  // 5

 

그렇다면, 배열을 인수목록으로 바꿔줘야하는데, 이때 필요한 것이 스프레드 문법이다.

함수를 호출할 때 ...arr를 사용하면, 이터러블 객체 arr이 인수 목록으로 '확장’된다.

let arr = [3, 5, 1];

alert( Math.max(...arr) ); // 5 (스프레드 문법이 배열을 인수 목록으로 바꿔주었다.)

 

 

 

 스프레드 문법의 예시 1 

이미 존재하는 배열을 일부로 하는 새로운 배열을 생성하기 

let parts = ['shoulders', 'knees'];
let lyrics = ['head', ...parts, 'and', 'toes'];

console.log(lyrics); // ["head", "shoulders", "knees", "and", "toes"]

 

 

 

 스프레드 문법의 예시 2 

배열을 연결하기1 concat을 사용한 것과 비교해보기

- concat을 사용했을 경우 (concat은 기존 배열에 요소를 추가해서 새로운 배열을 만든다)

let arr1 = [0, 1, 2];
let arr2 = [3, 4, 5];
// arr2 의 모든 항목을 arr1 에 붙임

console.log (arr1.concat(arr2)); // [0, 1, 2, 3, 4, 5]

 

- 스프레드 문법을 사용했을 경우

let arr1 = [0, 1, 2];
let arr2 = [3, 4, 5];

console.log([...arr1, ...arr2]); // [0, 1, 2, 3, 4, 5]

 

 

배열을 연결하기2 unshift를 이용한 것과 비교 

- unshift를 사용했을 경우 (unshift는 배열 앞에 요소를 추가한다)

let arr1 = [0, 1, 2];
let arr2 = [3, 4, 5];
// arr2 의 모든 항목을 arr1 의 앞에 붙임
Array.prototype.unshift.apply(arr1, arr2) // arr1 은 이제 [3, 4, 5, 0, 1, 2] 가 됨

 

- 스프레드 문법을 사용했을 경우

var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
arr1 = [...arr2, ...arr1]; // arr1 은 이제 [3, 4, 5, 0, 1, 2] 가 됨

 

배열 메서드가 헷갈린다면, https://dev-ini.tistory.com/51 게시글을 참고하세요!

 

 

 

 스프레드 문법의 예시 3 

이터러블 객체 여러개를 전달하기

let arr1 = [1, -2, 3, 4];
let arr2 = [8, 3, -8, 1];

alert( Math.max(...arr1, ...arr2) ); // 8

 

 

 

 스프레드 문법의 예시 4 

스프레드 문법을 평범한 값과 혼합해 사용하기

let arr1 = [1, -2, 3, 4];
let arr2 = [8, 3, -8, 1];

alert( Math.max(1, ...arr1, 2, ...arr2, 25) ); // 25

 

 

 

 스프레드 문법의 예시 5 

배열이 아닌 이터러블 객체에 스프레드 문법 사용해보기 → Array.from를 사용한 것과 비교해보기

- 스프레드 문법을 사용했을 경우

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를 사용했을 경우

let str = "Hello";

// Array.from은 이터러블을 배열로 바꿔준다.
console.log( Array.from(str) ); // ['H', 'e', 'l', 'l', 'o']

 

[...str]과 동일한 결과가 출력된다.

 

 

 그렇다면 Array.from(obj)와 [...obj] 차이점은 무엇일까? 

 

- Array.from은 유사 배열 객체와 이터러블 객체 둘 다에 사용할 수 있다.

- 스프레드 문법은 이터러블 객체에만 사용할 수 있습니다.

- 이런 이유때문에 무언가를 배열로 바꿀 때는 스프레드 문법보다 Array.from이 보편적으로 사용된다.

 

 

 

 배열과 객체의 복사본 만들기 

 

https://dev-ini.tistory.com/37 복사 게시글에서 얇은 복사 中 값 복사를 서술했던 적이 있다. (모르면 참고하자!)

그 중 2번째 방법으로  Object.assign()을 사용해 객체를 복사 했었는데,이 방법 말고도 스프레드 문법을 사용하면 배열과 객체를 복사할 수 있다.

 


 1. 배열을 복사하기 

let arr = [1, 2, 3];
let arrCopy = [...arr]; // 배열을 펼쳐서 각 요소를 분리후, 매개변수 목록으로 만든 다음에
                        // 매개변수 목록을 새로운 배열에 할당함


alert(JSON.stringify(arr) === JSON.stringify(arrCopy)); // true
// 기존 배열 '요소'와 배열 복사본의 '요소'가 같다는 점을 확인함.


alert(arr === arrCopy); // false (두 배열은 '참조'가 다름)


arr.push(4);
alert(arr); // 1, 2, 3, 4
alert(arrCopy); // 1, 2, 3  // 참조가 다르므로 기존 배열을 수정해도 복사본은 영향을 받지 않는다.

 

 

2. 객체를 복사하기

let obj = { a: 1, b: 2, c: 3 };
let objCopy = { ...obj }; // 객체를 펼쳐서 각 요소를 분리후, 매개변수 목록으로 만든 다음에
                          // 매개변수 목록을 새로운 객체에 할당함


alert(JSON.stringify(obj) === JSON.stringify(objCopy)); // true
// 기존 객체 '프로퍼티'와 객체 복사본의 '프로퍼티'가 같다는 점을 확인함.


alert(obj === objCopy); // false (두 객체는 참조가 다름)


obj.d = 4;
alert(JSON.stringify(obj)); // {"a":1,"b":2,"c":3,"d":4}
alert(JSON.stringify(objCopy)); // {"a":1,"b":2,"c":3}  // 참조가 다르므로 기존 객체를 수정해도 복사본은 영향을 받지 않는다.

 

 


 

 

 점 삼총사 (구조분해할당, 나머지 매개변수, 스프레드문법) 간단 비교 

 

 

 구조분해 할당 

배열 앞쪽에 위치한 값 몇 개만 필요하고 그 이후 이어지는 나머지 값들은 한데 모아서 저장하고 싶을 때, 
배열에서 마지막 요소에 ...을 붙인 매개변수 하나만 추가하면 나머지(rest)요소를 가져올 수 있다.

let [name1, name2, ...rest] = ["A", "B", "C", "D"];

console.log(name1); // A
console.log(name2); // B
console.log(rest); // ['C', 'D']

 

 

 나머지 매개변수 

 함수의 마지막 매개변수 앞에 '...'를 붙이면 모든 후속 매개변수를 배열에 넣도록 지정할 수 있다.

function myFun(name1, name2, ...Args) {
  console.log(name1) // A
  console.log(name2) // B
  console.log(Args) // (4) ['C, 'D', 'E', 'F']
}

myFun('A', 'B', 'C', 'D', 'E', 'F')

 

 

 스프레드 문법 

...arr를 사용하면, 이터러블 객체 arr이 인수 목록으로 '확장’된다. 즉, 배열을 인수목록으로 바꿀 수 있게 해준다.

let arr = ['B', 'C'];
let alphabets = ['A', ...arr, 'D', 'E'];

console.log(alphabets); // ['A', 'B', 'C', 'D', 'E']

 

 

 

 

 

참고 자료

https://ko.javascript.info/rest-parameters-spread

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Functions/rest_parameters

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Functions/arguments

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/Spread_syntax

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/concat

https://ko.javascript.info/destructuring-assignment

728x90