본문 바로가기

programming/javascript

Rest syntax VS Spread syntax (...)

나머지 연산자 vs 전개 연산자

자바스크립트 개발을 할 때 ' ... ' 연산자는 매우 유용하게 쓰인다. 쓰임에 따라서 나머지 연산자 혹은 전개 연산자라고 불리는데, 말 그대로 나머지를 가져오거나 어딘가에 배열이나 객체를 펼쳐놓을 때 많이 썼다. 쓸 줄도 알고 이름도 알았지만 이렇게 분류해서 생각해본 적은 처음이다. 그냥 같은 것이라고 생각만하고 썼는데, 직접 분류하려고 보니 명백하게 다른 쓰임새를 갖고 있었다. 

그래서 한번 정리를 해보려한다.

 

 


Rest syntax (나머지 구문)

Rest ParameterRest Property로 나누어 진다. 이름 그대로 생각하면 된다. 전자는 매개변수이고 후자는 객체의 프로퍼티이다.

 

rest parameter (나머지 매개변수)

매개변수의 이름 앞에 ... 를 붙여서 정의한 변수를 말한다. 이 파라미터에는 해당 함수를 호출 할 때 전달 된 인수들의 배열이 할당된다.

 

function func(...rest) {
	console.log(rest) // [1, 2, 3, 4];
}

func(1, 2, 3, 4);

 

연산자의 이름이 '나머지' 인 이유는 이름을 붙여서 받을 인수들은 받고, 이름 붙인 인수들을 제외한 나머지(뒷부분) 인수들을 배열로 받기 때문이다. 근데 나머지 는 반드시 뒤에 남은 인수들에 대해서만 적용이 가능하다.

 

function func(a, b, ...rest) {
  console.log(a); // 1
  console.log(b); // 2
  console.log(rest); // [3, 4];
}

func(1, 2, 3, 4);

function func2(...rest, c, d) { // 나머지 연산자는 이름을 붙일 매개변수를 제외하고 뒤에 남은 인수들에게만 적용된다.
  console.log(c); // 에러...
  console.log(d); 
  console.log(rest);
}

func2(1, 2, 3, 4);

function func3(a, b, c, d, ...rest) {
  console.log(a, b, c, d) // 1 2 3 4
  console.log(rest) // [] 나머지로 가져올 인수가 없으면 그냥 빈 배열이 만들어진다.
}

func3(1, 2, 3, 4);

 

arguments vs Rest parameter

  • arguments는 함수 호출시에 전달한 모든 인수를 포함하는 유사배열 객체이다. 반면 나머지 연산자는 직접 이름을 붙인 매개변 수를 제외한 남은 인수들을 rest parameter에 할당한다.
  • arguments는 배열이 아니기 때문에 Array method (forEach, map ...)를 사용하려면 번거로운 과정이 필요하다.
  • Rest parameter는 배열 인스턴스이기 떄문에 array method가 사용가능하다.
function func(...rest) {	
	rest.pop() // 정상 작동
}

func(1, 2, 3, 4);

function func2() {
    const args = arguments;
    const realArr = Array.prototype.slice.call(arguments);
    const realArr2 = Array.from(arguments);
    
    realArr.pop() // 정상 작동
    arguments.pop() // Error arguments.pop is not a function
}

func2(1, 2, 3, 4);

 

 

Rest parameter는 해체가 가능하다.

function func(...[a, b, c, d]) {
	return a + b + c + d;
]

console.log(func(1, 2, 3, 4)); // 10

위와 같이 배열이기 때문에 '구조 분해 할당'으로 해체가 가능하다.

 

Rest property (나머지 프로퍼티)

나머지 프로퍼티는 위의 파라미터에 대한 설명을 이해했다면 간단하다. 배열과 같이 객체를 destructure(구조 분해 할당) 할 때 가져갈 것은 가져가고 남은 프로퍼티들을 객체에 넣어서 통째로 가져온다. 코드를 보자

 

function func({ a, ...otherProps }) {
  console.log(a) // 1
  console.log(otherProps) // { b: 2, c: 3 }
}

func({ a: 1, b: 2, c: 3 })

function func2({ ...otherProps, a }) { // 파라미터와 마찬가지로 나머지 연산자는 마지막에 있어야 한다.
  console.log(a) // Error! Rest element must be last element
  console.log(otherProps)
}

func2({ a: 1, b: 2, c: 3 })

 

Spread syntax (전개 구문)

Spread 문법은 말 그대로 전개, 펼친다는 것이다. iterable한 배열이나 문자열 등과 같은 대상을 개별 요소로 분리해서 펼친다.

