← Back to NOTES 🌰 ← Effective JavaScript 🤿
← 아이템 36 - 인스턴스의 상태는 인스턴스 객체에만 저장하라
상속 관계에서 하위 클래스 생성자는 상위 클래스의 생성자를 명시적으로 호출해야 한다. 이를 통해 상위 클래스에서 정의된 인스턴스 프로퍼티가 올바르게 초기화되며, 하위 클래스 프로토타입 생성 시에는 Object.create()를 사용하여 상위 클래스 생성자를 호출하지 않도록 해야 한다.
this 바인딩을 통한 올바른 프로퍼티 설정Child.prototype = Object.create(Parent.prototype)Child.prototype.constructor = ChildParent.call(this, ...args)// 상위 클래스: Scene (장면을 관리하는 클래스)
function Scene(context, width, height, images) {
this.context = context;
this.width = width;
this.height = height;
this.images = images;
this.actors = [];
}
Scene.prototype.register = function(actor) {
this.actors.push(actor);
};
Scene.prototype.unregister = function(actor) {
var i = this.actors.indexOf(actor);
if (i >= 0) {
this.actors.splice(i, 1);
}
};
Scene.prototype.draw = function() {
this.context.clearRect(0, 0, this.width, this.height);
for (var i = 0; i < this.actors.length; i++) {
this.actors[i].draw();
}
};
// 상위 클래스: Actor (모든 배우 객체의 기본 클래스)
function Actor(scene, x, y) {
this.scene = scene;
this.x = x;
this.y = y;
scene.register(this); // 장면에 자동으로 등록
}
Actor.prototype.moveTo = function(x, y) {
this.x = x;
this.y = y;
this.scene.draw(); // 이동 후 장면 다시 그리기
};
Actor.prototype.exit = function() {
this.scene.unregister(this);
this.scene.draw();
};
Actor.prototype.draw = function() {
if (this.scene.images && this.scene.images[this.type]) {
var image = this.scene.images[this.type];
this.scene.context.drawImage(image, this.x, this.y);
}
};
Actor.prototype.width = function() {
return this.scene.images && this.scene.images[this.type]
? this.scene.images[this.type].width : 0;
};
Actor.prototype.height = function() {
return this.scene.images && this.scene.images[this.type]
? this.scene.images[this.type].height : 0;
};
// 올바른 하위 클래스 구현: SpaceShip
function SpaceShip(scene, x, y) {
// 중요: 상위 클래스 생성자를 명시적으로 호출
Actor.call(this, scene, x, y);
// 하위 클래스만의 고유 프로퍼티 초기화
this.points = 0;
}
// 프로토타입 상속 설정 (올바른 방법)
SpaceShip.prototype = Object.create(Actor.prototype);
SpaceShip.prototype.constructor = SpaceShip; // 생성자 참조 복원
// SpaceShip 고유 프로퍼티와 메서드
SpaceShip.prototype.type = "spaceship";
SpaceShip.prototype.scorePoint = function() {
this.points++;
console.log('점수 획득! 현재 점수:', this.points);
};
SpaceShip.prototype.left = function() {
this.moveTo(Math.max(this.x - 10, 0), this.y);
};
SpaceShip.prototype.right = function() {
var maxWidth = this.scene.width - this.width();
this.moveTo(Math.min(this.x + 10, maxWidth), this.y);
};
// 잘못된 프로토타입 설정 방법의 문제점
console.log('=== 잘못된 상속 방법의 문제 ===');
function BadSpaceShip(scene, x, y) {
Actor.call(this, scene, x, y);
this.points = 0;
}
// 문제가 있는 방법: new 연산자 사용
// BadSpaceShip.prototype = new Actor();
// 이렇게 하면 Actor 생성자가 호출되지만 인자가 없어서 undefined 프로퍼티 생성
console.log('올바른 상속 구조 확인:');
// 가상의 캔버스 및 이미지 설정 (실제 환경에서는 DOM에서 가져옴)
var mockCanvas = {
getContext: function() {
return {
clearRect: function() { console.log('화면 지우기'); },
drawImage: function() { console.log('이미지 그리기'); }
};
}
};
var mockImages = {
spaceship: { width: 32, height: 32 }
};
var scene = new Scene(mockCanvas.getContext('2d'), 800, 600, mockImages);
var ship = new SpaceShip(scene, 100, 100);
console.log('SpaceShip 인스턴스 프로퍼티:');
console.log('ship.scene:', !!ship.scene); // true
console.log('ship.x:', ship.x); // 100
console.log('ship.y:', ship.y); // 100
console.log('ship.points:', ship.points); // 0
// 상속 체인 확인
console.log('\\\\n상속 체계 확인:');
console.log('ship instanceof SpaceShip:', ship instanceof SpaceShip); // true
console.log('ship instanceof Actor:', ship instanceof Actor); // true
console.log('Object.getPrototypeOf(ship) === SpaceShip.prototype:',
Object.getPrototypeOf(ship) === SpaceShip.prototype); // true
// 또 다른 하위 클래스 예시: Alien
function Alien(scene, x, y, direction, speed, strength) {
// 상위 클래스 생성자 호출
Actor.call(this, scene, x, y);
// Alien 고유 프로퍼티
this.direction = direction;
this.speed = speed;
this.strength = strength;
this.damage = 0;
}
// 프로토타입 상속 설정
Alien.prototype = Object.create(Actor.prototype);
Alien.prototype.constructor = Alien;
Alien.prototype.type = "alien";
Alien.prototype.move = function() {
var newX = this.x + this.direction * this.speed;
// 화면 경계 확인
if (newX < 0 || newX > this.scene.width - this.width()) {
this.direction *= -1; // 방향 전환
} else {
this.moveTo(newX, this.y);
}
};
Alien.prototype.takeDamage = function(amount) {
this.damage += amount;
if (this.damage >= this.strength) {
this.exit(); // 파괴되면 장면에서 제거
return true; // 파괴됨
}
return false; // 아직 살아있음
};
// 복합 상속 예시 테스트
console.log('\\\\n=== 복합 상속 테스트 ===');
var alien1 = new Alien(scene, 200, 50, 1, 2, 3);
var alien2 = new Alien(scene, 300, 50, -1, 3, 2);
console.log('생성된 alien 수:', scene.actors.length);
alien1.move();
alien1.takeDamage(1);
console.log('alien1 손상 정도:', alien1.damage + '/' + alien1.strength);
// 메서드 오버라이딩 예시
SpaceShip.prototype.draw = function() {
// 상위 클래스 메서드 호출
Actor.prototype.draw.call(this);
// 추가적인 SpaceShip만의 그리기 로직
if (this.scene.context && this.points > 0) {
console.log('점수 표시:', this.points);
}
};
// 오버라이딩된 메서드 테스트
console.log('\\\\n=== 메서드 오버라이딩 테스트 ===');
ship.scorePoint();
ship.draw();
// 상속 구조의 메모리 효율성 확인
console.log('\\\\n=== 메모리 효율성 확인 ===');
var ship1 = new SpaceShip(scene, 50, 50);
var ship2 = new SpaceShip(scene, 150, 50);
console.log('메서드 공유 확인:');
console.log('ship1.moveTo === ship2.moveTo:', ship1.moveTo === ship2.moveTo); // true (Actor에서 상속)
console.log('ship1.scorePoint === ship2.scorePoint:', ship1.scorePoint === ship2.scorePoint); // true (SpaceShip에서 정의)
console.log('인스턴스 프로퍼티 독립성:');
console.log('ship1.points === ship2.points:', ship1.points === ship2.points); // true (둘 다 0)
ship1.scorePoint();
console.log('점수 변경 후:', ship1.points, ship2.points); // 1, 0 (독립적)
// 깊은 상속 체인 예시
function PoweredSpaceShip(scene, x, y, energy) {
SpaceShip.call(this, scene, x, y); // SpaceShip 생성자 호출
this.energy = energy;
this.maxEnergy = energy;
}
PoweredSpaceShip.prototype = Object.create(SpaceShip.prototype);
PoweredSpaceShip.prototype.constructor = PoweredSpaceShip;
PoweredSpaceShip.prototype.boost = function() {
if (this.energy >= 10) {
this.energy -= 10;
this.moveTo(this.x + 20, this.y); // 더 빠른 이동
console.log('부스트 사용! 남은 에너지:', this.energy);
} else {
console.log('에너지 부족!');
}
};
// 3단계 상속 테스트
var poweredShip = new PoweredSpaceShip(scene, 400, 400, 100);
console.log('\\\\n=== 3단계 상속 테스트 ===');
console.log('poweredShip instanceof PoweredSpaceShip:', poweredShip instanceof PoweredSpaceShip); // true
console.log('poweredShip instanceof SpaceShip:', poweredShip instanceof SpaceShip); // true
console.log('poweredShip instanceof Actor:', poweredShip instanceof Actor); // true
poweredShip.scorePoint(); // SpaceShip에서 상속
poweredShip.boost(); // PoweredSpaceShip 고유