1. 클래스와 인스턴스
1) 클래스
ex) 음식 - 고기
- 채소
- 과일 - 배
- 사과
- 바나나
→ 음식이라는 범주 안에 고기, 채소, 과일 / 과일이라는 범주 안에 배, 사과, 바나나
→ 음식과 과일은 어떤 사물의 공통 속성을 모아 정의한 것이고, 배, 사과, 바나나는 실존하여 만질 수 있는 것
→ 음식과 과일은 '클래스'이고 클래스 중 음식이 상위 개념, 과일이 하위 개념이므로 음식이 superClass, 과일이 subClass
→ 클래스 내부에서 실존하는 객체는 '인스턴스', 인스턴스는 인스턴스를 포함하고 있는 클래스의 속성을 모두 가짐
2. 자바스크립트의 클래스
- 자바 스크립트는 프로토타입 기반 언어로 클래스의 개념이 존재하지 않지만, 프로토타입 체이닝 등으로 상속을 구현 가능
(생성자 함수 Array를 new와 함께 사용하여 새로운 인스턴스를 만들면, 인스턴스는 Array의 prototype 내부 요소들을 사용 가능)
- 이때, 인스턴스에 상속되는 프로퍼티가 있고(prototype 내부의 프로퍼티), 인스턴스에 상속되지 않는 프로퍼티가 있음
→ 인스턴스에 상속이 되지 않는 프로퍼티: 스태틱 멤버(static member(static methods + static properties))
→ 인스턴스에 상속되는 프로퍼티: 프로토타입 메서드
- 아래 사진에서 prototype 내부의 메서드(push(), pop() 등)만 [1, 2]라는 인스턴스가 사용가능
- 그 외, from(), isArray(), arguments, length 등은 [1, 2]에서 사용이 불가능
var Rectangle = function (width, height) {
this.width = width;
this.height = height;
}
// 생성자 함수 Rectangle의 prototype 내에 getArea 메서드 생성
Rectangle.prototype.getArea = function () {
return this.width * this.height;
};
// 생성자 함수 Rectangle의 prototype 내부에가 아닌 static method로 isRectangle 함수 생성
Rectangle.isRectangle = function (instance) {
return instance instanceof Rectangle && instance.width > 0 && instance.height > 0;
};
// 생성자 함수 Rectangle로 생성한 instance rect1
var rect1 = new Rectangle(3, 4)
// 인스턴스 rect1에서 Rectangle의 prototype에 있던 getArea함수가 정상 작동함
console.log(rect1.getArea()); // 12
// 인스턴스 rect1에서 Rectangle의 static methos인 isRectangle은 작동x
console.log(rect1.isRectangle(rect1)); // Error
// 생성자 함수 Rectangle에서는 당연히 Rectangle의 static method인 isRectangle이 작동함
console.log(Rectangle.isRectangle(rect1)); // True
- 클래스는 static method를 통해 호출할 때는, 자신만 사용할 수 있는 것을 사용하는 것으로, 하나의 개체로 취급
- 앞서 배웠던 것처럼 하위 개념에 자신의 특성을 부여하는 경우에 클래스는 틀의 역할을 하는 추상적인 개념
3. 클래스 상속
1) 기본 구현
- 프로토타입 체인을 활용해 클래스 상속을 구현
var Grade = function () {
var args = Array.prototype.slice.call(arguments);
for (var i = 0; i < args.length; i++) {
this[i] = args[i];
}
this.length = args.length;
};
Grade.prototype = [];
var g = new Grade(100, 80);
- Grade의 prototype에 []을 참조시켜 임의로 배열로 선언
- 따라서, Grade는 Array에 프로토타입 체이닝되어 Array의 prototype을 참조할 수 있게됨
- 하지만 세부적으로 완벽한 superclass와 subclass의 구현이 이루어지지 않음
→ length 프로퍼티가 삭제 가능한 점
→ Grade.prototype에 빈 배열을 참조시킨 점
// 90을 유사 배열 객체인 g에 Array의 메서드 push를 이용하여 잘 적용됨
g.push(90);
console.log(g); // Grade{ 0: 100, 1: 80, 2: 90, length: 3 }
// 하지만, g의 생성자 함수 Grade를 통해 선언되었던 length는 삭제가 가능
delete g.length;
g.push(70);
console.log(g); // Grade{ 0: 70, 1: 80, 2: 90, length: 1 }
→ 70을 push했을 때 0번째 인덱스에 70이 들어가고 length가 1이 된 이유
→ g.__proto__, 즉 Grade.prototype이 빈 배열 []을 가리키고, g.length가 없어서 참조한 g.__proto__.length는 Array의 length 메서드가 적용됨
→ 따라서, 빈 배열에 값(70)을 할당하고, 이 배열의 길이를 읽어들인 것
→ Grade.prototype을 빈 배열 []이 아닌 요소가 있는 배열로 정의했다면
Grade.prototype = [ 'a', 'b', 'c', 'd' ];
var g = new Grade(100, 80);
// push는 그대로 정상 작동
g.push(90);
console.log(g); // Grade{ 0: 100, 1: 80, 2: 90, length: 3 }
// g.length가 delete 되는 것도 그대로
// 하지만, g.lenght가 없어져서 참조하게 되는 g.__proto__가 요소가 이미 4개가 있는 배열이므로
// 70을 push하면 70은 인덱스가 4인 곳에 들어가고, length도 기존 4에서 1이 추가된 5로 출력됨
delete g.length;
g.push(70);
console.log(g); // Grade{ 0: 100, 1: 80, 2: 90, ___ 4: 70, length: 5 }
- 이처럼 클래스의 값(메서드 등)이 인스턴스의 동작에 영향을 미치면 클래스의 추상성이 없어짐
- 클래스는 인스턴스가 사용할 메서드(속성)만 지닌 추상적인 틀로 작용하게끔 작성해야 함
- 다른 예제
// 직사각형을 만드는 생성자 함수, 가로와 세로 길이가 인자로 필요함
var Rectangle = function (width, height) {
this.width = width;
this.height = height;
};
Rectangle.prototype.getArea = function () {
return this.width * this.height;
};
var rect = new Rectangle(3, 4);
console.log(rect.getArea()); // 12
// 정사각형을 만드는 생성자 함수, 가로 길이 하나만 있으면 세로 길이는 똑같으므로 필요 x
var Square = function (width) {
this.width = width;
};
Square.prototype.getArea = function () {
return this.width * this.width;
};
var sq = new Square(5);
console.log(sq.getArea()); // 25
→ Rectangle과 Square의 공통적인 요소(인자 width, 함수 getArea()의 비슷한 패턴)를 사용하여 상속관계 형성
// Square 함수를 Rectangle 함수와 똑같은 형태로 변형
...
var Square = function (width) {
this.width = width;
this.height = width;
};
Square.prototype.getArea = function () {
return this.width * this.height;
};
...
// 변형한 Square 함수를 새로 쓰지 않고 아예 Rectangle 함수를 끌어와서
// call을 통해 Rectangle 함수에 Square 함수의 인자를 전달(width 자리에 width, height 자리에 width 전달)
// prototype 또한 Rectangle 생성자 함수를 사용하여 똑같이 생성
...
var Square = function (width) {
Rectangle.call(this, width, width);
};
Square.prototype = new Rectangle();
...
→ new Square로 생성한 인스턴스인 sq의 디렉터리 구조에서 Rectangle의 prototype을 참조하는 것을 확인할 수 있음
→ 앞선 예제의 문제가 아직 존재
→ Square의 prototype에 값이 존재하여 sq.width나 sq.height를 delete 한다면 Square.prototype.width의 값이 대신 계산되어 엉뚱한 결과 출력
→ 또한, sq.constructor는 Rectangle을 가리키므로 정사각형이 아닌 직사각형도 적용이 가능해지는 등 구조적 안전성 감소
'Front-end > Javascript' 카테고리의 다른 글
[코어 자바스크립트] 07. 클래스(2) (0) | 2023.01.02 |
---|---|
[코어 자바스크립트] 06. 프로토타입(2) (1) | 2022.12.27 |
[코어 자바스크립트] 06. 프로토타입(1) (0) | 2022.12.26 |
[코어 자바스크립트] 05. 클로저(3) (0) | 2022.12.21 |
[코어 자바스크립트] 05. 클로저(2) (0) | 2022.12.21 |