[코어 자바스크립트] 07. 클래스(1)
1. 클래스와 인스턴스 1) 클래스 ex) 음식 - 고기 - 채소 - 과일 - 배 - 사과 - 바나나 → 음식이라는 범주 안에 고기, 채소, 과일 / 과일이라는 범주 안에 배, 사과, 바나나 → 음식과 과일은 어떤 사물
data-science-study.tistory.com
2) 클래스가 구체적인 데이터를 지니지 않게 하는 방법
- 클래스가 값을 가지면 그 하위 클래스에서 delete 등의 변화가 발생했을 때, 원하지 않는 값이 참조되는 등 상위 클래스가 하위 클래스에 관여하는 안전성의 문제가 계속 발생
- 따라서, 클래스가 구체적인 값을 지니지 않도록 해야 함
- 가장 쉬운 방법은 일단 만들고 나서, 프로퍼티를 일일히 지운 뒤, 새로운 프로퍼티를 추가할 수 없도록 freeze
delete Square.prototype.width;
delete Square.prototype.height;
Object.freeze(Square.prototype);
- 프로퍼티가 많은 경우, 반복 작업을 없애고 범용적인 함수로 만들어 쓰기
var extendClass1 = function (SuperClass, SubClass, subMethods) {
// SubClass의 prototype 내의 프로퍼티 삭제
SubClass.prototype = new SuperClass();
for (var prop in SubClass.prototype) {
if (SubClass.prototype.hasOwnProperty(prop)) {
delete Subclass.prototype[prop];
}
}
// 메서드가 있다면, SubClass에 넘겨주기
if (subMethods) {
for (var method in subMethods) {
SubClass.prototype[method] = submethod[method];
}
}
// 그 후 더이상 SubClass의 prototype에 새로운 프로퍼티를 추가할 수 없도록 freeze
Object.freeze(SubClass.prototype);
return SubClass;
};
var Square = extendClass1(Rectangle, function (width) {
Rectangle.call(this, width, width);
});
→ extnedClass1에 상위 클래스인 Rectangle과 Rectangle을 상속받는 SubClass인 Square를 익명함수로 전달
→ SubClass에 있는 SuperClass에서 상속받은 프로퍼티를 삭제하여 애초에 상위 클래스의 프로퍼티를 사용하도록 설정(width를 두 번 전달할 때, Square의 width, width가 아닌 Rectangle의 width, height로 전달되어, Square의 width가 삭제되어도 Rectangle의 width, height로써 저장되어 있는 값이 변하지 않아 결과도 변하지 않게 됨)
→ 메서드는 인스턴스가 사용가능하도록 SubClass에 넘겨주기
- 두 번째 방법으로, 아무 프로퍼티를 생성하지 않는 빈 생성자 함수(Bridge)를 만들어서 그 prototype이 SuperClass의 prototype을 바라보게 하고, SubClass의 인스턴스에는 Bridge의 인스턴스 할당
var Rectangle = function (width, height) {
this.width = width;
this.height = height;
};
Rectangle.prototype.getArea = function () {
return this.width * this.height;
};
var Square = function (width) {
Rectangle.call(this, width, width);
};
var Bridge = function () {};
Bridge.prototype = Rectangle.prototype;
Square.prototype = new.Bridge();
Object.freeze(Square.prototype);

