Spread Operator (전개 구문)

DatQueue·2022년 5월 3일
1

포스팅 시작에 앞서 ...

지난 포스팅에서 우리는 apply, call, bind에 관해 알아보았다. 이러한 메서드를 사용하여 배열, 문자열, 객체 등 Iterable Object ( 반복 가능한 객체 )를 개별 요소로 분리할 수 있었다. 물론 apply, call, bind를 사용함으로써 그러한 효과를 낼 수 있지만 지금부터 얘기하고자하는 이 스프레드 연산자는 위 메서드보다 더 간결하고 좋은 효과를 낼 수 있다는 점에서 알 필요가 있고, ES6에 추가된 새로운 내용인 만큼 앞으로도 더욱 많이 사용될 것으로 생각된다.

⪧ MDN에서 정의하는 전개 구문

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

1. 함수 호출

myFunction(...iterableObj);

💨 apply() 대체

일반적으로 배열의 엘리먼트를 함수의 인수로 사용하고자 할때 Function.prototype.apply() 를 사용하였다. (자세한 내용은 이전 포스팅 참고)

function myFunction(x, y, z) {
  const addNum = x + y + z;
  console.log(addNum);    // 3 출력
}
const args = [0, 1, 2];
myFunction.apply(null, args);

다음은 간단하게 myFunction의 인수 x , y , z 를 더해 addNum을 호출하는 함수이다. 이 때 인수 x , y , z의 값을 apply메서드를 이용해 args배열로 부터 받아왔다. 이전 포스팅에서 다룬 내용이다.

그렇다면 이것을 Spread Operator를 이용해 작성해보자.

function myFunction(x, y, z) {
  const addNum = x + y + z;
  console.log(addNum);
}
const args = [0, 1, 2]; 
myFunction(...args);    // 나머지 매개변수 이용

가장 위의 함수호출 구문에 의거해서 작성한 코드이다. apply를 사용한 것 보다 훨씬 간결한 작성법이라 할 수 있다.

function orderNum(x, y, z) {
  const addNum = [x, y, z];
  console.log(addNum);
}
orderNum(...[1, 2, 3]);   // 변수 설정없이 바로 배열 대입

위 코드같이 args라는 인수로 넘겨줄 단일 배열 요소를 만들지 않고 바로 배열형태로 넣는 것 또한 가능하다.

💨 함수 인자값으로 사용 (reduce 이용)

함수를 실행하는 부분에 스프레드 연산자를 넣는 경우도 ( 앞전 코드 ) 있지만 함수 인자값에서 바로 스프레드 연산자를 사용할 수도 있다.

reduce 함수를 이용해 예제를 확인해보자.

function sum(...theArgs) {  // 함수 인자로써 spread operator 사용
  return theArgs.reduce((prev, curr) => {
    return prev + curr;
  });
}

const sum1 = sum(1, 2, 3);
const sum2 = sum(1, 2, 3, 4);
console.log(sum1, sum2);  // 6 , 10

💨 new 에 사용

new를 사용해 생성자를 호출 할 때, 배열과 apply직접 사용하는 것은 불가하였다. 하지만, 전개 구문 덕분에 배열을 new와 함께 쉽게 사용할 수 있게 되었다.

이전 포스팅에서 applynew구문에 적용시킨 적은 있었지만 그것은 함수의 this를 받아오기 위한 수단이었고 배열자체에 직접 apply를 사용할 수는 없었다.

다음 간단한 예시를 보자.

const date = [2022, 4, 3];

const makeDate = new Date(...date);
console.log(makeDate);

new생성자 인수로 date 배열의 각 단일요소들을 전개 구문을 통해 받아 올 수 있다. 결과를 확인해보면

잘 출력되는 것을 알 수 있다.

이것을 apply를 사용해 코드를 작성한다면 어떻게 될까?

function infoDate(year, month, day) {
  this.year = year;
  this.month = month;
  this.day = day;
}

function mkDate(year, month, day) {
  infoDate.apply(this, [year, month, day]);
  return new Date(year, month, day);
}

const addDate = mkDate(2022, 4, 3);
console.log(addDate);

더 쉽게 짤 수 도 있지만 이렇게 new Date를 실행할 함수가 다른 함수로 부터 apply를 이용해 this를 받아오고 최종적으로 새로운 변수를 통해 생성하는 식으로 만들 것이다.

2. 배열 리터럴에서의 전개

💨 배열 병합

