모던자바스크립트 Deep Dive

[35장] 스프레드 문

Ama_grammer 2025. 1. 16. 16:58

- 목표

  1. spread syntax
  2. spread syntax vs rest parameter
  3. spread syntax 사용

- spread syntax

하나로 뭉쳐 있는 여러 값들의 집합을 펼쳐서(전개, 분산하여, spread) 개별적인 값들의 목록을 만든다.
es6 이전 Function.prototype.apply() 와 같은 역할을 한다.

 

스프레드 문법을 사용할 수 있는 대상은 Array, String, Map, Set, DOM collection(NodeList, HTMLCollection), arguments 와 같이 for...of 반복문으로 순회 가능한 이터러블 속성을 갖은 데이터에 한정된다.

 

※ 쉼표로 구분한 값의 목록을 사용하는 문맥

- 함수 호출문의 인수 목록

- 배열 리터럴의 요소 목록

- 객체 리터럴의 프로퍼티 목록


- spread  vs rest 

spread syntax 는 rest parameter 와 형태가 동일하여 주의해야한다.

spread syntax 

여러 개의 값이 하나로 뭉쳐 있는 배열과 같은 이터러블을 펼쳐서 개별적인 값들의 목록으로 만든다.

const add = (a, b) => a + b;
const arr = [3, 4];
const result = add(...arr);
// spread syntax 는 arr 배열의 [3, 4] 를 분리하여 add 함수에 매개변수 a 와 b 에
// 인덱스가 작은 순으로 구조분해할당된다.
// add(...arr) === add(3,4)
console.log(result);
// output : 7

rest parameter

함수에 전달된 인수들의 목록을 배열로 전달받기 위해 매개변수 이름 앞에 ... 을 붙이는 것이다.

const add = (...rest) => rest.reduce((acc,cur) => acc + cur, 0);
const result = add(1,2,3,4,5,6,7,8,9);
// rest parameter 는 인수들의 목록(1,2,3,4,5,6,7,8,9)를 매개변수 rest 에 할당하는데
// 이것을 그대로 받으면 rest 에는 첫번째 인수 '1' 만 저장되고 rest 가 애초에 순회가능한 배열의 형태가
// 아니기 때문에 오류가 발생한다.
// 그렇기 때문에 rest parameter 를 사용하여 인수 목록을 배열로 전달받아 함수의 동작을 처리한다.
console.log(result);
// output : 45

정리

spread 문법배열을 풀어주는 동작이라고 생각하면 되고,

rest parameter 는 배열이 아닌 인수들의 모음을 배열로 변환해주는 서로 반대의 기능을 갖고 있다.

 


- spread  syntax 사용

스프레드 문법의 등장으로 ES5 에서 사용하던 기존의 방식보다 더 간결하고 가독성 좋게 표현할 수 있게 됐다.

1. 함수 호출문의 인수 목록에서 사용

Math.max()

 

const numbers = [1, 4, 23, 3, 2, 5, 7];
const max = Math.max(numbers);
console.log(max);
// output : NaN

Math.max 의 경로를 확인해보면

/**
* Returns the larger of a set of supplied numeric expressions.
* @param values Numeric expressions to be evaluated.
*/
max(...values: number[]): number;

rest 파라미터가 포함되어 있다.

그렇다는 것은 배열을 max 함수의 인수로 전달되는 값이 배열형태가 아닌 수들의 집합으로 그 수를 배열로 만들어주는게 rest 파라미터의 역할이기 때문에 max 에 numbers 를 인수로 전달하면 에러가 발생한다. 이것을 해결하기 위해 사용하는 것이 배열을 펼쳐서 개별적인 값들의 목록으로 변경해주는 spread syntax 를 사용해야한다.

const numbers = [1, 4, 23, 3, 2, 5, 7];
const max = Math.max(...numbers);
console.log(max);
// output : 23

2. 배열 리터럴 내부에서 사용

ES5 - concat

// ES5
var numbers = [1, 2, 3];
var additionalNums = [4, 5, 6];
var total = numbers.concat(additionalNums);
console.log(total);
// output : [1, 2, 3, 4, 5, 6]

 

ES6 - spread

// ES6+
const numbers = [1, 2, 3];
const additionalNums = [4, 5, 6];
const total = [...numbers, ...additionalNums];
console.log(total);
// output : [1, 2, 3, 4, 5, 6]

 

기존의 concat 문법도 충분히 좋은 문법이지만, 스프레드 문법을 사용하면 보다 더 직관적인 풀이가 가능하다.

 

ES5 - splice

