
모든 객체는 Object라는 이름을 가진 [[Prototype]]을 가진다.
[[Prototype]]용 getter,setter인 프로퍼티이다.
let animal ={
eat: "모든 동물은 먹는다."
}
let dog= {
bark: "멍멍"
}
dog.__proto__ = animal;
// animal이 dog의 프로토타입이 되도록 설정
console.log(dog.bark);
console.log(dog.eat);
우리는 여러 객체 리터럴을 만들 때 객체들이 공통된 프로퍼티와, 메소드를 가지고 있다면 중복을 제거하고 유지보수를 하기위해 생성자 함수를 생성하거나 클래스를 만들어 사용하였다.
코드 📄
function Human(name,face){
// 인스턴스 레벨
this.name =name;
this.face =face;
this.printNmae = ()=>{
console.log(`${this.name} : ${this.face}`);
}
}
const khw =new Human('khw','👦');
const kar =new Human('kar','👲');
// 원할 때 마다 객체 생성
console.log(khw);
// Human { name: 'khw', face: '👦', printNmae: [Function (anonymous)] }
다음과 같이 생성자 함수를 만들었을 때 우리는 필요할 때마다 생성자 함수를 이용해 새로운 객체를 만들 수 있다.
그런데 생성한 객체를 출력해보면 원하지 않은 printName() 메소드도 함께 출력되었다..
그 이유는 Human 생성자 함수안에 있는 프로퍼티,메소드들이 인스턴스 레벨이기 때문이다.
이렇게 객체를 생성할때마다 원하지 않는 값이 함께 출력되는 것은 메모리 낭비이다.
따라서 이러한 문제를 해결하려면 원하지 않는 값을 인스턴스 레벨이 아닌 프로토타입 레벨에 만들어 두고 필요할때만 사용할 수 있다.
코드 📄
function Human(name,face){
this.name =name;
this.face =face;
}
const khw =new Human('khw','👦');
const kar =new Human('kar','👲');
// 원할 때 마다 객체 생성
// 프로토타입 레벨에 함수 만들기
Human.prototype.printName= function(){
console.log(`${this.name} : ${this.face}`);
}
console.log(khw); // Human { name: 'khw', face: '👦' }
kar.printName(); // kar : 👲
코드 📄
// 프로토타입 레벨에 함수 만들기
Human.prototype.printName= function(){
console.log(`${this.name} : ${this.face}`);
}
console.log(khw); // Human { name: 'khw', face: '👦' }
kar.printName(); // kar : 👲
클래스처럼 인스턴스 레벨에서 동일한 이름으로 함수를 재정의 하는 것이다.
코드 📄
// 인스턴스 레벨에서의 오버라이딩
khw.printName = function(){
console.log('이름 얼굴 가림');
}
khw.printName(); // 이름 얼굴 가림
console.log(khw);
// Human { name: 'khw', face: '👦', printName: [Function (anonymous)] }
생성자 함수 이름에 프로퍼티와 메소드를 붙여 생성한다.
클래스의 static 키워드와 유사하다.
코드 📄
Human.MAX_AGE =100; // 정적 프로퍼티 생성
console.log(Human.MAX_AGE); // 100
console.log(khw.MAX_AGE); // undefined , 인스턴스 레벨에서 접근 불가
Human.hello = ()=>{ // 정적 메소드 생성
console.log('안녕');
}
Human.hello(); // 안녕
khw.hello(); // TypeError: khw.hello is not a function
객체지향 프로그래밍의 장점중 하나는 상속을 이용한 재사용성이다.
프로토타입을 이용해서 객체간에 상속이 가능하다.
코드 📄
function Human(name,face){
this.name =name;
this.face =face;
}
Human.prototype.printName= function(){
console.log(`${this.name} : ${this.face}`);
}
코드 📄
function Kid(name,face,brother){
this.brother =brother;
}
Kid.prototype = Object.create(Human.prototype);
// 프로토타입 레벨의 함수
Kid.prototype.play = function(){
console.log(`${this.brother} 같이 놀자~`);
}
그리고 또다른 생성자 함수가 있다.
Kid가 Human을 상속하기 위해 prototype을 이용할 수 있다.
즉 Kid가 가리키는 [[Prototype]]이 Human이라는 이름의 [[Prototype]]으로 바뀌면 되는 것이다.
이제 Kid는 Human을 상속하기 때문에 Human의 메소드인 printName도 사용 가능 하다.
코드 📄
const kid1 =new Kid('kar','👲','khw');
kid1.play(); // khw 같이 놀자~
kid1.printName(); // undefined :undefined
코드 📄
function Kid(name,face,brother){
// Class에서의 super(name,face) 와 유사
Human.call(this,name,face);
this.brother =brother;
}
코드 📄
const kid1 =new Kid('kar','👲','khw');
kid1.play(); // khw 같이 놀자~
kid1.printName(); // kar : 👲
객체 instanceof 생성자함수(객체)
객체가 생성자 함수(객체)에 상속되어 있는지 확인 할 수 있다.
console.log(kid1 instanceof Kid);// true
console.log(kid1 instanceof Human); // true
console.log(kid1 instanceof Object); // true
객체는 단 하나의 prototype만 가리킬 수 있다. 즉 부모는 하나뿐이다.
그럼에도 다양한 객체들의 함수들을 이용하고 싶을 때 Object.assign을 사용할 수 있다.
function Kid(name){
this.name =name;
}
const play = { // 객체 1
play: function(){
console.log(`${this.name}은 논다.`);
}
}
const study = { // 객체2
study : function(){
console.log(`${this.name}은 공부한다.`);
}
}
Object.assign(Kid.prototype,play,study);
// Kid의 prototype에 play,study객체의 속성들이 섞인다.
const kid1 =new Kid('ksw','👨🦲');
console.log(kid1);// Kid { name: 'ksw' }
kid1.play(); // ksw은 논다.
kid1.study(); // ksw은 공부한다.
여러 객체들의 함수를 다중 상속함으로서 하나의 prototype만 상속할 수 있는 단점을 커버한다.
정확히는 객체들의 함수들을 Kid의 prototype에 넣어주었기에 사용 할 수 있는 것이다.