코어 자바스크립트 책을 읽고 배운 내용을 바탕으로 작성되었다.
class
: 객체 지향 프로그래밍에서 구체적인 instance
를 생성하기 위해 변수와 메소드를 정의하는 일종의 틀이다.instance
: 클래스를 통해 만든 객체, 클래스의 속성을 지니는 실존하는 개체 (구체적인 예시)superclass
: 상위 클래스, subclass
: 하위 클래스class
의 멤버 (프로퍼티, 메서드) 중 instance
가 상속하는지 (참조하는지)에 대한 여부에 따라 static member
와 prototype member
로 나뉜다.// Constructor (Class)
const Retangle = function (width, height) {
this.width = width;
this.height = height;
}
// prototype method
Rectangle.prototype.getArea = function () {
return this.width * this.height;
}
// static method
Rectangle.isRectangle = function (instance) {
return instance instanceof Rectangle && instance.width > 0 && instance.height> 0;
}
const rect1 = Rectangle(3, 4);
console.log(rect1.getArea()); // 12
console.log(rect1.isRectangle(rect1)); // Error
console.log(Rectangle.isRectangle(rect1)); // true
getArea
메서드는 Constructor
의 prototype
프로퍼티 내부에 있는 메서드이므로 prototype
프로퍼티는 참조하고 있는 instance
의 __proto__
프로퍼티를 통해 getArea
메서드를 호출할 수 있다.__proto__
프로퍼티는 생략 가능하므로 instance
에서 직접 호출할 수 있다.getArea
메서드처럼 Constructor
의 prototype
프로퍼티 내부에 있어 instance
에서 직접 호출할 수 있는 메서드를 prototype method
라고 한다.isRectangle
메서드는 Constructor
의 prototype
프로퍼티 내부에 있지 않는 static method
이므로 instance
에서 직접 호출할 수 없다.rect1
인스턴스에서 isRectangle
메서드를 검색할 때 우선 rect1
에 해당 메서드가 있는지 검색하고, 없으니 rect1.__proto__
를 검색하고 없으니까, rect1.__proto__.__proto__(= Object.prototype)
를 검색한다. (Prototype Chain
을 통해 검색)static method
는 생성자 함수를 통해서만 호출할 수 있다.Prototype Chain
을 활용해 클래스 상속을 구현할 수 있다.const Retangle = function (width, height) {
this.width = width;
this.height = height;
}
Rectangle.prototype.getArea = function () {
return this.width * this.height;
}
const rect1 = Rectangle(3, 4);
console.log(rect1.getArea()); // 12
const Square = function (width) {
Rectangle.call(this, width, width);
}
Square.prototype = new Rectange();
const sq = new Square(5);
Rectangle.prototype
내부의 메서드를 상속받기 위해 Square.prototype
객체에 Rectangle instance
부여sq.__proto__
= Square.prototype
= Rectangle's instance
Square.prototype
에 값이 들어있다.Square.prototype.width (또는 height)
에 값을 부여하고 sq.width (또는 height)
를 삭제한다면 Prototype Chaining
에 의해 엉뚱한 값이 나올 수 있다.sq.constructor
== Rectangle
sq.__proto__.constructor
= Square.prototype.constructor
= Rectangle
💡 하위 클래스를 삼을 생성자 함수의 prototype
에 상위 클래스의 instance
를 부여하는 것만으로 기본적인 클래스 상속을 구현할 수 있지만 구조적으로 안전성이 떨어진다.
1) 첫번째 방법: 하위 클래스를 삼을 생성자 함수의 prototype
에 상위 클래스의 instance
를 부여하고 나서 하위 클래스의 prototype
내부에 존재하는 프로퍼티들을 전부 지우고 하위 클래스의 prototype
를 freeze
하는 방법
const extendClass1 = function (SuperClass, SubClass, subMethods){
SubClass.prototype = new SuperClass();
for (let prop in SubClass.prototype){
if (SubClass.prototype[prop].hasOwnProperty(prop)){
delete SubClass.prototype[prop];
}
}
if (subMethods){
for (let method in subMethods){
SubClass.prototype[method] = subMethods[method];
}
}
Object.freeze(SubClass.prototype);
return SubClass;
}
2) 두번째 방법: 좀 더 대중적인 방법으로 하위 클래스의 prototype
에 직접 상위 클래스의 instance
를 부여하는 대신 아무런 프로퍼티를 생성하지 않는 빈 생성자 함수(Bridge
)를 하나 더 만들어서 빈 생성자 함수의 prototype
에 상위 클래스의 prototype
를 부여하고, 하위 클래스의 prototype
에는 Bridge
의 instance
를 할당한다.
const extendClass2 = (function (SuperClass, SubClass, subMethods){
const Bridge = function () {};
return function (){
Bridge.prototype = SuperClass.prototype;
SubClass.prototype = new Bridge ();
if (subMethods){
for (let method in subMethods){
SubClass.prototype[method] = subMethods[method];
}
}
Object.freeze(SubClass.prototype);
return SubClass;
};
})();
Bridge
를 만들고 클로저를 활용하여 Bridge
를 기억하는 함수를 리턴함으로써 불필요한 메모리에 불필요한 함수 선언을 줄였다.3) 세번째 방법: Object.create()
이용
Object.create()
는 proto
라는 인자를 반드시 필요로 한데 이 인자는 새로 만든 객체의 프로토타입이어야 할 객체이다.prototype
의 __proto__
객체가 상위 클래스의 prototype
을 바라보되, 상위 클래스의 instance
가 되지 않는다.const extendClass3 = function (SuperClass, SubClass, subMethods){
SubClass.prototype = Object.create(SuperClass.prototype);
if (subMethods){
for (let method in subMethods){
SubClass.prototype[method] = subMethods[method];
}
}
Object.freeze(SubClass.prototype);
return SubClass;
}
아이디어
💡
subclass
의prototype
내부의__proto__
객체가superclass
의prototype
을 참조하고,subclass
의prototype
에는 불필요한 인스턴스 프로퍼티가 남아 있지 않도록 상속을 구현하면 된다.
subclass
의 prototype
객체 내부의 constructor
프로퍼티가 원래의 subclass
를 바라보도록 하면 된다.const extendClass1 = function (SuperClass, SubClass, subMethods){
SubClass.prototype = new SuperClass();
for (let prop in SubClass.prototype){
if (SubClass.prototype[prop].hasOwnProperty(prop)){
delete SubClass.prototype[prop];
}
}
SubClass.prototype.constructor = SubClass;
if (subMethods){
for (let method in subMethods){
SubClass.prototype[method] = subMethods[method];
}
}
Object.freeze(SubClass.prototype);
return SubClass;
}
const extendClass2 = (function (SuperClass, SubClass, subMethods){
const Bridge = function () {};
return function (){
Bridge.prototype = SuperClass.prototype;
SubClass.prototype = new Bridge ();
SubClass.prototype.constructor = SubClass;
if (subMethods){
for (let method in subMethods){
SubClass.prototype[method] = subMethods[method];
}
}
Object.freeze(SubClass.prototype);
return SubClass;
};
})();
const extendClass3 = function (SuperClass, SubClass, subMethods){
SubClass.prototype = Object.create(SuperClass.prototype);
SubClass.prototype.constructor = SubClass;
if (subMethods){
for (let method in subMethods){
SubClass.prototype[method] = subMethods[method];
}
}
Object.freeze(SubClass.prototype);
return SubClass;
}
super
구현하기super
메서드 추가const extendClass = function (SuperClass, SubClass, subMethods){
SubClass.prototype = Object.create(SuperClass.prototype);
SubClass.prototype.constructor = SubClass;
SubClass.prototype.super = function (propName){
const self = this;
if (!propName) return function (){
SuperClass.apply(self, arguments);
};
const prop = SuperClass.prototype[propName];
if (typeof prop !== "function") return prop;
return function () {
return prop.apply(self, arguments);
};
}
if (subMethods){
for (let method in subMethods){
SubClass.prototype[method] = subMethods[method];
}
}
Object.freeze(SubClass.prototype);
return SubClass;
}
const Rectangle = function (width, height) {
this.width = width;
this.height = height;
}
Rectangle.prototype.getArea = function () {
return this.width * this.height;
}
const Square= extendClass(
Rectangle,
function (width){
this.super()(width, width);
}, {
getArea: function (){
console.log('size if :', this.super("getArea")());
}
}
);
const sq = new Square(10);
sq.getArea(); // size is : 100 (subclass의 메서드 실행)
console.log(sq.super("getArea")()); // 100 (superclass의 메서드 실행)
superclass
의 생성자 함수에 접근하고자 할 때는 this.super()
, superclass
의 프로토타입 메서드에 접근하고자 할 때는 this.super(propName)
와 같이 사용하면 된다.const ES5 = function (name) {
this.name = name;
}
ES5.staticMethod = function () {
return this.name + 'staticMethod';
}
ES5.prototype.method = function () {
return this.name + 'method';
}
const ES6 = class {
// 생성자
constructor (name) {
this.name = name;
}
// static method : 생성자 함수(클래스)만이 호출할 수 있음.
static staticMethod () {
return this.name + 'staticMethod';
}
// prototype method
method () {
return this.name + 'method';
}
};
reference error
가 발생한다.// 클래스 선언
class Person {
}
// 클래스 표현식
const person = class Person {
};
syntax error
가 발생한다.class Person {
constructor (name, age){
this.name = name;
this.age = age;
}
greet() {
console.log(`Hi my name is ${this.name} and I'm ${this.age} years old`);
}
farewell() {
console.log("goodbye friend");
}
}
const alberto = new Person ("Alberto", 26);
alberto.greet(); // Hi my name is Alberto and I'm 26 years old
alberto.farewell(); // goodbye friend
static method
: 생성자 함수 (클래스) 통해서만 접근할 수 있음. 인스턴스에서 호출할 수 없는 메서드
static
키워드를 붙여 정적 메서드 생성class Person {
constructor (name, age){
this.name = name;
this.age = age;
}
static info () {
console.log("I am a Person class, nice to meet you");
}
}
const alberto = new Person ("Alberto", 26);
alberto.info(); // TypeError
Person.info(); // I am a Person class, nice to meet you
setter
와 getter
메서드를 사용하여 클래스 내에 값을 설정하거나 가져올 수 있다.
set
이나 get
키워드를 붙여 setter
나 getter
메서드 생성class Person {
constructor (name, surname){
this.name = name;
this.surname = surname;
this.nickname = "";
}
set nicknames(value){
this.nickname = value;
console.log(this.nickname);
}
get nicknames(){
console.log(`Your nickname is ${this.nickname}`);
}
}
const alberto = new Person("Alberto", "Montalesi");
// setter 호출
alberto.nicknames = "Albi"; // Albi
// getter 호출
alberto.nicknames; // "Your nickname is Albi"
extends
키워드를 이용하여 클래스 상속을 구현할 수 있다. (모든 속성과 메서드를 상속함)class Person {
constructor (name, age){
this.name = name;
this.age = age;
}
greet() {
console.log(`Hi my name is ${this.name} and I'm ${this.age} years old`);
}
}
class Adult extends Person {
constructor (name, age, work){
this.name = name;
this.age = age;
this.work = work;
}
}
const alberto = new Adult("Alberto", 26, "softward developer"); // ReferenceError
Adult
클래스는 Person
으로부터 상속받는 클래스이기 때문에 Adult
를 만들기에 앞서 Person
을 만들어야 한다.super()
를 호출하여 상위 클래스를 생성해야 한다.class Person {
constructor (name, age){
this.name = name;
this.age = age;
}
greet() {
console.log(`Hi my name is ${this.name} and I'm ${this.age} years old`);
}
}
class Adult extends Person {
constructor (name, age, work){
super(name, age);
this.work = work;
}
}
const Rectangle = class {
constructor(width, height) {
this.width = width;
this.height = height;
}
getArea() {
return this.width * this.height;
}
};
const Square = class {
constructor(width) {
super(width, width);
}
getArea() {
console.log('size is: ', super.getArea());
}
};
constructor
내부에서 super
키워드를 함수처럼 사용하여 superclass
의 constructor
실행한다.constructor
를 제외한 다른 메서드에서는 super
키워드를 객체처럼 사용할 수 있고, 이때 객체는 Superclass.prototype
을 바라본다.this
는 super
가 아니라 원래의 this
를 그대로 따른다.class
는 어떤 사물의 공통 속성을 모아 정의한 추상적인 개념 (구체적인 instance
를 생성하기 위해 변수와 메소드를 정의하는 일종의 틀)이고, instance
는 클래스의 속성을 지니는 구체적인 사례이다.superclass
)의 조건을 충족하면서 더 구체적인 조건이 추가된 것이 하위 클래스 (subclass
)이다.prototype
내부에 정의된 메서드를 prototype method
라고 하고, 이들은 instance
가 마치 자신의 것처럼 호출할 수 있다. static method
라고, 이들은 인스턴스에서 호출할 수 없고, 클래스(생성자 함수)를 통해서만 호출할 수 있다.subclass
의 prototype
에 superclass
의 instance
를 부여한 다음, subclass
의 prototype
내부 프로퍼티를 모두 삭제하는 방법Bridge
) 활용Object.create()
이용subclass
의 prototype
의 constructor
프로퍼티가 subclass
를 바라보도록 해야 한다.subclass
의 prototype
프로퍼티 내부의 __proto__
프로퍼티가 superclass
의 prototype
프로퍼티를 참조하도록 하고, subclass
의 prototype
내부에는 불필요한 인스턴스 프로퍼티가 존재하지 않아야 한다.super
구현