// ES5
var numbers = [1, 2, 3];
var additionalNums = [4, 5, 6];
var total = numbers.splice(1, 0, additionalNums);
console.log(total);

위의 코드로 numbers 의 첫번째 배열이후에 additionalNums 를 추가하는 것이 기대된다.

결과를 예상해보자.

더보기

// output : [1, [4, 5, 6], 2, 3]

의도한 바와 다르게 배열안에 배열의 형태인 중첩 배열이 출력된다.

ES5 사양에서 [1, 4, 5, 6, 2, 3] 의 출력을 구현하려면 아래와 같이 별도의 처리가 필요하다.

// ES5
var numbers = [1, 2, 3];
var additionalNums = [4, 5, 6];
Array.prototype.splice.apply(numbers, [1, 0].concat(additionalNums));
console.log(numbers);

 

ES5 사양으로도 충분히 배열의 splice 기능을 원하는 방향으로 구현할 수 있다.

하지만, 왜 spread syntax 를 사용하는 것일까?

우선 spread syntax 가 적용된 코드를 보고 비교해보자.

 

ES6 - spread syntax

// ES6
const numbers = [1, 2, 3];
const additionalNums = [4, 5, 6];
numbers.splice(1, 0, ...additionalNums);
console.log(numbers);

 

한눈에 보기에도 spread 문법을 사용한 코드가 ES5 의 프로토타입을 사용하여 구현한코드보다 간결하고 직관적이다.

하지만, 그것 이외에도 spread 문법을 사용하는 이유중 하나는 prototype 사용 지양의 이유가 있다.

우선 안전성의 문제가 있다.

프로토타입 메서드를 직접 호출하면, 해당 메서드가 변조된 경우 문제가 될 수 있어 prototype 직접 호출 방식을 지양해야한다.

다음으로는 상대적이긴 하지만 성능의 문제가 있다.

ES5 방식은 apply 를 사용하여 배열 형태의 두 번째 인수를 처리해야 하므로, 추가적인 연산 및 메모리 할당이 발생한다.

반면에 스프레드 문법은 최신 자바스크립트 엔진에서 최적화되어 효율적이다.

위와 같은 이유로 특별한 상황을 제외하고는 spread 를 사용하는 것이 더 효과적이다.

 

3. 객체 리터럴 내부에서 사용

객체 복사 (얕은 복사)

const obj = { x: 100, y: 200, z: 300};
const objCopy = { ...obj };
console.log(objCopy);
// output : { x: 100, y: 200, z: 300 }
console.log(obj == objCopy);
// output : false;
console.log(obj === objCopy);
// output : false;

 

assign - 객체 병합

const mergeObj = Object.assign({}, { x: 100, y: 200, z: 300 }, { y: 20, z: 40, q: 60 });
console.log(mergeObj);
// output : { x: 100, y: 20, z: 40, q: 60 }

 

spread - 객체 병합

const obj1 = { x: 100, y: 200, z: 300};
const obj2 = { y: 20, z: 40, q: 60 };
const mergeObj = {...obj1 , ...obj2 };
console.log(mergeObj);
// output : { x: 100, y: 20, z: 40, q: 60 }

 

객체를 병합할 때 위와 같이 2가지의 방법이 있는데 둘 다 동일하게 객체를 병합하는 동작을 한다.

간결하고 가독성이 높은 코드를 선호한다면 spread syntax 를 사용하는 것이 좋고

호환성을 중요시하거나 특정한 이유로 Polyfill 이 필요한 환경에서는 Object.assign 을 선택하는것이 좋다.

하지만, 가능하다면 spread syntax 를 사용하는 것이 개발 유지 보수 및 가독성 개선 측면에서 이점이 있기에 spraed syntax 를 사용을 지향하는 것이 좋을것이다.


🔥 배운점

평소에 spread syntax 와 rest parameter 가 있다는 것은 알았는데 둘 다 (...) 연산을 한다는 점에서 동일한 기능인가 생각하고 구체적인 탐구를 진행하지 않았었는데, 모던 자바스크립트 Deep dive 책을 학습하면서 이 두개가 어떻게 다른지 알게되었다.

항상 반성하는 부분이지만, 모르는 내용에 관대하지 않고 구체적으로 해결하는 습관을 길러 꼼꼼한 개발자로 성장해나가야겠다.

 

- 참고

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

 

나머지 매개변수 - JavaScript | MDN

나머지 매개변수 구문을 사용하면 함수가 정해지지 않은 수의 매개변수를 배열로 받을 수 있습니다. JavaScript에서 가변항 함수를 표현할 때 사용합니다.

developer.mozilla.org