Review - ES6 문법(2)

Verba volant, scripta manent·2021년 1월 28일
0

어제 HA시험을 시간에 맞춰서 마치고 ES6문법 중 arrow function에 대해 복습했다.
이번에는 spread syntax과 구조 분해 할당에 대해 복습하려고 한다.

Spread syntax란?

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

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

요점정리

Rest parameter는 인수들의 목록을 배열로 전달받는 것이다.
Spread syntax는 이러한 Rest parameter를 펼쳐서 개별적인 값들의 목록을 만드는 것이다.

Spread syntax를 사용할 수 있는 대상

Array, String, Map, Set, DOM, arguments처럼 for..of 문을 쓸 수 있는 것들

ex)
console.log(...[1,2]); // 1 2

console.log(...'Yeonlisa'); // Y e o n l i s a

console.log(...new Map([['a','1'], ['b','2']])); // ['a', '1'], ['b', '2']

console.log(...new Set([1, 2])); // 1 2

이터러블이 아닌 일반 object는 대상이 될 수 없으며, Spread syntax 결과물 또한 값으로 사용할 수 없다.

Spread syntax를 사용할 수 있는 경우

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

요소들의 집합인 배열을 펼쳐서 개별적인 값들의 목록으로 만들고 함수의 인수 목록으로 전달해야 하는 경우가 있다.
Spread syntax가 나타나기 전에는 이럴 경우 Function.prototype.apply를 사용했다고 한다.

ex)
ES5)
var arr = [1,2,3,4]
// apply 함수의 두번째 인수(배열)는 apply 함수가 호출하는 함수의 인수 목록이다.
// 배열이 펼쳐져서 인수로 전달되는 효과가 있다.
var max = Math.max.apply(null, arr); // 4

ES6)
let arr = [1,2,3,4]
// Spread syntax를 사용하여 배열을 1,2,3,4로 펼쳐서 Math.max에 전달한다.
// Math.max(...[1,2,3,4]) === Math.max(1,2,3,4)
let max = Math.max(...arr); // 4

참 간편하고 깔끔하다^^

2. 배열 리터럴 내부에서 사용하는 경우

1. concat

Spread syntax가 나타나기 전에는 2개의 배열을 1개의 배열로 결합하고 싶은 경우 concat 메서드를 사용해야 했다.

ex)
ES5)
var arr = [1,2,3].concat([4,5,6]);
// 배열 [1,2,3]에 [4,5,6]을 concat해야 했다.
console.log(arr); // [1,2,3,4,5,6]

ES6)
let arr = [...[1,2,3],...[4,5,6]];
// 배열 안에서 Spread syntax를 사용하여 배열을 1,2,3,4,5,6으로 펼친다.
console.log(arr); // [1,2,3,4,5,6]

2. splice

Spread syntax가 나타나기 전에는 어떤 배열의 중간에 다른 배열의 요소들을 추가하거나 삭제할때면 splice 메서드를 사용했다. 이때, 두 번째 인수(배열)는 apply 메서드가 호출하는 함수에 해체되어 전달되고, 세 번째 인수로 배열을 전달하면 배열 자체가 추가된다.

ex)
ES5)
var arr1 = [1,4];
var arr2 = [2,3];
// apply 메서드의 두 번째 인수(배열)는 apply 메서드가 호출한 splice 메서드의 인수 목록이라고 했다.
// apply 메서드의 두 번째 인수 [1,0].concat(arr2)는 [1,0,2,3]으로 평가된다.
// 따라서 [1,0,2,3]이 해체되어 전달된다.
// 즉, arr1[1]부터 0개의 요소를 제거하고 그 자리에 새로운 요소(2,3)을 삽입한다.
Array.prototype.splice.apply(arr1, [1,0].concat(arr2));
console.log(arr1); // [1,2,3,4]

