[JavaScript] 전개 구문 Spread syntax (...)
참고 문서
테스트 환경 정보
- ES2015에서 최초 정의
- ES2018에서 객체 초기화 관련 정의
- Chrome 60/Edge 79/Firefox 55/Opera 47/Safari 11.1 이상에서 사용 가능
- IE에서 사용 불가
개요
전개 구문은 객체나 배열의 요소를 말그대로 전개하거나 분해할 때 사용한다. 단순 전개부터 apply()
를 대체하거나 인스턴스 생성, 배열 복제/연결, 객체 복제 등에 사용할 수 있다.
var obj = {a: 1, b: 2};
var obj2 = {...obj};
console.log(obj2); // Object { a: 1, b: 2 }
var n = [1, 2, 3];
console.log(n); // Array(3) [ 1, 2, 3 ]
console.log(...n); // 1 2 3
함수 호출에서: 인수 목록을 위한 전개 Spread for argument lists
객체는 안 되영
인수 목록에서 plain object는 전개할 수 없다:
var obj = {a: 1, b: 2};
log(...obj); // Uncaught TypeError: obj is not iterable
인수 목록에서 전개하려면 Array
나 Map
같은 Iterable object여야 함.
apply() 대체
원래 배열의 요소를 함수의 인자로 활용하려면 Function.prototype.apply()
를 써야 했다:
function fn(a, b, c) {
return '' + a + b + c;
}
fn(3, 2, 1); // 321
fn.apply(null, [3, 2, 1]); // 321
var arr = [10, 30, 5];
Math.min.apply(Math, arr); // 5
이제는 전개 구문으로:
fn(...[3, 2, 1]); // 321
var arr = [10, 30, 5];
Math.min(...arr); // 5
이렇게 간단하게 작성한다.
그리고 전개 구문은 인수 목록에서 여러번 허용된다:
var arr = [7, 8];
fn(...[6], ...arr) // 678
요렇게도 쓸 수 있다는 말.
new에 적용
배열의 요소를 전개한다는 점을 이용해서 생성자에게 전달할 인수를 작성할 때 사용함:
function Fn(a, b, c) {
this.a = '' + a + b + c;
}
var o = new Fn(1, 2, 3);
log(o); // Fn { a: "123" }
var o2 = new Fn(...[1, 2, 3]);
log(o2); // Fn { a: "123" }
배열 리터럴의 전개
배열 연결(concatenate)
둘 이상의 배열을 연결할 땐 Array.prototype.concat()
을 사용하곤 했는데:
var arr1 = [1, 2, 3];
var arr2 = [4, 5, 6];
var arr3 = arr1.concat(arr2); // [1, 2, 3, 4, 5, 6]
이제는 그냥 이렇게 하면 된다:
var arr4 = [...arr1, ...arr2];
응용하면 좀 더 유연한 방식의 리터럴 작성이 가능함:
var def = ['d', 'e', 'f'];
var alphabet = ['a', 'b', 'c', ...def, 'g'];
alphabet; // ["a", "b", "c", "d", "e", "f", "g"]
배열 복제(cloning)
원본을 복제한 새 배열을 만드는 방법이다:
var arr = [1, 2, 3];
var arr2 = [...arr];
arr === arr2; // false
arr2; // [1, 2, 3]
배열을 한 번 전개해도 내용물이 여전히 배열인 경우(예: 2단 이상의 배열) 기존 배열을 참조하는게 되니 주의할 것:
var arr = [[1, 2], [3]];
var arr2 = [...arr];
arr[0]; // [1, 2]
arr2[0].pop(); // 참조하는 배열을 수정해서 arr도 영향을 받음.
arr[0]; // [1]
객체 리터럴의 전개
객체 복제(cloning)
객체의 전개는 Object.assign()
을 대체하여 객체 복제에 사용할 수 있다:
var obj = {foo: 'bar', x: 42};
var clone1 = {...obj};
clone1; // Object { foo: "bar", x: 42 }
clone1 === obj; // false
var clone2 = {...obj, x: '사십이', c: 1}; // obj에서 x는 재할당하고 c를 추가한 새 배열 객체 반환
clone2; // Object { foo: "bar", x: "사십이", c: 1 }
배열과 마찬가지로 얕은 복제(shallow cloning)다:
var obj = {foo: {a: 1, b: 2}};
var clone4 = {...obj};
obj.foo.a; // 1
clone4.foo.a = 3;
obj.foo.a; // 3
객체 연결(concatenate)
객체의 프로퍼티를 이어 붙여서 새 객체를 만드는 방법. 만약 이름이 같은 프로퍼티가 있다면, 나중에 오는 객체의 프로퍼티가 덮어쓴다:
var obj1 = {foo: 'bar', x: 42};
var obj2 = {foo: 'baz', y: 13};
var mergedObj = {...obj1, ...obj2}; // obj2.foo가 obj1.foo를 덮어쓰게 됨
mergedObj; // Object { foo: "baz", x: 42, y: 13 }
우선권
전개 구문은 연산자가 아니기 때문에 우선순위가 없다…고 하지만, 굳이 언급하자면 가장 우측의 객체(혹은 프로퍼티)가 우선권을 가지며 두 번째 객체부터 차례대로 좌측으로 병합된다고 볼 수 있다:
var m1 = {a: 1, b: 22};
var m2 = {...m1, b: 33, b: 44, b: 55, a: 11};
console.log(m1); // Object {a: 1, b: 22} // 원본은 그대로 유지
console.log(m2); // Object { a: 11, b: 55 }
var j1 = {a: 1, b: 2};
var j2 = {b: 33, d: 4};
var j3 = {d: 44, a: 11};
var j4 = {f: 5, a: 111};
var j5 = {...j1, ...j2, ...j3, ...j4};
console.log(j5); // Object { a: 111, b: 33, d: 44, f: 5 }
나머지 구문
구조 분해 할당 표현식에서 사용할 땐 전개 구문 대신 나머지 구문이라 부르며 전개 구문과는 다른 기능을 수행한다:
var [min = 0, max = Infinity, ...rest] = [1, 2, 3, 4, 5];
min; // 1
max; // 2
rest; // [3, 4, 5]
var {x = 24, b = 48, ...y} = {a: 1, b: 2, c: 3, d: 4};
x; // 24
b; // 2
y; // { a: 1, c: 3, d: 4 }
위 코드에서 ...rest
, ...y
가 나머지 구문임. 해당 내용은 구조 분해 할당을 살펴볼 것.
끗.