어제 HA시험을 시간에 맞춰서 마치고 ES6문법 중 arrow function에 대해 복습했다.
이번에는 spread syntax과 구조 분해 할당에 대해 복습하려고 한다.
여러 개의 값이 하나로 뭉쳐 있는 배열과 같은 이터러블을 펼쳐서 개별적인 값들의 목록을 만드는 것
...
을 붙이는 것Rest parameter는 인수들의 목록을 배열로 전달받는 것이다.
Spread syntax는 이러한 Rest parameter를 펼쳐서 개별적인 값들의 목록을 만드는 것이다.
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가 나타나기 전에는 이럴 경우 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
참 간편하고 깔끔하다^^
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]
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]
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
가 나오는 것이다.
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을 배열로 변환한다.
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
ex)
let [x, y, z] = [1, 2, 3];
ex)
let [x, y, z]; // SyntaxError: Missing initializer in destructuring declaration
let [a, b, c] = {}; // TypeError: {} is not 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
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
ex)
let { lastName, firstName } = { firstName: 'Lisa' , lastName: 'Yeon' };
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.
ex)
let { lastName, firstName } = user;
// 위와 아래는 같은 표현이다.
let { lastName: lastName, firstName: firstName } = user;
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
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
ex)
let { x, ...rest } = { x: 1, y: 2, z: 3 };
console.log(x, rest); // 1 { y: 2, z: 3 }