ES6)
let arr1 = [1,4];
let arr2 = [2,3];
arr1.splice(1, 0, ...arr2);
console.log(arr1); // [1,2,3,4]

3. 배열 복사

Spread syntax가 나타나기 전에는 배열을 복사하려면 slice 메서드를 사용했다.

ex)
ES5)
var origin = [1,2,3];
var copy = origin.slice();

console.log(copy); // [1,2,3]
console.log(copy === origin); // false

ES6)
let origin = [1,2,3];
let copy = [...origin];

console.log(copy); // [1,2,3]
console.log(copy === origin); // false

이때 copy는 원본 배열의 각 요소를 얕은 복사(shallow copy)하여 만들어진 새로운 복사본이다.
주소값까지는 복사를 하지 않기 때문에 copy === origin 이 false가 나오는 것이다.

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

Spread syntax가 나타나기 전에는 iterable을 배열로 변환하려면 Function.prototype.apply 또는 Function.prototype.call 메서드를 사용하여 slice 메서드를 호출해야 했다.

ex)
ES5)
function sum() {
  // iterable이면서 유사 배열 객체인 arguments를 배열로 변환한다.
  var args = Array.prototype.slice.call(arguments);
  
  return args.reduce(function (pre, cur) {
    return pre + cur;
  }, 0);
}

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

ES6)
방법1. Spread syntax 사용
function sun() {
  // iterable이면서 유사 배열 객체인 arguments를 배열로 변환한다.
  return [...arguments].reduce((pre, cur) => pre + cur, 0);
}

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

방법2. Rest parameter 사용
let sum = (...args) => args.reduce((pre, cur) => pre + cur, 0);
// Rest parameter인 args는 함수에 전달된 인수들의 목록을 배열로 전달받는다.

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

여기서 Spread syntax를 사용하면 좀 더 간편하게 iterable을 배열로 변환시킬 수 있다.
arguments 객체는 iterable이면서 유사 배열 객체이므로 Spread syntax의 문법 대상이 될 수 있다.

Rest parameter를 사용하면 Spread syntax보다 더 간결하다ㅋㅋㅋㅋ

★ 여기서 유의할 점은! iterable이 아닌 유사 배열 객체는 Spread syntax의 문법 대상이 될 수 없다는 것이다.

ex)
// iterable이 아닌 유사 배열 객체 
let arrayLike = {
  0: 1,
  1: 2,
  2: 3,
  3: 4,
  length: 4
}

let arr = Array.prototype.slice.call(arrayLike); // [1,2,3,4]
console.log(Array.isArray(arr)); // true

// Spread syntax 사용할 수 없는 예시
let arrayLike = {
  0: 1,
  1: 2,
  2: 3,
  3: 4,
  length: 4
}

let arr = [...arrayLike]; // Uncaught TypeError: arrayLike is not iterable

// 방법 : Array.from 메소드를 사용한다.
Array.from(arrayLike); // [1, 2, 3, 4]

이처럼 iterable이 아닌 유사 배열 객체를 배열로 변경하려면 Array.from 메서드를 사용하면 된다.
Array.from : 유사 배열 객체 또는 iterable을 배열로 변환한다.

3. 객체 리터럴 내부에서 사용하는 경우

Spread syntax의 대상은 iterable이지만, Spread property를 사용하면 일반 객체를 대상으로도 spread syntax를 사용할 수 있게 된다고 한다.

ex)
// Spread property
// 객체 복사(얕은 복사)
let obj = { x: 1, y: 2, z: 3 };
let copy = { ...obj };
console.log(copy); // {x: 1, y: 2, z: 3}
console.log(obj === copy); // false

// 객체 병합
let merged = { x: 1, y: 2, z: 3, ... { a: 4 }};
console.log(merged); // {x: 1, y: 2, z: 3, a: 4}

Spread property가 나타나기 전에는 Object.assign 메서드를 사용하여 여러 개의 객체 병합 또는 특정 프로퍼티의 추가 혹은 변경했다.

