문법

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 } }



https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Classes

Class 정의

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

Class 선언

class를 정의하는 한 가지 방법은 class 선언을 이용하는 것입니다. class를 선언하기 위해서는 클래스의 이름과 함께 class 키워드를 사용해야 합니다.

class Polygon {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}

Hoisting

함수 선언과 클래스 선언의 중요한 차이점은 함수 선언의 경우 호이스팅이 일어나지만, 클래스 선언은 그렇지 않다는 것입니다. 클래스를 사용하기 위해서는 클래스를 먼저 선언 해야 하며, 그렇지 않으면, 다음 아래의 코드는 ReferenceError 에러를 던질 것입니다. :

var p = new Polygon(); // ReferenceError

class Polygon {}

Class 표현식

class 표현식은 class를 정의 하는 또 다른 방법입니다. Class 표현식은 이름을 가질 수도 있고, 갖지 않을 수도 있습니다. 기명 class 표현식에 주어진 이름은 클래스의 body에 대해 local scope에 한해 유효합니다.

// unnamed
var Polygon = class {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
};

// named
var Polygon = class Polygon {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
};

Class body 와 method 정의

Class body는 중괄호 {} 로 묶여 있는 안쪽 부분입니다.. 이곳은 여러분이 method나 constructor와 같은 class members를 정의할 곳입니다.

Strict mode

클래스 선언과 클래스 표현식의 본문(body)은 strict mode 에서 실행됩니다.

Constructor (생성자)

constructor 메소드는 class 로 생성된 객체를 생성하고 초기화하기 위한 특수한 메소드입니다.  "constructor" 라는 이름을 가진 특수한 메소드는 클래스 안에 한 개만 존재할 수 있습니다. 만약 클래스에 한 개를 초과하는 constructor 메소드를 포함한다면, SyntaxError 가 발생할 것입니다.

constructor는 부모 클래스의 constructor 를 호출하기 위해 super 키워드를 사용할 수 있습니다.

Prototype methods

method definitions도 참조해보세요.
class내부의 메서드를 선언할 때는 function 키워드를 사용하지 않습니다.

class Rectangle {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
  // Getter
  get area() {
    return this.calcArea();
  }
  // Method
  calcArea() {
    return this.height * this.width;
  }
}

const square = new Rectangle(10, 10);

console.log(square.area); // 100

Static methods

static 키워드는 클래스를 위한 정적(static) 메소드를 정의합니다. 정적 메소드는 클래스의 인스턴스화(instantiating) 없이 호출되며, 클래스의 인스턴스에서는 호출할 수 없습니다. 정적 메소드는 어플리케이션(application)을 위한 유틸리티(utility) 함수를 생성하는데 주로 사용됩니다.

class Point {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }

    static distance(a, b) {
        const dx = a.x - b.x;
        const dy = a.y - b.y;

        return Math.sqrt(dx*dx + dy*dy);
    }
}

const p1 = new Point(5, 5);
const p2 = new Point(10, 10);

console.log(Point.distance(p1, p2));

 

Boxing with prototype and static methods

정적 메소드나 프로토타입 메소드가 this 값 없이 호출될 때, this 값은 메소드 안에서 undefined가 됩니다. class 문법 안에 있는 코드는 항상 strict mode 로 실행되기 때문에 이동작은 "use strict" 명령어가 없더라도 같습니다.

 

class Animal { 
  speak() {
    return this;
  }
  static eat() {
    return this;
  }
}

let obj = new Animal();
obj.speak(); // Animal {}
let speak = obj.speak;
speak(); // undefined

Animal.eat() // class Animal
let eat = Animal.eat;
eat(); // undefined

If the above is written using traditional function–based syntax, then autoboxing in method calls will happen in non–strict mode based on the initial this value. If the inital value is undefinedthis will be set to the global object.

Autoboxing will not happen in strict mode, the this value remains as passed.

function Animal() { }

Animal.prototype.speak = function() {
  return this;
}

Animal.eat = function() {
  return this;
}