전개 구문 없이, 기존의 배열을 결합하는 데 있어서 이전 포스팅에서도 언급하였지만 우리는 push, concat 등의 메서드를 사용하였다.
우리는 Es6부터 나온 이 Spread operator를 이용해 좀더 깔끔한 배열 병합을 가능하게 할 수 있다.

다음 아주 간단한 예시를 통해 알 수 있다.

const numOne = [1, 2, 3];
const numTwo = [...numOne, 4, 5, 6];
console.log(numTwo);  // [1,2,3,4,5,6]

💨 배열 복사

Javascript에서 배열을 새로운 변수에 할당하는 경우 새로운 배열은 기존 배열을 참조한다. 따라서 새로운 배열을 변경하는 경우 원본 배열 역시 변경된다.

다음 간단한 예시를 보자.

const arrOne = ["apple", "banana"];
const arrTwo = arrOne;

arrTwo.push("peach");
console.log(arrTwo); // ['apple', 'banana', 'peach']
console.log(arrOne); // ['apple', 'banana', 'peach']

arrTwo는 예상대로 기존 배열에 peach라는 요소가 추가된 배열이 출력되었지만 arrOne또한 arrTwo와 동일하게 출력되었다. arrTwoarrOne을 참조하고, 그 후 arrTwo를 수정하면서 기존의 참조된 arrOne배열또한 변경된 것을 확인할 수 있다.

이런 형태의 배열 참조를 원하지 않고, 배열 복사를 위해서는 우린 기존에 slice 또는 Es5 이후 나온 map을 이용하여 배열 복사를 진행하였다.

⨠ 먼저, slice를 이용한 배열 복사이다.

코드를 통해 확인해보자.

const students1 = ["John", "Mike"];
const students2 = students1.slice();  //slice 이용

students2.push("Andy");
console.log(students2); // ['John', 'Mike', 'Andy']
console.log(students1); // ['John', 'Mike']   : 기존 배열 그대로 유지

students2 를 변경하여도 배열 복사를 하였기 때문에 기존 students 배열은 변경없이 유지된다.

⨠ 다음으로 map을 이용한 배열 복사이다.

const students1 = ["John", "Ann"];
const students2 = students1.map((item) => item);   // map 사용
students2.push("Judy");
console.log(students2);  // ['John', 'Ann', 'Judy']
console.log(students1);  // ['John', 'Ann']

마찬가지로 기존 students 배열에는 변화가 없다.

⨠ 그럼 이제 Es6의 Spread 연산자를 사용해보자.

const students1 = ["John", "Ann"];
const students2 = [...students1];  // Spread operator 이용

students2.push("Judy");
console.log(students2);  // ['John', 'Ann', 'Judy']
console.log(students1);  // ['John', 'Ann']

확실히 코드가 간결해진 것을 확인할 수 있다.

3. 객체 리터럴에서의 전개

얕은 복제 (prototype 제외) 또는 객체의 병합은 이제 Object.assign() 보다 더 짧은 문법을 사용해 가능해졌다.

💨 Object.assign() 을 이용한 객체 복사

⨠ 구문

Object.assign(target, ...sources)

간단한 코드를 통해 확인해보자.

const obj = { a: 1 };
const copy = Object.assign({}, obj);  // target 을 빈배열 { }로 한 case
console.log(copy); // { a: 1 }

💨 Spread operator 를 이용한 객체 복사

다음 간단한 코드를 통해 구문을 살펴보자.

let currentState = { name: "John", job: "developer" };
currentState = { ...currentState, age: 30 };

console.log(currentState);  // {name: 'John', job: 'developer', age: 30}

다음과 같이 객체의 복사는 { ...obj } 를 이용하면 된다.

참고 :
Spread 문법은 배열을 복사할 때 1레벨 깊이에서 효과적으로 동작한다. 그러므로, 위 예제가 아닌 만약 다차원 배열을 복사하는것에는 적합하지 않을 수 있다. 이것은 Object.assign( ) 또한 마찬가지이다.

마무리 ...

이번 포스팅은 MDN 공식문서의 정의와 예제들을 참조해 작성해보았다. Es6로의 변화 이후 꼭 알면 좋을 기법이라 생각해 포스팅을 작성해보았고 유연하게 잘 사용할 필요가 있다고 생각한다.

profile
You better cool it off before you burn it out / 티스토리(Kotlin, Android): https://nemoo-dev.tistory.com

0개의 댓글