이 챕터의 요약 미리보기
- 같은 클래스에 속하는 객체는 같은 프로토타입 객체에서 프로퍼티를 상속합니다.
- ES5 이전에는 일반적으로 먼저 생성자 함수를 정의한 뒤에야 클래스를 정의할 수 있었습니다.
function
키워드로 생성한 함수는prototype
프로퍼티를 가지고 있고, 이 프로퍼티 값은 함수를new
와 함께 생성자로 호출했을 때 생성되는 모든 객체의 프로토타입으로 사용됩니다.- 이
prototype
객체를 초기화해서 클래스에서 공유하는 메서드를 정의할 수 있습니다.- 서브클래스는 클래스 선언에
extends
키워드를 사용해 정의합니다.- 서브클래스는
super
키워드를 써서 슈퍼클래스의 생성자 또는 덮어쓰인 메서드를 호출할 수 있습니다.
자바스크립트에서 클래스는 같은 프로토타입 객체
에서 프로퍼티를 상속
하는 객체 집합니다. 따라서 프로토타입 객체가 클래스의 핵심 기능입니다.
function range(from, to){
let r = Object.create(range.methods);
r.from = from;
r.to = to;
return r;
}
range.methods = {
includes(x){ return this.from <= x && x <= this.to; },
*[Symbol.iterator](){
for(let x = Math.ceil(this.from); x <= this.to; x++) yield x;
},
toString(){ return`( ${this.from}...${this.to} )`; }
}
let r = range(1, 3);
r.includes(2) // true
r.toString() // '(1...3)'
[...r] // [1, 2, 3]
// isPrototypeOf 객체의 프로토타입 체인에 특정 프로토타입이 존재하는 지 확인
range.methods.isPrototypeOf(r); // true
함수 객체는 prototype 프로퍼티를 가집니다.
생성자 함수를 공유하는 객체는 모두 같은 객체를 상속하며, 따라서 같은 클래스의 멤버입니다.
function Range(from, to){
this.from = from;
this.to = to;
}
Range.prototype = {
includes: function(x){ return this.from <= x && x <= this.to; },
[Symbol.iterator]: function*(){
for(let x = Math.ceil(this.from); x <= this.to; x++) yield x;
},
toString: function(){ return`( ${this.from}...${this.to} )`; }
};
/* new와 Range() 생성자 함수 사용 */
let r = new Range(1, 3);
r.includes(2) // true
r.toString() // '(1...3)'
[...r] // [1, 2, 3]
Range.prototype 객체를 덮어쓰는 경우,
Range.prototype.constructor도 덮어씌워지므로, 객체 내부에 명확히
Range.prototype = {constructor: Range}
로 명시하거나
Range.prototype.includes = function(x){
return this.from <= x && x <= this.to;
}
메서드를 Range.prototype 객체 외부에서 확장하는 방법도 있다.
생성자와 new.target
함수 바디 안에서new.target
을 사용하면 함수가 생성자로 호출됐는지 알 수 있습니다.function Foo() { if (!new.target) throw "Foo() must be called with new"; console.log("Foo instantiated with new"); } Foo(); // throws "Foo() must be called with new" new Foo(); // logs "Foo instantiated with new"
class
키워드로 생성된 클래스는 new 없이 생성자를 호출할 수 없습니다.
- new.target은 new에 의해 직접 호출된 생성자를 가리킵니다.
- 이는 그 생성자가 부모 클래스에 있고 자식 생성자로부터 위임받은 경우도 그 경우입니다.
class A { constructor() { console.log(new.target.name); } } class B extends A { constructor() { super(); } } let a = new A(); // logs "A" let b = new B(); // logs "B"
instanceof
: 인스턴스 객체가 생성자 함수를 상속하는지 확인할 수 있습니다.다.
r instanceof Range // true;
// 직접 상속하지 않아도 prototype을 상속해도 true로 평가
function Strange(){}
Strange.prototype = Range.prototype;
new Strange() instanceof Range // true
let s = new Strange();
s instanceof Range // true
let F = function(){};
let p = F.prototype;
let c = p.constructor;
c === F // true
F.prototype.constructor === F // true
let o = new F();
o.constructor === F // true
class Range{
constructor(from, to){
this.from = from;
this.to = to;
}
includes(x){ return this.from <= x && x <= this.to; },
*[Symbol.iterator](){
for(let x = Math.ceil(this.from); x <= this.to; x++) yield x;
},
toString(){ return`( ${this.from}...${this.to} )`; }
};
let r = new Range(1, 3);
r.includes(2) // true
r.toString() // '(1...3)'
[...r] // [1, 2, 3]
다른 클래스를 상속하는 서브 클래스를 정의할 수 있다.
class Span extends Range{
constructor(start, length){
if(length >= 0){
super(start, start + length);
} else{
super(start+length, start);
}
}
}
인스턴스 객체에 상속되지 않는, 생성자 함수의 프로퍼티를 정의할 수 있습니다.
class Range{
constructor(from, to){
this.from = from;
this.to = to;
}
static parse(s){
console.log(`it's only for constructor`);
}
};
let r = new Range(1, 3);
Range.parse(); // it's only for constructor
r.parse(); // Uncaught TypeError: r.parse is not a function
클래스의 블럭 { } 내부 public field에서 바로 할당 = 한 변수는 인스턴스 객체의 프로퍼티로 호출 가능합니다.
class Buffer{
from = 'made by buffer';
size = 5;
}
let b = new Buffer();
b.from // 'made by buffer'
b.size // 5
반면에 #을 붙여서 private field 에서 할당한 변수는 class 바디 내부에서는 사용 가능하지만, 외부에서는 볼 수 없고 접근할 수도 없습니다.
즉 인스턴스 객체에서도 사용할 수 없습니다.
class Buffer{
#from = 'made by buffer';
#size = 5;
get size(){return this.#size * 2; }
}
let b = new Buffer();
b.size // 10
b.from // undefined
Buffer.size // undefined
객체를 생성 후 프로토타입의 프로퍼티가 바뀌더라도 상속 관계는 끊어지지 않습니다. 따라서 프로토타입 객체에 새로운 메서드를 추가하여 자바스크립트 클래스를 확장할 수 있습니다.
class People {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
class User extends People {}
People.prototype.greeting = function () {
console.log("hi");
};
let j = new User("Joy", 10);
j.greeting(); // hi
심지어 내장된 클래스의 프로토타입 객체(String.prototype, Object.prototype 등)에도 새로운 메서드를 추가할 수 있습니다.
하지만 추후 자바스크립트 새 버전에서 같은 이름의 메서드를 정의할 경우 혼란을 주거나 호환성 문제를 야기할 수 있으므로 지양하는 것이 좋습니다.