[JS #5] ES6 Map(), Set()

얼마 전부터 회사 업무를 진행할 때 본격적으로, 그리고 의식적으로 ES6 에 도입된 문법을 적용하고 있는데, 그중에서 가장 자주 활용하는 자료구조, Map 과 Set 에 대해 이야기해보려고 합니다. 이 글의 모티브는 상당부분 Mozilla 웹기술블로그에 기반합니다. 사내세미나에서 발표한 내용을 글로 정리했습니다.

Map

  • Map() 은 자바스크립트의 key-value 페어(pair) 로 이루어진 컬렉션
  • key 를 사용해서 value 를 get, set 할 수 있음
  • key 들은 중복될 수 없음: 하나의 key 에는 하나의 value 만
  • key 로 사용할 수 있는 데이터형: string, symbol(ES6), object, function >> number 는 사용할 수 없음에 주의!



// 새로운 map 을 만들고 map 에 key, value 엔트리를 추가
let me = new Map();
me.set('name', 'kevin');
me.set('age', 28);
console.log(me.get('age'); // 28
// 대괄호를 사용해서 map 을 선언하는 방법
const roomTypeMap = new Map(
[
["01", "원룸(오픈형)"],
["02", "원룸(분리형)"],
["03", "원룸(복층형)"],
["04", "투룸"],
["05", "쓰리룸"]
]
);
// 새로운 map 을 만들고 그 데이터를 기존의 [key, value] 페어컬렉션으로 채움
let you = new Map().set('name', 'paul').set('age', 34);
console.log(you.get('name')); // 'paul'
// has(): 주어진 key 가 존재하는지 확인
console.log(me.has('name')); // true
// size: map 에 담겨진 엔트리의 개수를 조회
console.log(you.size); // 2
// delete(): 엔트리를 삭제
me.delete('age');
console.log(me.has('age')); // false
// clear(): 모든 엔트리를 삭제
you.clear();
console.log(you.size); // 0

<참고 1> Map 과 Object 비교

  • Object 의 key 는 string 과 symbol(ES6) 만 가능하지만, map 은 어떤 값도 가능
  • Object 에서는 크기를 추적해서 알 수 있지만, map 은 손쉽게 얻을 수 있음(size)

Map 의 iterable object

  • map.keys(), map.values()
  • map 안의 key 혹은 value 들을 순회할 수 있는 iterable object 를 반환
let me = new Map().set('a', 1).set('b', 2);
console.log([...me.keys()]); // ['a', 'b']
console.log([...me.values()]); // [1, 2]
  • map.entries(), map.next()
  • map 안의 모든 엔트리들을 순회할 수 있는 iterable object 를 반환
let you = new Map().set('Seoul', 28).set('Tokyo', 26);
let iterObj = you.entries();
console.log(iterObj.next()); // {value: ['Seoul', 28], done: false}
console.log(iterObj.next()); // {value: ['Tokyo', 26], done: false}
console.log(iterObj.next()); // {value: undefined, done: true}
  • for-of, map.forEach();
  • forEach 의 경우, 인자 순서가 이상한데(key, value 순서가 반대) Array.prototype.forEach() 구문과 통일성을 유지하기 위함(value, index, array 순서인 것)
let we = new Map().set('car', 30).set('bus', 45);
// for-of 로 map 순회하기
for (let [key, value] of we) {
console.log(key + '^' + value);
}
// 차례대로 'car^30', 'bus^45' 출력
// forEach 로 map 순회하기
we.forEach((value, key, map) => {
console.log(key + '$' + value);
});
// 차례대로 'car$30', 'bus$45' 출력
  • 자바스크립트 배열 메서드에 존재하는 map, filter 메서드는 Map 에 존재하지 않는다. 하지만 아래와 같은 방식으로 우회해서 사용이 가능하다.
let me = new Map().set('a', 1).set('b', 2);
// value 가 1 이상인 엔트리만 filtering 하기
let map1 = new Map(
[...me]
.filter(([k, v]) => v > 1)
);
console.log([...map1.entries()]) // [['b', 2]]
// key 뒤에 'super' 문자열을 붙이고, value 에 1을 더하기
let map2 = new Map(
[...me]
.map(([k, v]) => [k + "super", v + 1])
);
console.log([...map2.entries()]) // [['asuper, 2], [bsuper, 3]]



Set

  • Set() 은 value 들로 이루어진 컬렉션(“집합”이라는 표현이 적절)
  • Array 와는 다르게 Set 은 같은 value 를 2번 포함할 수 없음
  • 따라서 Set 에 이미 존재하는 값을 추가하려고 하면 아무 일도 없음





// 비어있는 새로운 set 을 만듬
let setA = new Set();
// 새로운 set 을 만들고 인자로 전달된 iterable 로 인자를 채움
let setB = new Set().add('a').add('b');
setB.add('c');
console.log(setB.size); // 3
// has(): 주어진 값이 set 안에 존재할 경우, true 를 반환
// indexOf() 보다 빠름. 단, index 가 없음
console.log(setB.has('b')); // true
// set 에서 주어진 값을 제거
setB.delete('b');
console.log(setB.has('b')); // false
// set 안의 모든 데이터를 제거
setB.clear();
console.log(setB.size); // 0
  • <TODO> has() 는 indexOf() 보다 빠르다. 다만, index 이 존재하지 않기때문에 index 로 value 로 접근할 수 없다.

<참고 2> Spread 연산자

  • 이터러블 오브젝트(iterable object)의 엘리먼트를 하나씩 분리하여 전개
// string == iterable object
console.log([...'music']); // ['m', 'u', 's', 'i', 'c']

<참고 3> for 문들

  • for 문
let sampleArr = [1, 2, 3, 4, 5];
for (let i = 0, length = sampleArr.length; i < length; i++) {
console.log(sampleArr[i]);
}
  • forEach: ES5 자바스크립트 배열 메서드
let sampleArr = [1, 2, 3, 4, 5];
sampleArr.forEach(v => console.log(v));
  • for-in: Object 를 순회하기 위한
let sampleObj = {
a: 1,
b: 'hello',
c: [1, 2]
}
for (let key in sampleObj) {
console.log(key);
console.log(sampleObj[key]);
}
  • for-of: 배열의 요소들, 즉 data 를 순회하기 위한(string 도 가능)
let sampleArr = [1, 2, 3, 4, 5];
let (for value of sampleArr) {
console.log(value);
}

  • 일반 객체(Object)는 iterable 하지 않다!
  • for-of 나 …(spread 연산자)를 사용할 수 없다!
  • for-in 으로나 순회할 수 있다.


Set 의 iterable object

  • set.values();
  • 기본적으로 Set 의 prototype 메서드로 keys() 는 존재하지 않고, values() 만 존재하지만, MDN 의 설명에 따르면, map 오브젝트와 동일하게 동작하기 때문에 Set.prototype.keys() 는 Set.prototype.values() 와 같은 결과



let setA = new Set();
setA.add('a');
setA.add('b');
setA.add('a');
console.log([...setA.keys()]); // ['a', 'b']
console.log([...setA.values()]); // ['a', 'b']
  • set.entries();
let setB = new Set();
setB.add('Korea');
setB.add('Japan');
setB.add('China');
let entries = setB.entries();
console.log(entries.next()); 
// {value: ['Korea', 'Korea'], done: false}
console.log(entries.next());
// {value: ['Japan', 'Japan'], done: false}
console.log(entries.next());
// {value: ['China', 'China'], done: false}
console.log(entries.next());
// {value: undefined, done: true}
  • for-of, set.forEach();
let setC = new Set();
setC.add('Korea');
setC.add('Japan');
setC.add('China');
for (let key of setC) {
console.log(key);
}
// 차례대로 'Korea', 'Japan', 'China' 출력
setC.forEach((v, k) => {
console.log(v);
})
// 차례대로 'Korea', 'Japan', 'China' 출력

Set: 집합연산

스위프트 집합연산 — 링크
  • union(합집합), intersection(교집합), difference(차집합)
let setA = new Set([1, 2, 3, 4, 5]);
let setB = new Set([4, 5, 6, 7, 8]);
// 합집합
let unionSet = new Set([...setA, ...setB])
for (let value of unionSet) {
console.log(value);
}
// 차례대로 1, 2, 3, 4, 5, 6, 7, 8 출력
// 교집합
let intersectionSet = new Set(
[...setA].filter(v => setB.has(v))
);
for (let value of intersectionSet) {
console.log(value);
}
// 차례대로 4, 5 출력
// 차집합
let differenceSet = new Set(
[...setA].filter(v => !setB.has(v))
);
for (let value of differenceSet) {
console.log(value);
}
// 차례대로 1, 2, 3 출력
  • symmetricDifference
// Symmetric Difference
var set1 = new Set([1, 2, 3, 4, 5]);
var set2 = new Set([3, 4, 5, 6, 7]);
var symmetricDifferenceSet = new Set(
[...[...set1].filter(x => !set2.has(x)), ...[...set2].filter(x => !set1.has(x))]
)
for (let value of symmetricDifferenceSet) {
console.log(value);
}
// 차례대로 1, 2, 6, 7 출력

지금까지 Map 과 Set 에 대해 자세히 알아보았습니다.

단순히 key 와 value 를 set 하거나 value 를 set 하는 것뿐만 아니라, iterable object 의 특성을 살려서 map 과 set 을 순회하는 것을 알아보았습니다. 더 나아가서는 map 과 set 에서는 지원하지 않는 배열 메서드(Array.prototype) 인 map, filter 를 적용해보고, 집합연산까지도 진행해보았습니다.

억지로 배열의 형태로, 기본 객체형태로 코딩하기 보다는 적재적소의 자료구조의 특성에 맞게 코딩하는 습관을 기르면 좋겠습니다.

다음에는 윗글에서도 잠깐 나왔지만, 왜 배열의 indexOf 메서드보다 Set 의 has 가 더 “빠른지" 알아보겠습니다^^ 읽어주셔서 감사합니다.


The iterable protocol

iterable protocol 은 JavaScript 객체들이, 예를 들어 for..of 구조에서 어떠한 value 들이 loop 되는 것과 같은 iteration 동작을 정의하거나 사용자 정의하는 것을 허용합니다. 다른 type 들(Object 와 같은)이 그렇지 않은 반면에, 어떤 built-in type 들은 Array 또는 Map 과 같은 default iteration 동작으로 built-in iterables 입니다.

iterable 하기 위해서 object는 @@iterator 메소드를 구현해야 합니다. 이것은 object (또는 prototype chain 의 오브젝트 중 하나) 가 Symbol.iterator key 의 속성을 가져야 한다는 것을 의미합니다 :

PropertyValue
[Symbol.iterator]object를 반환하는, arguments 없는 functioniterator protocol 을 따른다.

어떠한 객체가 반복(Iterate)되어야 한다면 이 객체의 @@iterator 메소드가 인수없이 호출되고, 반환된 iterator는 반복을 통해서 획득할 값들을 얻을 때 사용됩니다.

The iterator protocol

iterator protocol 은 value( finite 또는 infinite) 들의 sequence 를 만드는 표준 방법을 정의합니다. 

객체가 next() 메소드를 가지고 있고, 아래의 규칙에 따라 구현되었다면 그 객체는 iterator이다:

PropertyValue
next

아래 2개의 속성들을 가진 object 를 반환하는 arguments 없는 함수 :

  • done (boolean)
    • Iterator(반복자)가 마지막 반복 작업을 마쳤을 경우 true. 만약 iterator(반복자)에 return 값이 있다면 value의 값으로 지정된다. 반환 값에 대한 설명은 여기.
    • Iterator(반복자)의 작업이 남아있을 경우 false. Iterator(반복자)에 done 프로퍼티 자체를 특정짓지 않은 것과 동일하다.
  • value - Iterator(반복자)으로부터 반환되는 모든 자바스크립트 값이며 done이 true일 경우 생략될 수 있다.

몇몇 iterator들은 iterable(반복 가능)이다:

var someArray = [1, 5, 7];
var someArrayEntries = someArray.entries();

someArrayEntries.toString();           // "[object Array Iterator]"
someArrayEntries === someArrayEntries[Symbol.iterator]();    // true

 Iteration protocols 사용 예시

String 은 built-in iterable 객체의 한 예시입니다.

var someString = "hi";
typeof someString[Symbol.iterator];          // "function"

String 의 기본 iterator 는 string 의 문자를 하나씩 반환합니다.

var iterator = someString[Symbol.iterator]();
iterator + "";                               // "[object String Iterator]"
 
iterator.next();                             // { value: "h", done: false }
iterator.next();                             // { value: "i", done: false }
iterator.next();                             // { value: undefined, done: true }

spread operator와 같은 특정 내장 구조(built-in constructs)들은 실제로는 동일한 iteration protocol을 사용한다:

[...someString]                              // ["h", "i"]

사용자만의 @@iterator를 특정함으로써 원하는 반복 행위(iteration behavior)를 설정할 수 있다:

var someString = new String("hi");          // need to construct a String object explicitly to avoid auto-boxing

someString[Symbol.iterator] = function() {
  return { // this is the iterator object, returning a single element, the string "bye"
    next: function() {
      if (this._first) {
        this._first = false;
        return { value: "bye", done: false };
      } else {
        return { done: true };
      }
    },
    _first: true
  };
};

재설정된 @@iterator가 어떻게 내장 구조(built-in constructs)의 반복 행위에 영향을 주는지 참고:

[...someString];                              // ["bye"]
someString + "";                              // "hi"

Iterable 예시

내장 iterables

String, Array, TypedArray, Map and Set 는 모두 내장 iterable이다. 이 객체들의 프로토타입 객체들은 모두 @@iterator 메소드를 가지고 있기 때문이다.

사용자 정의된 iterables

이렇게 고유한 iterables 를 만들 수 있다.

var myIterable = {};
myIterable[Symbol.iterator] = function* () {
    yield 1;
    yield 2;
    yield 3;
};
[...myIterable]; // [1, 2, 3]

Iterable을 허용하는 내장 API들

Iterable을 허용하는 많은 내장 API들이 있다. 예를 들어: Map([iterable])WeakMap([iterable])Set([iterable]) and WeakSet([iterable])이 그것이다:

var myObj = {};
new Map([[1,"a"],[2,"b"],[3,"c"]]).get(2);               // "b"
new WeakMap([[{},"a"],[myObj,"b"],[{},"c"]]).get(myObj); // "b"
new Set([1, 2, 3]).has(3);                               // true
new Set("123").has("2");                                 // true
new WeakSet(function*() {
    yield {};
    yield myObj;
    yield {};
}()).has(myObj);                                         // true

뿐만 아니라 Promise.all(iterable)Promise.race(iterable)와 Array.from() 또한 해당된다.

Iterable과 함께 사용되는 문법

for-of loops, spread operatoryield* destructuring assignment는 iterable과 함께 사용되는 구문(statements)과 표현(expressions)이다.

for(let value of ["a", "b", "c"]){
    console.log(value);
}
// "a"
// "b"
// "c"

[..."abc"]; // ["a", "b", "c"]

function* gen(){
  yield* ["a", "b", "c"];
}

gen().next(); // { value:"a", done:false }

[a, b, c] = new Set(["a", "b", "c"]);
a // "a"

잘 정의되지 못한 iterables

만약 Iterable의 @@iterator 메소드가 iterator 객체를 반환하지 않는다면 그것은 잘 정의되지 못한 iterable이라고 할 수 있다. 이러한 iterable을 사용하는 것은 런타임 예외나 예상치 못한 결과를 불러올 수 있다:

var nonWellFormedIterable = {}
nonWellFormedIterable[Symbol.iterator] = () => 1
[...nonWellFormedIterable] // TypeError: [] is not a function

Iterator 예시

간단한 iterator

function makeIterator(array){
    var nextIndex = 0;
    
    return {
       next: function(){
           return nextIndex < array.length ?
               {value: array[nextIndex++], done: false} :
               {done: true};
       }
    };
}

var it = makeIterator(['yo', 'ya']);

console.log(it.next().value); // 'yo'
console.log(it.next().value); // 'ya'
console.log(it.next().done);  // true

무한 iterator

function idMaker(){
    var index = 0;
    
    return {
       next: function(){
           return {value: index++, done: false};
       }
    };
}

var it = idMaker();

console.log(it.next().value); // '0'
console.log(it.next().value); // '1'
console.log(it.next().value); // '2'
// ...

Generator와 함께 사용된 iterator

function* makeSimpleGenerator(array){
    var nextIndex = 0;
    
    while(nextIndex < array.length){
        yield array[nextIndex++];
    }
}

var gen = makeSimpleGenerator(['yo', 'ya']);

console.log(gen.next().value); // 'yo'
console.log(gen.next().value); // 'ya'
console.log(gen.next().done);  // true



function* idMaker(){
    var index = 0;
    while(true)
        yield index++;
}

var gen = idMaker();

console.log(gen.next().value); // '0'
console.log(gen.next().value); // '1'
console.log(gen.next().value); // '2'
// ...

generator object 는 iterator 또는 iterable 인가?

generator object 는 iterator 이면서 iterable 입니다.

var aGeneratorObject = function*(){
    yield 1;
    yield 2;
    yield 3;
}();
typeof aGeneratorObject.next;
// "function", 이것은 next 메서드를 가지고 있기 때문에 iterator입니다.
typeof aGeneratorObject[Symbol.iterator];
// "function", 이것은 @@iterator 메서드를 가지고 있기 때문에 iterable입니다.
aGeneratorObject[Symbol.iterator]() === aGeneratorObject;
// true, 이 Object의 @@iterator 메서드는 자기자신(iterator)을 리턴하는 것으로 보아 잘 정의된 iterable이라고 할 수 있습니다.
[...aGeneratorObject];
// [1, 2, 3]


기본 매개 변수 (Default Parameters)

var link = function (height, color, url) {
    var height = height || 50
    var color = color || 'red'
    var url = url || 'http://azat.co'
    ...
}

함수에 넘겨주는 인자값에 대한 default 처리를 위해 위와 같이 처리 했었다면 ES6에서는 아래와 같이 간단히 처리할 수 있다.

var link = function(height = 50, color = 'red', url = 'http://azat.co') {
  ...
}

단, 주의해야 할 점이 있다. 인자값으로 0 또는 false가 입력될 때 두 예시의 결과는 다르다. ES5에서는 || 처리 시 0 또는 false 값이 입력 되어도 거짓이 되므로 기본값으로 대체된다. 하지만 ES6의 기본 매개 변수를 사용하면 undefined 를 제외한 입력된 모든 값(0, false, null 등)을 인정한다.

Index

  1. ES6 Class 문법
  2. Class 정의
  3. constructor
  4. Prototype 기반 상속(ES5)과 Class 기반 상속(ES6) 비교
  5. super 키워드
  6. static 키워드
  7. 마치며

ES6 Class 문법

JavaScript Class는 ECMAScript 6을 통해 소개되었습니다. ES6의 Class는 기존 prototype 기반의 상속을 보다 명료하게 사용할 수 있도록 문법을 제공합니다. 이를 Syntatic Sugar라고 부르기도 합니다.

Syntatic Sugar : 읽고 표현하는것을 더 쉽게 하기 위해서 고안된 프로그래밍 언어 문법을 말합니다.

JavaScript를 ES6를 통해 처음 접하시는 분들은 알아두셔야할 것이 JavaScript의 Class는 다른 객체지향 언어(C++, C#, Java, Python, Ruby 등…)에서 사용되는 Class 문법과는 다르다는 것입니다. JavaScript에는 Class라는 개념이 없습니다.
Class가 없기 때문에 기본적으로 Class 기반의 상속도 불가능합니다. 대신 다른 언어에는 존재하지 않는 프로토타입(Prototype)이라는 것이 존재합니다. JavaScript는 이 prototype을 기반으로 상속을 흉내내도록 구현해 사용합니다. Prototype을 처음 접하시는 분은 “Prototype 이제는 이해하자”를 참고하시면 도움이 될것같습니다.


Class 정의

JavaScript에서 Class는 사실 함수입니다. 함수를 함수 선언과 함수 표현식으로 정의할 수 있듯이 class 문법도 class 선언과 class 표현식 두가지 방법으로 정의가 가능합니다.

JavaScript 엔진은 function 키워드를 만나면 Function 오브젝트를 생성하듯, class 키워드를 만나면 Class 오브젝트를 생성합니다. class는 클래스를 선언하는 키워드이고 Class 오브젝트는 엔진이 class 키워드로 생성한 오브젝트입니다.

Class 선언

함수 선언과 달리 클래스 선언은 호이스팅이 일어나지 않기 때문에, 클래스를 사용하기 위해서는 먼저 선언을 해야합니다. 그렇지 않으면 ReferenceError 가 발생합니다.

1
2
3
4
5
6
7
8
9
class People {
constructor(name) {
this.name = name;
}

say() {
console.log('My name is ' + this.name);
}
}

Class 표현식

Class 표현식은 이름을 가질 수도 있고 갖지 않을 수도 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const People = class People {
constructor(name) {
this.name = name;
}

say() {
console.log('My name is ' + this.name);
}
}

const People = class {
constructor(name) {
this.name = name;
}

say() {
console.log('My name is ' + this.name);
}
}

constructor

constructor는 클래스 인스턴스를 생성하고 생성한 인스턴스를 초기화하는 역할을 합니다. new People() 코드를 실행하면 People.prototype.constructor가 호출됩니다. 이를 default constructor라고 하며 constructor가 없으면 인스턴스를 생성할 수 없습니다.

1
const people = new People('KimJongMin');

new People(‘KimJongMin’)을 실행하면 People 클래스에 작성한 constructor가 자동으로 호출되고 파라미터 값으로 ‘KimJongMin’을 넘겨 줍니다.

new 연산자가 인스턴스를 생성하는 것처럼 보이지만, 사실 new 연산자는 constructor를 호출하면서 파라미터를 넘겨주는 역할만 합니다. 호출된 constructor가 인스턴스를 생성하여 반환하면 new 연산자가 받아 new를 실행한 곳으로 반환합니다. 과정은 다음과 같습니다.

  1. new People(‘KimJongMin’)을 실행
  2. new 연산자가 constructor를 호출하면서 파라미터 전달
  3. constructor에 작성한 코드를 실행하기 전에 빈 Object 를 생성
  4. constructor 코드를 실행
  5. 생성한 Object(인스턴스)에 property 할당 (인스턴스를 먼저 생성했기 때문에 this로 Object 참조 가능
  6. 생성한 Object 반환

다음은 생성된 인스턴스의 구조입니다.

1
console.dir(people);

people 인스턴스의 __proto__는 People Class 오브젝트와 함께 생성된 Prototype object를 가리키고 있습니다. 결국 Class 문법을 이용한 코드를 prototype 기반의 코드로 변경하면 다음과 같습니다.

1
2
3
4
5
6
7
function People(name) {
this.name = name;
}

People.prototype.say = function () {
console.log('My name is ' + this.name);
};

Prototype 기반 상속(ES5)과 Class 기반 상속(ES6) 비교

먼저 ES5에서 Prototype을 사용하여 상속을 구현하는 방법을 살펴보고, 그 후 ES6에서 Class로 상속을 구현하는 형태를 보겠습니다.

ES5 Prototype 기반 상속

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function Cat(name) {
this.name = name;
}

Cat.prototype.speak = function () {
console.log(this.name + ' makes a noise.');
};

function Lion(name) {
// `super()` 호출
Cat.call(this, name);
}

// `Cat` 클래스 상속
Lion.prototype = Object.create(Cat.prototype);
Lion.prototype.constructor = Lion;

// `speak()` 메서드 오버라이드
Lion.prototype.speak = function () {
Cat.prototype.speak.call(this);
console.log(this.name + ' roars.');
};

var lion = new Lion('Samba');
lion.speak();

[결과]
Sambda makes a noise.
Sambda roars.

new Lion()을 실행하면 Lion()이 호출되고, default constructor를 호출합니다. 그래서 Lion()을 생성자(constructor) 함수라고 합니다.

생성자 함수가 있으면 Cat.prototype.speak와 같이 prototype에 메서드를 연결한 코드가 있습니다. 이와 같이 prototype에 작성하지 않으면 각각의 인스턴스에 메서드가 생성되게 됩니다. 이 형태가 ES5에서 인스턴스를 구현하는 기본 형태 입니다.

Object.create()를 통해 Cat.prototype에 연결된 메서드를 Lion.prototype.__proto__에 첨부합니다. Lion.prototype에는 constructor가 연결되어 있는데 prototype을 재 할당했기 때문에 지워진 constructor를 다시 할당해 줍니다.

결과적으로 Lion 생성자 함수의 구조는 다음과 같습니다.

ES6 Class 기반 상속

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Cat {
constructor(name) {
this.name = name;
}

speak() {
console.log(this.name + ' makes a noise.');
}
}

class Lion extends Cat {
speak() {
super.speak();
console.log(this.name + ' roars.');
}
}

const lion = new Lion('Samba');
lion.speak();

[결과]
Sambda makes a noise.
Sambda roars.

ES6에서는 extends 키워드로 상속을 구현합니다. Cat 클래스를 상속받은 Lion 클래스의 구조는 다음과 같습니다.

위의 prototype을 통해 상속을 구현한 Lion 생성자 함수의 구조와 비교했을때 일치합니다. 추가적으로 new Lion(‘Samba’) 를 실행하면 다음의 과정을 거치게됩니다.

  1. Lion 클래스의 constructor를 호출
  2. Lion 클래스에 constructor를 작성하지 않았기 때문에 슈퍼 클래스의(Cat) constructor가 호출됨 (내부적으로 프로토타입 체인으로 인해)
  3. 슈퍼 클래스의 constructor에서 this는 현재의 인스턴스를 참조하므로 인스턴스의 name 프로퍼티에 파라미터로 전달받은 값을 설정
  4. 생성한 인스턴스를 lion에 할당

super 키워드

서브 클래스와 슈퍼 클래스에 같은 이름의 메서드가 존재하면 슈퍼 클래스의 메서드는 호출되지 않습니다. 이때 super 키워드를 사용해서 슈퍼 클래스의 메서드를 호출할 수 있습니다. (서브 클래스의 constructor에 super()를 작성하면 슈퍼 클래스의 constructor가 호출됩니다.)


static 키워드

static 키워드는 클래스를 위한 정적(static) 메소드를 정의합니다. 정적 메소드는 prototype에 연결되지 않고 클래스에 직접 연결되기 때문에 클래스의 인스턴스화(instantiating) 없이 호출되며, 클래스의 인스턴스에서는 호출할 수 없습니다. 동일한 클래스 내의 다른 정적 메서드 내에서 정적 메서드를 호출하는 경우 키워드 this를 사용할 수 있다.

1
2
3
4
5
6
7
8
9
10
class Lion {
static speak() {
console.log('Noise~');
}
}

Lion.speak();

[결과]
Noise~

정적 메소드는 어플리케이션(application)을 위한 유틸리티(utility) 함수를 생성하는데 주로 사용됩니다.


마치며

ES6의 Class 문법에 대해 정리해 보았다. JavaScript 언어를 약 1년전 Node.js 를 시작하며 처음 접하게 되었는데 사실 그 당시 Prototype과 상속에 대해 크게 다룰일이 없었다. (어쩌면 너무 무지해서 사용 필요성을 느끼지 못했을 수도…) 그 후 Node.js 버전을 올리고 ES6를 공부하며 Class 문법을 접하게 되었는데 JavaScript의 Prototype에 대한 이해와 지식이 부족하다 보니 이전에 공부했던 C++과 Java의 Class 처럼 이해했던 것 같다. 그래도 그 후 Prototype과 더불이 Class까지 공부하며 지금은 어느정도 이해하게 된것 같다. 결론은… 역시나 JavaScript에서 Prototype을 이해하는건 중요한것 같다.

비구조화 할당 (Destructuring Assignment)


비구조화 할당(destructuring assignment) 구문은 배열이나 객체의 속성을 해체하여 그 값을 개별 변수에 담을 수 있게 하는 자바스크립트 표현식(expression)입니다.

ES5에서는 구조화된 데이터를 변수로 받기 위해 아래와 같이 처리해야 했다.

// browser
var data = $('body').data(), // data has properties house and mouse
  house = data.house,
  mouse = data.mouse

// Node.js
var jsonMiddleware = require('body-parser').json

var body = req.body, // body has username and password
  username = body.username,
  password = body.password

하지만 ES6에서는 비구조화 할당을 사용해 아래와 같이 처리할 수 있다.

var {house, mouse} = $('body').data() // we'll get house and mouse variables

var {jsonMiddleware} = require('body-parser')

var {username, password} = req.body

주의할 점은 var로 할당하려는 변수명과 구조화된 데이터의 property명이 같아야 한다. 또한 구조화된 데이터가 아니라 배열의 경우 {} 대신 []를 사용해서 위와 유사하게 사용할 수 있다.

var [col1, col2]  = $('.column'),
  [line1, line2, line3, , line5] = file.split('\n')


Destructring of nested object


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let body = {
        "userid":"da91love",
        "password":"1234567890",
        "site":{
            "naver":"blog.naver.com",
            "tistory":"valuefatory"
        }
}
 
const {userid,site:{naver}} = body;
 
console.log(userid);
console.log(naver);
 
const [c, ,d] = [123];
console.log(c); // 1
console.log(d); // 3
cs



위의 예에서 site안에 들어있는 naver을 해체 하고 싶은 경우, 중괄호{}로 감싸서 site{naver}로 해체하면 된다.

이때 한가지 주의할점은, 배열은 순서가 있기 때문에 꼭 순서대로 해체해야 원하는 값을 얻을 수 있지만, 객체는 순서가 정해지지 않기 때문에 순서를 꼭 지킬 필요가 없다. 

이것이 무슨 말인가하면...


body의 객체는 userid,password,site순인데

site의 naver 값을 얻기 위해 


const {userid, ,site:{naver}} = body;할 필요가 없다는 것이다.







default value for destructuring

-https://medium.com/@pyrolistical/destructuring-nested-objects-9dabdd01a3b8




1
2
3
4
let foo = {};
 
const { prop1 = {} } = foo
const { prop2 = {} } = foo
cs


foo가 비어있는 객체이기 때문에, prop1은 undefined가 되고, prop1에는 default 값인 {}가 들어가게 된다.




이제 아래부터 문제가 되기 시작하는데..


1
2
3
4
5
6
const user = {
  id: 339,
  name'Fred',
  age: 42
};
const {education: {degree}} = user;  // TypeError: Cannot match against 'undefined' or 'null'.
cs

education객체 아래에는 degree프로퍼티가 존재하지 않고 디폴트 값도 설정되어 있지 않기 때문에 타입에러가 발생한다.



그럼 이 문제도 위에서 처럼 디폴트값에 무언가를 주면 해결되지 않으려나 했는데...


1
2
3
4
5
6
7
const user = {
  id: 339,
  name'Fred',
  age: 42
};
const {education: {degree} = {}} = user;
console.log(degree); //prints: undefined
cs


타입에러가 발생하진 않았지만 우리의 기대치인 {}와는 다른 undefined의 값이 나온다...

아.. 왜이럴까.




아래의 코드가 해결책.

1
2
3
4
5
6
7
const user = {
  id: 339,
  name'Fred',
  age: 42
};
const {education: {degree} = {degree:{}}} = user;
console.log(degree); //{}
cs


user객체에는 education도 degree도 존재하지 않기 때문에, 어떤 네스팅 오브젝트에 {}값을 할당할 것인지 지정해주어야 한다..




만약 네스팅이 한번 더 해지면 어떨까.

1
2
3
4
5
6
const user = {
  id: 339,
  name'Fred',
  age: 42
};
const {education: {school: {name}} = {}} = user;  // TypeError: Cannot match against 'undefined' or 'null'.
cs


위의 코드는 물론 에러를 유발한다. 



아래가 해결책

1
2
3
4
5
6
const user = {
  id: 339,
  name'Fred',
  age: 42
};
const {education: {school: {name}} = {shcool:{name:{}}}} = user;
cs





Link to section구문

var a, b, rest;
[a, b] = [10, 20];
console.log(a); // 10
console.log(b); // 20

[a, b, ...rest] = [10, 20, 30, 40, 50];
console.log(a); // 10
console.log(b); // 20
console.log(rest); // [30, 40, 50]

({ a, b } = { a: 10, b: 20 });
console.log(a); // 10
console.log(b); // 20


// 제안 3단계(stage 3 proposal)
({a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40});
console.log(a); // 10
console.log(b); // 20
console.log(rest); //{c: 30, d: 40}

Link to section설명

객체 및 배열 리터럴 식은 즉석에서 쉽게 데이터 패키지를 만들 수 있도록 합니다.

var x = [1, 2, 3, 4, 5];

비구조화 할당은 이와 비슷한 구문이지만, 할당문의 좌변에 원래 변수에서 어떤 값들을 해체할지 정의합니다.

var x = [1, 2, 3, 4, 5];
var [y, z] = x;
console.log(y); // 1
console.log(z); // 2

이 기능은 Perl이나 Python 같은 언어에 존재하는 것과 비슷합니다.

Link to section배열 비구조화

Link to section기본 변수 할당

var foo = ["one", "two", "three"];

var [one, two, three] = foo;
console.log(one); // "one"
console.log(two); // "two"
console.log(three); // "three"

Link to section선언에서 분리한 할당

비구조화를 통해 변수의 선언과 분리하여 값을 할당할 수 있습니다.

var a, b;

[a, b] = [1, 2];
console.log(a); // 1
console.log(b); // 2

Link to section기본값

배열로부터 해체된 값이 undefined인 경우에는 변수에 기본값을 할당할 수 있습니다.

var a, b;

[a=5, b=7] = [1];
console.log(a); // 1
console.log(b); // 7

Link to section변수 값 교환하기

하나의 비구조화 표현식만으로 두 변수의 값을 교환할 수 있습니다.

비구조화 할당 없이 두 값을 교환하려면 임시 변수(또는 일부 저-레벨 언어에서는 XOR-swap 트릭)가 필요합니다.

var a = 1;
var b = 3;

[a, b] = [b, a];
console.log(a); // 3
console.log(b); // 1

Link to section함수에서 반환된 배열 파싱

함수가 배열을 반환하는 경우가 있습니다. 비구조화는 반환된 배열 값을 통하여 작업을 더 간결하게 할 수 있습니다.

아래 예제에서, f() 함수는 그 출력으로 배열 [1, 2]을 반환하는데, 단 한 줄의 비구조화 구문으로 그 배열을 파싱할 수 있습니다.

function f() {
  return [1, 2];
}

var a, b;
[a, b] = f();
console.log(a); // 1
console.log(b); // 2

Link to section일부 반환값 무시하기

다음과 같이 관심 없는 반환값을 무시할 수 있습니다.

function f() {
  return [1, 2, 3];
}

var [a, , b] = f();
console.log(a); // 1
console.log(b); // 3

다음과 같이 반환된 값을 모두 무시할 수도 있습니다.

[,,] = f();

Link to section변수에 배열의 나머지를 할당하기

배열을 비구조화할 경우, rest 패턴을 이용해 해체하고 남은 부분을 하나의 변수에 할당할 수 있습니다.

var [a, ...b] = [1, 2, 3];
console.log(a); // 1
console.log(b); // [2, 3]

rest 요소의 오른쪽 뒤에 콤마가 있으면 SyntaxError가 발생할 것입니다.

var [a, ...b,] = [1, 2, 3];
// SyntaxError: rest element may not have a trailing comma

Link to section정규식과 일치하는 값 해체하기

정규식의 exec() 메서드는 일치하는 부분를 찾으면 그 문자열에서 정규식과 일치하는 부분 전체를 배열의 맨 앞에, 그리고 그 뒤에 정규식에서 괄호로 묶인 각 그룹과 일치하는 부분을 포함하는 배열을 반환합니다. 비구조화 할당은 필요하지 않은 경우 일치하는 전체 부분은 무시하고 필요한 부분만 쉽게 빼올 수 있습니다.

var url = "https://developer.mozilla.org/en-US/Web/JavaScript";

var parsedURL = /^(\w+)\:\/\/([^\/]+)\/(.*)$/.exec(url);
console.log(parsedURL); // ["https://developer.mozilla.org/en-US/Web/JavaScript", "https", "developer.mozilla.org", "en-US/Web/JavaScript"]

var [, protocol, fullhost, fullpath] = parsedURL;

console.log(protocol); // "https"

Link to section객체 비구조화

Link to section기본 할당

var o = {p: 42, q: true};
var {p, q} = o;

console.log(p); // 42
console.log(q); // true

Link to section선언 없는 할당

비구조화를 통해 변수의 선언과 분리하여 변수에 값을 할당할 수 있습니다.

var a, b;

({a, b} = {a: 1, b: 2});

할당 문을 둘러싼 ( .. )는 선언 없이 객체 리터럴(object literal) 비구조화 할당을 사용할 때 필요한 구문입니다.

{a, b} = {a:1, b:2}는 유효한 독립 구문이 아닙니다. 좌변의 {a, b}이 객체 리터럴이 아닌 블록으로 간주되기 때문입니다.

하지만, ({a, b} = {a:1, b:2})는 유효한데, var {a, b} = {a:1, b:2}와 같습니다.

참고: ( .. ) 표현식 앞에 세미콜론이 있어야 합니다. 그렇지 않을 경우 이전 줄에과 연결되어 함수를 실행하는데 이용될 수 있습니다.

Link to section새로운 변수 이름으로 할당하기

객체로부터 속성을 해체하여 객체의 원래 속성명과는 다른 이름의 변수에 할당할 수 있습니다.

var o = {p: 42, q: true};
var {p: foo, q: bar} = o;

console.log(foo); // 42
console.log(bar); // true

Link to section기본값

객체로부터 해체된 값이 undefined인 경우, 변수에 기본값을 할당할 수 있습니다.

var {a = 10, b = 5} = {a: 3};

console.log(a); // 3
console.log(b); // 5

Link to section기본값 갖는 새로운 이름의 변수에 할당하기

새로운 변수명 할당과 기본값 할당을 한번에 할 수 있습니다.

var {a:aa = 10, b:bb = 5} = {a: 3};

console.log(aa); // 3
console.log(bb); // 5

Link to section함수 매개변수의 기본값 설정하기

ES5 버전

function drawES5Chart(options) {
  options = options === undefined ? {} : options;
  var size = options.size === undefined ? 'big' : options.size;
  var cords = options.cords === undefined ? { x: 0, y: 0 } : options.cords;
  var radius = options.radius === undefined ? 25 : options.radius;
  console.log(size, cords, radius);
  // 이제 드디어 차트 그리기 수행
}

drawES5Chart({
  cords: { x: 18, y: 30 },
  radius: 30
});

ES2015 버전

function drawES2015Chart({size = 'big', cords = { x: 0, y: 0 }, radius = 25} = {}) {
  console.log(size, cords, radius);
  // 차트 그리기 수행
}

drawES2015Chart({
  cords: { x: 18, y: 30 },
  radius: 30
});

위의 drawES2015Chart 함수의 원형에서 비구조화된 좌변에 빈 오브젝트 리터럴을 할당하는 것을 볼 수 있습니다 {size = 'big', cords = {x: 0, y: 0}, radius = 25} = {}. 빈 오브젝트를 우변에 할당하지 않고도 함수를 작성할 수 있습니다. 하지만, 지금의 형태에서는 단순히 drawES2015Chart()와 같이 어떤 매개변수 없이도 호출할 수 있지만, 우변의 빈 오브젝트 할당을 없앤다면 함수 호출시 적어도 하나의 인자가 제공되어야만 합니다. 이 함수가 어떠한 매개변수 없이도 호출할 수 있길 원한다면 지금 형태가 유용하고, 무조건 객체를 넘기길 원하는 경우에는 빈 객체 할당을 하지 않는 것이 유용할 수 있습니다.

Link to section중첩된 객체 및 배열의 비구조화

var metadata = {
    title: "Scratchpad",
    translations: [
       {
        locale: "de",
        localization_tags: [ ],
        last_edit: "2014-04-14T08:43:37",
        url: "/de/docs/Tools/Scratchpad",
        title: "JavaScript-Umgebung"
       }
    ],
    url: "/en-US/docs/Tools/Scratchpad"
};

var { title: englishTitle, translations: [{ title: localeTitle }] } = metadata;

console.log(englishTitle); // "Scratchpad"
console.log(localeTitle);  // "JavaScript-Umgebung"

Link to sectionfor of 반복문과 비구조화

var people = [
  {
    name: "Mike Smith",
    family: {
      mother: "Jane Smith",
      father: "Harry Smith",
      sister: "Samantha Smith"
    },
    age: 35
  },
  {
    name: "Tom Jones",
    family: {
      mother: "Norah Jones",
      father: "Richard Jones",
      brother: "Howard Jones"
    },
    age: 25
  }
];

for (var {name: n, family: { father: f } } of people) {
  console.log("Name: " + n + ", Father: " + f);
}

// "Name: Mike Smith, Father: Harry Smith"
// "Name: Tom Jones, Father: Richard Jones"

Link to section함수 매개변수로 전달된 객체에서 필드 해체하기

function userId({id}) {
  return id;
}

function whois({displayName: displayName, fullName: {firstName: name}}){
  console.log(displayName + " is " + name);
}

var user = {
  id: 42,
  displayName: "jdoe",
  fullName: {
      firstName: "John",
      lastName: "Doe"
  }
};

console.log("userId: " + userId(user)); // "userId: 42"
whois(user); // "jdoe is John"

이 예제는 user 객체로부터 iddisplayName 및 firstName 을 해체해 출력합니다.

Link to section계산된 속성 이름과 비구조화

계산된 속성 이름(computed property name)은, 객체 리터럴과 비슷하게 비구조화에도 사용될 수 있습니다.

let key = "z";
let { [key]: foo } = { z: "bar" };

console.log(foo); // "bar"

Link to section객체 비구조화에서의 Rest

Rest/Spread Properties for ECMAScript 제안(stage 3)에서는 비구조화에 rest 구문을 추가하고 있습니다. rest 속성은 비구조화 패턴으로 걸러지지 않은 열거형 속성의 키를 가진 나머지 항목들을 모읍니다.

let {a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40}
a; // 10 
b; // 20 
rest; // { c: 30, d: 40 }

Link to section속성 이름이 유효한 자바스크립트 식별자명이 아닌 경우

비구조화는 자바스크립트 식별자명으로 적합하지 않은 속성명이 제공된 경우에도 이용할 수 있습니다. 이때는 대체할 유효한 식별자명을 제공해야 합니다.

const foo = { 'fizz-buzz': true };
const { 'fizz-buzz': fizzBuzz } = foo;

console.log(fizzBuzz); // "true"






1강 

ecma는 자바스크립트의 코어 스크립트언어. 연산자 조건문 이딴거


2강let const

원시형에서 변수는 let

상수는 const


-var 재할당 가능


-let 동일한 변수에 두번 선언 불가


let name = 'boy';

let name = 'girl'; 

=> 불가


-재할당 가능

let name = 'boy';

name = 'girl'; 

=>가능


let name;

console.log(name)

=> undefined


-const 동일한 변수에 두번선언은 당연히 안되고

재할당도 안됨.

const name = 'boy';

name = 'girl'; 

=>불가


단, const가 참조형 자료를 담고있을 경우 프로퍼티는 재할당 가능

const name = {

 boy1:'james',

 boy2:'chrals'

}


name.boy1 = 'daseul'



const name

=>선언과 동시에 값할당하지 않으면 실행초자안됨



3강 block scope

-block-sockpe : {}로 감싸진 변수의 유효범위

-기존의 var는 function scope레벨 호이스팅

-let키워드를 사용해 변수의 유효범위를 블록스코프처리



호이스팅이란 var let function등 모든 선언문이 최상단으로 끌어올려지는 것

함수표현식 함수선언식

var num = 1;

function printNum(){

console.log('first:'+num);

var num=2;

console.log('second:'+num);

}

printNum();


=>first:undefined second:2



===============호이스팅된 경우

var num;

function printNum(){

  var num;

console.log('first:'+num);

num=2;

console.log('second:'+num);

}

num = 1;

printNum();




var pruduct = 'phone';

if(true){

var product ='tv';

console.log(product);

}

console.log(product);


=> tv tv

===============호이스팅된 경우

var pruduct';

pruduct = 'phone';

if(true){

var product;

        product='tv';

console.log(product);

}

console.log(product);








let pruduct = 'phone';

if(true){

let product ='tv';

console.log(product);

}

console.log(product);


=>tv phone


함수스코브 :var let const어떤 걸로 선언하든 선언한 함수내부에서 선언한 것은 함수내부에서만 사용가능

var let const


블록스코프:블록 내부에서 let const로 선언한 것만 블록 내부에서 사용가능



4강 temporal dead zone


let은 var와 달리 변수가 초기화 되기 전에 접근하면 undefined가 아닌 레퍼런스에러가 발생한다.

var는 선언과 동시에 undefined로 자동으로 초기화 되지만

let의 경우 선언은 되지만 초기화는 되지 않는다. 처음으로 호이스팅된 변수에 값을 집어넣는 순간 초기화가 된다.


let product = 'phone';

if(true){

let product;//호이스팅으로 인해 변수 product에 대한 TDZ 시작 

console.log(product);

let product ='tv';//product에 대한 TDZ 끝

}

console.log(product);

=>error


=====================================

var products = 'phone';

if(true){

console.log(product);

var product ='tv';

}

console.log(product);

=>undefined tv


정리

원시형 변수는 let, 상수는 const

참조형은 const





문법

Object.assign(target, ...sources)

인자

target
타켓 오브젝트
sources
하나 이상의 소스 오브젝트

리턴값

타겟 오브젝트

설명

소스 프로퍼티와 동일한 프로퍼티의 키를 가진 타켓 오브젝트의 프로퍼티들은 소스 오브젝트의 프로퍼티로 덮어쓰기 될 것입니다.

Object.assign() 메소드는 열거할 수 있는 소스 오브젝트의 프로퍼티들만 타켓 오브젝트로 복사합니다. 이 메소드는 소스 오브젝트 대상으로 게터를 호출하고, 타켓 오브젝트 대상으로 세터를 호출합니다.
따라서, 소스 오브젝트의 프로퍼티를 그냥 단순히 복사하거나, 새로운 프로퍼티를 생성하는 것이 아니라, 타켓 오브젝트의 프로퍼티를 게터와 세터를 이용하여 할당할 수도 있습니다.
 먄약, 병합되는 소스 오브젝트가 게터를 포함하고 있다면, 새로운 프로퍼티를 타겟의 프로토타입에 병합하는 것은 알맞지 않을 것입니다. 열거가능성을 포함한 프로퍼티를  프로토타입으로 복사하기 위해서는 Object.getOwnPropertyDescriptor() 와  Object.defineProperty() 을 사용하시기 바랍니다.

String 과 Symbol 프로퍼티 둘 다 복사될 것입니다.

프로퍼티가 쓰기불가능(non-writable) 등과 같이 만약 에러가 발생할 수 있는 상황에서는,  TypeError 가 발생하고 타겟 오브젝트에는 변화가 없을 것입니다.

Object.assign() 메소드는 null 이나 undefined 는 반환하지 않으니 주의하세요.

예제들

객체 복제하기

var obj = { a: 1 };
var copy = Object.assign({}, obj);
console.log(copy); // { a: 1 }

깊은 복제에 대한 주의사항

깊은 복제에 대해서는 대안적인 방법을 사용할 필요가 있습니다. 왜냐하면, Object.assign()는 타겟 오브젝트에 할당을 할 때, 프로퍼티의 참조를 복사하기 때문입니다.

function test() {
  let a = { b: {c:4} , d: { e: {f:1}} }
  let g = Object.assign({},a)
  let h = JSON.parse(JSON.stringify(a));
  console.log(g.d) // { e: { f: 1 } }
  g.d.e = 32
  console.log('g.d.e set to 32.') // g.d.e set to 32.
  console.log(g) // { b: { c: 4 }, d: { e: 32 } }
  console.log(a) // { b: { c: 4 }, d: { e: 32 } }
  console.log(h) // { b: { c: 4 }, d: { e: { f: 1 } } }
  h.d.e = 54
  console.log('h.d.e set to 54.') // h.d.e set to 54.
  console.log(g) // { b: { c: 4 }, d: { e: 32 } }
  console.log(a) // { b: { c: 4 }, d: { e: 32 } }
  console.log(h) // { b: { c: 4 }, d: { e: 54 } }
}
test();

객체 병합하기

var o1 = { a: 1 };
var o2 = { b: 2 };
var o3 = { c: 3 };

var obj = Object.assign(o1, o2, o3);
console.log(obj); // { a: 1, b: 2, c: 3 }
console.log(o1);  // { a: 1, b: 2, c: 3 }, 타겟 오브젝트, 그 자체도 변화합니다.

같은 프로퍼티를 가지고 있는 객체 병합하기

var o1 = { a: 1, b: 1, c: 1 };
var o2 = { b: 2, c: 2 };
var o3 = { c: 3 };

var obj = Object.assign({}, o1, o2, o3);
console.log(obj); // { a: 1, b: 2, c: 3 }

다른 오브젝트가 가지고 있는 똑같은 프로퍼티들은 인수의 순서에 따라 덮어 씌여질 것입니다.

심볼 타입 프로퍼티 복사하기

var o1 = { a: 1 };
var o2 = { [Symbol('foo')]: 2 };

var obj = Object.assign({}, o1, o2);
console.log(obj); // { a : 1, [Symbol("foo")]: 2 } (cf. bug 1207182 on Firefox)
Object.getOwnPropertySymbols(obj); // [Symbol(foo)]

프로토타입 체인 위에 있는 프로퍼티와 열거할 수 없는 프로퍼티들은 복사되지 않습니다

var obj = Object.create({ foo: 1 }, { // foo는 변수 obj의 프로토타입 체인 위에 있습니다.
  bar: {
    value: 2  // 변수 bar는 non-enumerable 프로퍼티입니다. 따라서 복사되지 않을 것입니다.
  },
  baz: {
    value: 3,
    enumerable: true  // 변수 baz는 그 자체도 enumerable 프로퍼티입니다. 따라서 복사될 것입니다.
  }
});

var copy = Object.assign({}, obj);
console.log(copy); // { baz: 3 }

원시 타입들은 객체로 변환될 것입니다

var v1 = 'abc';
var v2 = true;
var v3 = 10;
var v4 = Symbol('foo');

var obj = Object.assign({}, v1, null, v2, undefined, v3, v4); 
// null과 undefined는 무시될 것입니다.
// String wrapper만이 enumerable 프로퍼티를 가지고 있음을 주의하세요.
console.log(obj); // { "0": "a", "1": "b", "2": "c" }

예외는 진행중인 복사 작업을 중지시킬 것입니다

var target = Object.defineProperty({}, 'foo', {
  value: 1,
  writable: false
}); // target.foo is a read-only property

Object.assign(target, { bar: 2 }, { foo2: 3, foo: 3, foo3: 3 }, { baz: 4 });
// TypeError: "foo" is read-only
// The Exception is thrown when assigning target.foo

console.log(target.bar);  // 2, 첫 번째 소스 오브젝트는 성공적으로 복사되었습니다.
console.log(target.foo2); // 3, 두 번째 소스 프로젝트의 첫 번째 프로퍼티도 성공적으로 복사되었습니다.
console.log(target.foo);  // 1, 예외는 여기서 던져집니다.
console.log(target.foo3); // undefined, Object.assign() 메소드는 끝났습니다. 변수 foo3는 복사되지 않습니다.
console.log(target.baz);  // undefined, 세 번째 소스 오브젝트, 역시 복사되지 않습니다.

복사 접근자들

var obj = {
  foo: 1,
  get bar() {
    return 2;
  }
};

var copy = Object.assign({}, obj); 
console.log(copy); 
// { foo: 1, bar: 2 }, copy.bar의 값은 obj.bar'의 게터가 리턴해주는 값입니다.

// 이것은 모든 descriptors를 복사하는 할당 함수입니다.
function completeAssign(target, ...sources) {
  sources.forEach(source => {
    let descriptors = Object.keys(source).reduce((descriptors, key) => {
      descriptors[key] = Object.getOwnPropertyDescriptor(source, key);
      return descriptors;
    }, {});
    // 기본적으로, Object.assign는 열거할 수 있는 Symbol도 복사합니다.
    Object.getOwnPropertySymbols(source).forEach(sym => {
      let descriptor = Object.getOwnPropertyDescriptor(source, sym);
      if (descriptor.enumerable) {
        descriptors[sym] = descriptor;
      }
    });
    Object.defineProperties(target, descriptors);
  });
  return target;
}

var copy = completeAssign({}, obj);
console.log(copy);
// { foo:1, get bar() { return 2 } }


+ Recent posts