[모던 자바스크립트 Deep Dive] - (6) 스프레드 문법

JIN·2024년 10월 24일
post-thumbnail

... ?

자바스크립트를 사용하다 보면 스프레드 문법(...)을 한 번쯤은 접해보셨을 텐데요. 스프레드 문법은 어떤 특징을 가지고 있고, 어떻게 사용하는지에 대해 자세히 정리해보겠습니다.

스프레드 문법(=전개 문법)은 하나로 묶여 있는 여러 값의 집합을 개별적인 값들의 목록으로 "펼쳐주는" 문법입니다. Array(배열), String(문자열), Map, Set, DOM 컬렉션, arguments와 같이 반복 가능한 이터러블에서 사용할 수 있습니다.

이터러블(iterable) = 배열이나 문자열처럼 내부 요소를 하나씩 순회할 수 있는 객체

"펼치다"라는 뜻의 spread라는 이름답게, ES6에 도입된 이 문법은 다양한 값들을 넓게 펼쳐 목록으로 사용할 수 있는 편리함을 제공합니다.

console.log(...[1, 2, 3]); // 1 2 3
console.log(...'hello'); // h e l l o
console.log(...new Map([['a', '1'], ['b', '2']])) // [ 'a', '1' ] [ 'b', '2' ]
console.log(...new Set([1, 2, 3]) // 1 2 3


console.log(...{a: 1, b: 2});
// TypeError: Spread syntax requires ...iterable[Symbol.iterator] to be a function

이처럼 스프레드 문법을 사용하면 값들의 목록을 개별적으로 펼칠 수 있습니다. 하지만 주의할 점은 스프레드 문법결과하나의 값이 아니라는 점입니다. 스프레드 문법은 값 자체를 생성하지 않고 순회 가능한 목록을 각각의 요소로 풀어줍니다. 그래서 결과 자체를 하나의 변수에 할당할 수 없죠.

비유하자면, 라면 가게에서 면을 펼쳐 나열할 수는 있지만, 각각의 면을 개별 요리로 바로 사용할 수는 없는 것과 같습니다.

const noodles = ['면', '면', '면', '면', '면'];

console.log(...noodles); // 면 면 면 면 면

const noodles2 = ...['면', '면', '면', '면', '면']; // SyntaxError: Unexpected token '...'

따라서 스프레드 문법의 결과물은 함수 호출에서 인수 목록, 배열의 요소 목록, 객체의 프로퍼티 목록 등, 목록을 사용하는 문맥에서만 유효합니다.

함수 호출문에서 인수 목록 사용하기

스프레드 문법은 배열을 개별 값들의 목록으로 만들어 함수의 인수 목록으로 전달할 때 사용이 가능합니다. 예를 들어, Math.max 메서드는 인수로 전달된 여러 숫자 중 최대값을 반환합니다. 단일 배열을 직접 전달할 경우 최대값을 찾을 수 없어 NaN이 반환됩니다.

const num1 = 1;
const num2 = 2;
const numMax = Math.max(num1, num2)

console.log(numMax) // 2

const arr = [1,2,3];
const arrMax = Math.max(arr)

console.log(arrMax) // NaN

스프레드 문법을 쓰기 이전에는 Function.prototype.apply를 사용해야했지만, 스프레드 문법을 사용하면 훨씬 더 간결하게 최대값을 반환할 수 있습니다.

const arr = [1,2,3];

const applyMax = Math.max.apply(null, arr); 
console.log(applyMax); // 3

const spreadMax = Math.max(...arr);
console.log(spreadMax); // 3

단, 스프레드 문법은 Rest 파라미터와 형태가 동일하여 혼동할 수 있어 주의가 필요합니다.

Rest 파라미터?
함수에 전달된 인수들의 목록을 배열로 전달받기 위해 매개변수 이름 앞에 ...를 붙이는 것
스프레드 문법은 여러개의 값이 하나로 뭉쳐있는 배열과 같은 이터러블을 펼쳐서 개별적인 값들의 목록을 만드는 것이라면, Rest는 목록을 다시 배열로 만드는 것으로 스프레드 문법과는 완전히 반대되는 개념입니다.

const arr = [1,2,3]
function restTest(...rest) { // Rest 파라미터 : 1, 2, 3 => [1,2,3]
    console.log(rest) // [1,2,3]
}
restTest(...arr) // 스프레드 문법 : [1,2,3] => 1, 2, 3

배열 리터럴 내부에서 사용하기

1. 배열 합치기

스프레드 문법 이전에 2개의 배열을 1개의 배열로 결합하고 싶을때는 배열 리터럴 만으로는 불가능하고 concat 메서드를 사용해야 했습니다.

const arr = [1,2].concat([3,4]);

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

하지만 스프레드 문법을 사용하면 별도의 메서드를 사용하지 않고 배열 리터럴 만으로 2개의 배열을 1개로 결합이 가능합니다.

const arr = [...[1,2], ...[3,4]]
console.log(arr); // [ 1, 2, 3, 4 ]

이처럼 스프레드 문법을 사용하면 [1,2]를 1,2로 펼치고 - [3,4]를 3,4로 펼치고 빈배열에 추가하여 arr가 만들어지게 됩니다.

2. 배열 중간에 요소 추가 및 삭제

스프레드 문법 이전에 어떤 배열 중간에 다른 배열의 요소들을 추가하거나 삭제하려면 splice 메서드를 사용해야했습니다.

const arr1 = [1,4];
const arr2 = [2,3];

arr1.splice(1, 0, arr2) 

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

하지만 splice 메서드를 사용해서 추가하면 배열 자체가 추가되어서 [1,2,3,4]가 아닌 [ 1, [ 2, 3 ], 4 ]로 나오게 됩니다.

만약 [ 1, 2, 3, 4 ]로 출력하고 싶을때는 앞서 사용한 concat메서드를 추가로 또 사용해야한다는 번거로움이 있습니다.

const arr1 = [1,4];
const arr2 = [2,3];

Array.prototype.splice.apply(arr1, [1,0].concat(arr2));;

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

하지만, 스프레드 문법을 사용하면 간결하게 표현이 가능합니다.

const arr1 = [1,4];
const arr2 = [2,3];

arr1.splice(1, 0, ..arr2) 

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

3. 배열 복사

스프레드 문법 이전에 배열을 복사하려면 slice 메서드를 사용해야 했습니다. 하지만 스프레드 문법을 사용하면 메서드를 쓰지 않고 간편하게 복사가 가능합니다.

const origin = [1,2];
const sliceCopy = origin.slice();

const spreadCopy = [...origin];

console.log(sliceCopy) // [1,2]
console.log(spreadCopy) // [1,2]
consol.log(origin === spreadCopy) // false

이렇게 복사를 하면 원본 배열의 각 요소를 얕은 복사하여 새로운 복사본을 생성하게 됩니다. 복사된 배열을 참조값이 다르기에 일치 연산자 사용시 false를 출력합니다.

4. 이터러블을 배열로 변환

스프레드 문법 이전에 이터러블(배열, 문자열, arguments)을 배열로 변환하려면 Function.prototype.apply 또는 Function.prototype.call 메서드를 사용하여 slice 메서드를 추가호 호출해야 했습니다.

function sum() {
    // 이터러블 이면서 유사 배열 객체인 argments를 배열로 변환
    const args = Array.prototype.slice.call(arguments);

    return args.reduce(function(pre, cur) {
        return pre + cur
    }, 0)

}

console.log(sum(1,2,3)) // 6

이역시 스프레드 문법을 사용하면 조금더 쉽게 이터러블을 배열로 변환할 수 있습니다.

function sum() {
//     // 이터러블 이면서 유사 배열 객체인 argments를 배열로 변환
return [...arguments].reduce((pre, cur) => pre + cur, 0)

}

console.log(sum(1,2,3)) // 6

객체 리터럴 내부에서 스프레드 문법을 사용한다면?

기존에는 객체 리터럴에서 스프레드 문법을 사용한다면, 객체는 이터러블이 아니기 때문에 사용이 불가능 했지만, ES2018에 추가된 객체 스프레드 프로퍼티 덕분에 스프레드 문법과 같이 요소들을 펼쳐 객체 병합, 복사, 업데이트 등을 편리하게 처리할 수 있습니다.

객체 스프레드 프로퍼티는 이터러블에서 사용하는 스프레드 문법과 기본 원리는 동일하나, 객체에서 사용할때는 스프레드 프로퍼티라는 별도의 명칭으로 부르고 있습니다.

1. 객체 결합 및 속성 추가

스프레드 프로퍼티을 사용하면 Object.assign 메서드 없이도 여러 객체를 간편하게 결합하거나 속성을 추가할 수 있습니다.

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

// 기존: Object.assign 사용
const combinedObj = Object.assign({}, obj1, obj2);
console.log(combinedObj); // { a: 1, b: 2, c: 3, d: 4 }

// 객체 스프레드 프로퍼티 사용
const spreadCombinedObj = { ...obj1, ...obj2 };
console.log(spreadCombinedObj); // { a: 1, b: 2, c: 3, d: 4 }

또한, 객체의 특정 속성을 변경하거나 추가하려면 간편하게 새로운 속성을 스프레드 프로퍼티과 함께 사용할 수 있습니다.

const user = { name: 'Alice', age: 25 };
const updatedUser = { ...user, age: 26, city: 'Seoul' };

console.log(updatedUser); // { name: 'Alice', age: 26, city: 'Seoul' }

2. 객체 복사

객체의 얕은 복사를 위해 스프레드 프로퍼티을 사용할 수도 있습니다. 이때, 얕은 복사이므로 중첩된 객체나 배열 요소는 원본 객체와 참조를 공유하므로 주의가 필요합니다.

const original = { x: 1, y: { z: 2 } };
const copy = { ...original };

console.log(copy); // { x: 1, y: { z: 2 } }
console.log(copy === original); // false (새로운 객체)
console.log(copy.y === original.y); // true (중첩 객체는 동일 참조)

스프레드 프로퍼티는 객체의 모든 프로퍼티를 개별적으로 펼쳐 객체 리터럴에 추가하는 방식으로 새로운 객체를 반환하므로, 원본 객체를 변경하지 않고 불변성을 유지하면서 객체를 결합하는 데 유용합니다.

어떻게 활용할 수 있을까?

1. 함수 내부 기본값 설정

스프레드 프로퍼티를 활용하여 함수 매개변수에서 기본값을 설정할 수 있습니다. 이 방식은 특히 객체 구조 분해 할당과 함께 유용하게 사용됩니다.

function createUser({ name, age, city = 'Unknown' }) {
    return { name, age, city };
}

const user = createUser({ name: 'Alice', age: 25 });
console.log(user); // { name: 'Alice', age: 25, city: 'Unknown' }

2. 배열 혹은 객체에서 특정 요소 제거

스프레드 문법을 사용해 배열이나 객체에서 특정 요소를 제거하고 새로운 객체나 배열을 만들 경우에도 유용하게 사용이 가능합니다.

// 배열에서 요소 제거
const numbers = [1, 2, 3, 4];
const newNumbers = [...numbers.slice(0, 2), ...numbers.slice(3)];
console.log(newNumbers); // [1, 2, 4]

// 객체에서 속성 제거
const person = { name: 'Bob', age: 30, city: 'Seoul' };
const { city, ...rest } = person;
console.log(rest); // { name: 'Bob', age: 30 }

⭐️ 마무리

정리를 하다보니 스프레드 문법을 잘 이해하고 있으면 배열이나 객체를 다루는 다양한 상황에서 코드를 훨씬 간결하고 읽기 쉽게 작성할 수 있어 매우 유용한 문법이라 느껴졌습니다. 스프레드 문법이 등장한 ES6 이후에 자바스크립트를 배우기 시작해서 다행인것 같습니다 😮‍💨


출처: 모던 자바스크립트 Deep Dive 35장 스프레드 문법 (627p ~ 635p)


profile
MAXIMUM EFFORT 🙃

0개의 댓글