ex)
// 객체 병합. property가 중복되는 경우 뒤에 위치한 property가 우선권을 갖는다.
let merged = Object.assign({}, { x: 1, y: 2}, { y: 3, z: 4});
console.log(merged); // {x: 1, y: 3, z: 4}

// 특정 property 변경
let changed = Object.assign({}, { x: 1, y: 2}, { y: 10 });
console.log(changed); // {x: 1, y: 10}

// property 추가
let added = Object.assign({}, { x: 1, y: 2}, { z: 0 });
console.log(added); // {x: 1, y: 2, z: 0}

Spread property가 나타나면서 Object.assign 메서드를 대체할 수 있게 되었다.

ex)
// 객체 병합. property가 중복되는 경우 뒤에 위치한 property가 우선권을 갖는다.
let merged = { ...{ x: 1, y: 2}, ...{ y: 3, z: 4}};
console.log(merged); // {x: 1, y: 3, z: 4}

// 특정 property 변경
let changed = Object.assign({}, { x: 1, y: 2}, { y: 10 });
 // -> changed = { ... { x: 1, y: 2}, ...{ y: 10 });
console.log(changed); // {x: 1, y: 10}

// property 추가
let added = Object.assign({}, { x: 1, y: 2}, { z: 0 });
// -> added = { ... { x: 1, y: 2}, ...{ z: 0 });
console.log(added); // {x: 1, y: 2, z: 0}

구조 분해 할당이란?

구조화된 배열과 같은 iterable 또는 객체의 구조를 파괴하여 1개 이상의 변수에 개별적으로 할당하는 것을 뜻한다.
배열과 같은 iterable 또는 객체 리터럴에서 필요한 값만 추출하여 변수에 할당할 때 유용하다.

배열 구조 분해 할당

ES6의 배열 구조분해 할당은 배열의 각 요소를 배열로부터 추출하여 1개 이상의 변수에 할당한다.
이때의 배열 구조분해 할당의 대상(즉, 할당문의 우변)은 iterable이어야 하고, 할당 기준은 배열의 인덱스다.
즉, 순서대로 할당된다는 뜻이다.
배열 구조분해 할당은 배열과 같은 iterable에서 필요한 요소만 추출하여 변수에 할당하고 싶을 때 유용하다는 점이 있다.

ex)
ES5에서 구조 분해하여 1개 이상의 변수에 할당하는 방법)
var arr = [1,2,3,4];

var one = arr[0];
var two = arr[1];
var three = arr[2];
var four = arr[3];

console.log(one, two, three, four); // 1 2 3 4

ES6에서 구조 분해 할당하는 방법)
let arr = [1,2,3,4];

// 변수 one, two, three, four를 선언하고 배열 arr를 구조분해하여 할당한다.
// 이때의 할당 기준은 배열의 인덱스다. 즉, 순서대로 할당된다.
let [one, two, three, four] = arr;

console.log(one, two, three, four); // 1 2 3 4

방법

  1. 배열 구조분해 할당을 위해선 할당 연산자 왼쪽에 값을 할당받을 변수를 선언한다.
    이때 변수는 배열 리터럴 형태로 선언한다.
ex)
let [x, y, z] = [1, 2, 3];
  1. 이때 우변에 iterable을 할당하지 않으면 에러가 발생한다.
ex)
let [x, y, z]; // SyntaxError: Missing initializer in destructuring declaration
let [a, b, c] = {}; // TypeError: {} is not iterable
  1. 배열 구조분해 할당의 기준은 배열의 인덱스라고 했다. 즉, 순서대로 할당되는데 이때 변수의 개수와 iterable의 요소 개수가 반드시 일치할 필요는 없다.
ex)
let [a, b] = [1, 2];
console.log(a, b); // 1 2

let [c, d] = [1];
console.log(c, d); // 1 undefined

let [e, f] = [1, 2, 3];
console.log(e, f); // 1 2