→ Rectangle 자리를 Bridge가 대체하며 인스턴스를 제외한 프로토타입 경로에 구체적인 데이터 x
- 범용성을 고려하여 함수로 작성하면 다음과 같이 작성할 수 있음
var extendClass2 = (function () {
var Bridge = function () {};
return function (SuperClass, SubClass, subMethods) {
Bridge.prototype = SuperClass.prototype;
SubClass.prototype = new Bridge();
if (subMethods) {
for (var method in sybMethods) {
SubClass.prototype[method] = SubMethods[method];
}
}
Object.freeze(SubClass.prototype);
return SubClass;
};
})();
- 세 번째 방법, Object.create 사용
...
Square.prototype = Object.create(Rectangle.prototype);
Object.freeze(Square.prototype);
...
→ 이 방법으로 생선한 SubClass의 prototype의 __proto__는 SuperClass의 Prototype을 바라보지만, SuperClass의 인스턴스가 되지는 않음
- 위 세가지 방법 모두 SubClass.prototype의 __proto__가 SuperClass.prototype을 참조하고, SubClass.prototype에는 불필요한 인스턴스 프로퍼티가 남아있지 않아야 함
3) constructor 복구하기
- SubClass의 인스턴스의 constructor는 여전히 SuperClass를 가리킴
- 따라서, SubClass.prototype.constructor가 원래의 SubClass를 바라보도록 해주어야 함
var extendClass1 = function (SuperClass, SubClass, subMethods) {
SubClass.prototype = new Superclass();
for (var prop in SubClass.prototype) {
if (SubClass.prototype.hasOwnProperty(prop)) {
delete SubClass.prototype[prop];
}
}
SubClass.prototype.constructor = SubClass;
if (SubMethods) {
for (var method in subMethods) {
SubClass.prototype[method] = subMethods[method];
}
}
Object.freeze(SubClass.prototype);
return SubClass;
}
var extendClass2 = (function () {
var Bridge = function () {};
return function (SuperClass, SubClass, subMethods) {
Bridge.prototype = SuperClass.prototype;
SubClass.prototype = new Bridge();
SubClass.prototype.constructor = SubClass;
if (subMethods) {
for (var method in sybMethods) {
SubClass.prototype[method] = SubMethods[method];
}
}
Object.freeze(SubClass.prototype);
return SubClass;
};
})();
var extendClass3 = function (SuperClass, SubClass, subMethods) {
SubClass.prototype = Object.create(Superclass.prototype);
SubClass.prototype.constructor = SubClass;
if (SubMethods) {
for (var method in subMethods) {
SubClass.prototype[method] = subMethods[method];
}
}
Object.freeze(SubClass.prototype);
return SubClass;
}
- 세가지의 각 방법에서 SubClass의 prototype을 SuperClass의 prototype으로 지정한 다음에 SubClass.prototype.constructor를 SubClass로 다시 선언해주는 코드를 통해 SubClass의 constructor가 다시 자시능ㄹ 바라보도록 함
- 상위 클래스는 이제 완전히 상속만을 위해 추상화 됨
4) 상위 클래스에의 접근 수단
- 하위 클래스에서 상위 클래스의 메서드 실행 결과를 바탕으로 추가 작업 수행하려고 할 때, 하위 클래스에서 상위 클래스의 prototype의 메서드에 접근할 수 있는 수단
var extendClass = function (SuperClass, SubClass, subMethods) {
SubClass.prototype = Object.create(SuperClass.prototype);
SubClass.prototype.constructor = SubClass;
// 새롭게 super 메서드의 동작 추가
SubClass.prototype.super = function (propName) {
var self = this;
if (!propName) return function () { // 인자가 없다면 SuperClass 생성자 함수에 클로저를 통해 접근
SuperClass.apply(self, arguments);
}
var prop = SuperClass.prototype[propName];
if (typeof prop !== 'function') return prop; // 사용하려는 SuperClass의 프로퍼티의 타입이 함수가 아니면 사용하려는 값을 그대로 반환
return function () { // 함수라면, 클로저를 통해 메서드에 접근
return prop.apply(self, arguments);
}
};
// 추가된 내용 끝
if (subMethods) {
for (var method in subMethods) {
SubClass.prototype[method] = subMethods[method];
}
}
Object.freeze(SubClass.prototype);
return SubClass;
};
- super 메서드: SuperClass의 생성자 함수에 접근하고자 할 때는 this.super(), SuperClass의 prototype에 접근하고자 할 때는 this.super(propName)
- 기존 this에 바인딩하는 문법을 썼다면 이제 this.super()(width, width)라고 함으로써 가독성 증가
var Rectangle = function (width, height) {
this.width = width;
this.height = height;
};
Rectangle.prototype.getArea = function () {
return this.width * this.height;
};
var Square = extendClass(
Rectangle,
function (width) {
this.super()(width, width);
}, {
getArea: function () {
console.log('size is :', this.super('getArea')());
}
}
);
var sq = new Square(10);
sq.getArea();
console.log(sq.super('getArea')());
4. ES6의 클래스 및 클래스 상속
- ES6의 클래스
// ES5
var ES5 = function (name) {
this.name = name;
};
ES5.staticMethod = function () {
return this.name + ' staticMethod';
};
ES5.prototype.method = function () {
return this.name + ' method';
};
var es5Instance = new ES5('es5');
console.log(ES5.staticMethod()); // ES5 staticMethod
console.log(es5Instance.method()); // es5 method
// ES6
var ES6 = class {
// 생성자 함수
constructor (name) {
this.name = name;
}
// static 메서드로, 생성자 함수(클래스) 자신만 호출 가능)
static staticMethod () {
return this.name + ' staticMethod';
}
// prototype 객체 내부에 할당되는 메서드, 인스턴스가 프로토타입 체이닝을 통해 호출 가능
method () {
return this.name + ' method';
}
};
var es6Instance = new ES6('es6');
console.log(ES6.staticMethod()); // ES6 staticMethod
console.log(es6Instance.method()); // es6 method
- ES6의 클래스 상속
var Rectangle = class {
constructor (width, height) {
this.width = width;
this.height = height;
}
getArea () {
return this.width * this.height;
}
};
var Square = class extends Rectangle { // Square가 Rectangle의 상속을 받게 하기 위하여 extends 추가
constructor (width) {
super(width, height); // constructor 내부의 super는 SuperClass의 constructor 실행
}
getArea () {
console.log('size is :', super.getArea()); // constructor 외부의 super는 객체처럼 사용 가능, 이때 객체는 SuperClass.prototype을 바라보고, this는 super가 아닌 원래의 this
}
};
'Front-end > Javascript' 카테고리의 다른 글
[코어 자바스크립트] 07. 클래스(1) (0) | 2022.12.29 |
---|---|
[코어 자바스크립트] 06. 프로토타입(2) (1) | 2022.12.27 |
[코어 자바스크립트] 06. 프로토타입(1) (0) | 2022.12.26 |
[코어 자바스크립트] 05. 클로저(3) (0) | 2022.12.21 |
[코어 자바스크립트] 05. 클로저(2) (0) | 2022.12.21 |