마찬가지로 ... 구문을 쓴다.

console.log(...[1, 2, 3]) // 1 2 3

console.log(...'apple') // a p p l e

 

함수의 호출에 사용할 때

function func(a, b, c) {
	console.log(a, b, c) // 1 2 3
}

// 전개 연산자는 배열을 각각 개별의 요소로 분리하여 a, b, c에 각각 할당 된다.
func(...[1, 2, 3]);

function func2(a, b, c, d, e) {
	console.log(a, b, c, d, e); // 1 2 3 4 5
}

// 이런 희한한 것도 가능하다.
func2(...[1, 2], 3, 4, ...[5])

 

배열에 사용할 때

const array = [4, 5, 6];

// 배열 안에다가 array의 원소를 전개한다. 배열에 배열을 이어 붙인 것과 같은 효과!
console.log([1, 2, 3, ...array]); // [1, 2, 3, 4, 5, 6];

// 빈 배열에다가 array를 전개하면 새로운 배열에 4, 5, 6 원소가 들어가게 된다. 
// 배열 전체를 slice 한 것과 같은 결과를 얻을 수 있다.
console.log([...array]) // [4, 5, 6]

// 원래 배열의 push 메서드를 사용할 때는 원소 각각을 인수로 넣어줘야한다.
array.push(1, 2, 3);
console.log(array) // [4, 5, 6, 1, 2, 3]

// 전개 연산자를 이럴 때 사용할 수 있다.
const array2 = [1, 2, 3];
array.push(...array2);
console.log(array) // [4, 5, 6, 1, 2, 3]

 

객체에 사용할 때

객체는 사실 iterable 하지 않기 때문에 Spead syntax를 적용할 수 없다. 하지만 Spead Property 일땐 얘기가 다르다.

const obj1 = { a: 1 };
const obj2 = { b: 2, c: 3 };

const obj3 = { ...obj1, ...obj2 };

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

 

key-value 쌍을 객체 내에 전개하는 것이 가능하다. 따라서 객체끼리 합치거나, 새로운 객체에 이전 객체의 내용을 옮길 때 매우 쉽게 옮길 수 있다. 

 

Spread 구문을 쓸 때 주의할 점이 있다. 바로 함수 호출에 사용할 때 함수의 인자 갯수에 대한 주의사항인데, 자바스크립트 코어 엔진에서는 65536개가 한계이다. 이는 엔진마다 다르기 때문에 정해진 부분이 없다. 대략적으로 몇만개 이상의 인자를 spread 할 때는 주의해야한다. 이 부분에 대해서는 apply 문서를 보면 좋을 듯하다.

 

Rest syntax와 Spread syntax를 정리해보면

 

속성 Rest(나머지) Spread(전개)
용도 여러 엘리먼트를 수집하며 이를 하나의 엘리먼트로 '압축' 전개는 iterable한 데이터를 그 엘리먼트로 '확장' 
사용법 필요한 데이터에 이름을 붙이고 남은 것들에 대해 (a, b, ...name) 으로 압축한다. 다른 데이터에다가 ... 구문으로 데이터를 확장한다.
[1, 2, ...[3, 4, 5]]
프로퍼티 객체 구조분해 할당을 할 때 ({ a, b, ...otherProps }) 와 같이 otherProps에 Rest property를 저장한다. 객체에다가 다른 객체를 확장 할 때 { a: 1, ...obj2 } 처럼 Spread Property를 전개하여 확장한다.

 

틀린 부분이 있다면 지적 부탁드립니다.

 

참조

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

 

전개 구문 - JavaScript | MDN

전개 구문을 사용하면 배열이나 문자열과 같이 반복 가능한 문자를 0개 이상의 인수 (함수로 호출할 경우) 또는 요소 (배열 리터럴의 경우)로 확장하여, 0개 이상의 키-값의 쌍으로 객체로 확장시

developer.mozilla.org

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

 

Rest 파라미터 - JavaScript | MDN

Rest 파라미터 구문은 정해지지 않은 수(an indefinite number, 부정수) 인수를 배열로 나타낼 수 있게 합니다.

developer.mozilla.org

https://poiemaweb.com/es6-extended-parameter-handling

 

Extended Parameter Handling | PoiemaWeb

Rest 파라미터는 Spread 문법(...)을 사용하여 매개변수를 작성한 형태를 말한다. Rest 파라미터를 사용하면 인수를 함수 내부에서 배열로 전달받을 수 있다.

poiemaweb.com