let obj = new Animal();
let speak = obj.speak;
speak(); // global object

let eat = Animal.eat;
eat(); // global object

Instance properties

Instance properties must be defined inside of class methods:

class Rectangle {
  constructor(height, width) {    
    this.height = height;
    this.width = width;
  }
}

Static class-side properties and prototype data properties must be defined outside of the ClassBody declaration:

Rectangle.staticWidth = 20;
Rectangle.prototype.prototypeWidth = 25;

 

 

 

extends를 통한 클래스 상속(sub classing)

extends 키워드는 클래스 선언이나 클래스 표현식에서 다른 클래스의 자식 클래스를 생성하기 위해 사용됩니다.

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

class Dog extends Animal {
  speak() {
    console.log(this.name + ' barks.');
  }
}

subclass에 constructor가 있다면, "this"를 사용하기 전에 가장 먼저 super()를 호출해야 합니다.

또한 es5에서 사용되던 전통적인 함수 기반의 클래스를 통하여 확장할 수도 있습니다.

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

class Dog extends Animal {
  speak() {
    console.log(this.name + ' barks.');
  }
}

var d = new Dog('Mitzie');
d.speak();

클래스는 생성자가 없는 객체(non-constructible)을 확장할 수 없습니다. 만약 기존의 생성자가 없는 객체을 확장하고 싶다면, 이 메소드를 사용하세요. Object.setPrototypeOf():

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

class Dog {
  constructor(name) {
    this.name = name;
  }
  speak() {
    console.log(this.name + ' barks.');
  }
}
Object.setPrototypeOf(Dog.prototype, Animal);

var d = new Dog('Mitzie');
d.speak();

Sub classing built-in objects

TBD

Species

아마 배열을 상속 받은 MyArray 클래스에서 Array를 반환하고 싶을 것입니다. Species 패턴은 기본 생성자를 덮어쓰도록 해줍니다.

예를 들어, map()과 같은 기본 생성자를 반환하는 메서드를 사용할 때 이 메서드가 MyArray 객체 대신 Array 객체가 반환하도록 하고 싶을 것입니다. Symbol.species 심볼은 이러한 것을 가능하게 해줍니다:

class MyArray extends Array {
  // 부모 Array 생성자로 종류 덮어쓰기
  static get [Symbol.species]() { return Array; }
}
var a = new MyArray(1,2,3);
var mapped = a.map(x => x * x);

console.log(mapped instanceof MyArray); // false
console.log(mapped instanceof Array);   // true

super 를 통한 상위 클래스 호출

super 키워드는 객체의 부모가 가지고 있는 함수들을 호출하기 위해 사용됩니다..

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.');
  }
}

ES5 프로토타입 상속 문법과 ES6 클래스 상속 문법의 비교

ES6 (ES2015):

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.');
  }
}

ES5:

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.');
};

 

 

Mix-ins

추상 서브 클래스 또는 믹스-인은 클래스를 위한 템플릿입니다. ECMAScript 클래스는 하나의 단일 슈퍼클래스만을 가질 수 있으며, 예를 들어 툴링 클래스로부터의 다중 상속은 불가능합니다. 기능은 반드시 슈퍼클래스에서 제공되어야 합니다.

슈퍼클래스를 인자로 받고 이 슈퍼클래스를 확장하는 서브클래스를 생성하여 반환하는 함수를 사용하여 ECMAScript에서 믹스-인을 구현할 수 있습니다:

아래는 두 함수는 어떤 클래스를 인자로 받고 그 클래스를 상속한 새로운 익명 클래스를 리턴하는 arrow function 입니다.

var calculatorMixin = Base => class extends Base {
  calc() { }
};

var randomizerMixin = Base => class extends Base {
  randomize() { }
};

위 믹스-인을 사용하는 클래스는 다음과 같이 작성할 수 있습니다:

class Foo { }
class Bar extends calculatorMixin(randomizerMixin(Foo)) { }

명세서


+ Recent posts