let [g, , h] = [1, 2, 3];
console.log(g, h); // 1 3
  1. 배열 구조분해 할당을 위한 변수에 Rest parameter와 유사하게 Rest element ...을 사용할 수 있다.
    Rest element는 Rest parameter와 마찬가지로 반드시 마지막에 위치해야 한다.
ex)
// Rest element
let [x, ...y] = [1, 2, 3];
console.log(x, y); // 1 [2, 3];

객체 구조 분해 할당

ES5에서 객체의 각 property를 객체로부터 구조분해하여 변수에 할당하기 위해서는 property 키를 사용해야 했다.

ex)
ES5)
var user = { firstName: 'Lisa' , lastName: 'Yeon' };

var firstName = user.firstName;
var lastName = user.lastName;

console.log(firstName, lastName); // Lisa Yeon

ES6의 객체 구조분해 할당은 객체의 각 property를 객체로부터 추출하여 1개 이상의 변수에 할당한다.
이때 객체 구조분해 할당의 대상(할당문의 우변)은 객체여야 하며, 할당 기준은 property 키다.
즉, 순서는 의미가 없고 선언된 변수 이름과 property 키가 일치하면 할당되는 것이다.

ex)
ES6)
let user = { firstName: 'Lisa' , lastName: 'Yeon' };

// 변수 lastName, firstName을 선언하고 user 객체를 구조분해하여 할당한다.
// 이때 property 키를 기준으로 구조분해 할당이 이루어지며 순서는 의미가 없다.
let { lastName, firstName } = user;

console.log(firstName, lastName); // Lisa Yeon

방법

  1. 객체 구조분해 할당을 위해서는 할당 연산자 왼쪽에 property 값을 할당받을 변수를 선언해야 하는데 이때 변수를 객체 리터럴 형태로 선언한다.
ex)
let { lastName, firstName } = { firstName: 'Lisa' , lastName: 'Yeon' };
  1. 이때 우변에 객체 혹은 객체로 평가될 수 있는 표현식(String, Number, Array 등)을 할당하지 않으면 에러가 발생하게 된다.
ex)
let { lastName, firstName }; // SyntaxError: Missing initializer in destructuring declaration
let { lastName, firstName } = null; // TypeError: Cannot destructure property 'lastName' of 'null' as it is null.
  1. property 축약 표현을 통해 선언할 수도 있다.
ex)
let { lastName, firstName } = user;
// 위와 아래는 같은 표현이다.
let { lastName: lastName, firstName: firstName } = user;
  1. 객체의 property 키와 다른 변수 이름으로 property 값을 할당받으려면 다음과 같이 변수를 선언한다.
ex)
let user = { firstName: 'Lisa' , lastName: 'Yeon' };
// property 키를 기준으로 구조분해 할당이 이루어진다.
// property 키가 lastName인 property 값을 ln에 할당하고,
// property 키가 firstName인 property 값을 fn에 할당한다.
let { lastName: ln, firstName:fn } = user;
console.log(fn, ln); // Lisa Yeon
  1. 객체에서 property 키로 필요한 property 값만 추출하여 변수에 할당하고 싶을 때 유용하다.
ex)
let str = 'Coding';
// String 래퍼 객체로부터 length property만 추출한다.
let { length } = str;
console.log(length); // 6

let todo = { id: 1, content: 'HTML' , completed: true };
// todo 객체로부터 id property만 추출한다.
let { id } = todo;
console.log(id); // 1
  1. 객체 구조분해 할당을 위한 변수에 Rest parameter나 Rest element와 유사하게 Rest property ...을 사용할 수 있으며, 반드시 마지막에 위치해야 한다.
ex)
let { x, ...rest } = { x: 1, y: 2, z: 3 };
console.log(x, rest); // 1 { y: 2, z: 3 }
profile
말은 사라지지만 기록은 남는다

0개의 댓글