javascript- 프로토타입

현우.·2024년 6월 20일

JavaScript

목록 보기
27/31
post-thumbnail

객체의 프로토타입

모든 객체는 Object라는 이름을 가진 [[Prototype]]을 가진다.

  • 우리가 객체를 생성한 후 별도의 프로퍼티나 메소드를 만들지 않아도 가져다 사용할 수 있는 이유는 객체 내부에 숨겨진 [[Prototype]]에서 프로퍼티와 메소드를 찾아 사용하기 때문이다.
    이러한 동작 방식을 프로토타입 상속이라고 한다.
  • 외부에서 [[Prototype]]에 직접적으로 접근할 수 없는 대신
    __proto__, Object.getPrototypeOf(), Object.setPrototypeOf(),
    생성자 함수에서는 protoype으로 [[Prototype]]에 접근할 수 있다.

__proto__

[[Prototype]]용 getter,setter인 프로퍼티이다.

  • __proto__ 프로퍼티를 통해 [[Prototype]]에 접근하거나 [[Prototype]]을 세팅할 수 있다.
  • 근래에 들어서는 __proto__ 대신 getPrototypeOf(), setPrototypeOf()를 사용하는 추세이다.
let animal ={
  eat: "모든 동물은 먹는다."
}

let dog= {
  bark: "멍멍"
}

dog.__proto__ = animal;
// animal이 dog의 프로토타입이 되도록 설정

console.log(dog.bark);
console.log(dog.eat);

배열의 프로토타입

  • 모든 배열은 Array라는 이름의 [[Prototype]]을 가진다.
  • 또한 배열은 동시에 객체이기도 하기 때문에 Object라는 [[Prototype]]도 가진다.
  • 결국 배열은 Array, Object 프로퍼티, 메소드를 모두 사용 할 수 있다.

우리는 여러 객체 리터럴을 만들 때 객체들이 공통된 프로퍼티와, 메소드를 가지고 있다면 중복을 제거하고 유지보수를 하기위해 생성자 함수를 생성하거나 클래스를 만들어 사용하였다.

코드 📄

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 : 👲
  • 프로토타입 레벨에 함수를 만들려면 [[Prototype]]에 접근해 메소드를 직접 만들어야한다.
  • 생성자 함수에서 [[Prototype]]에 접근하려면 prototype 프로퍼티를 사용하면 된다.
  • [Prototype]]에 메소드를 만들고 필요할 때만 꺼내서 사용하면 된다.

오버라이딩

클래스처럼 인스턴스 레벨에서 동일한 이름으로 함수를 재정의 하는 것이다.

코드 📄

//  인스턴스 레벨에서의 오버라이딩
khw.printName = function(){
    console.log('이름 얼굴 가림');
}
khw.printName(); // 이름 얼굴 가림
console.log(khw); 
// Human { name: 'khw', face: '👦', printName: [Function (anonymous)] }
  • 인스턴스 레벨에서 printName() 메서드를 재정의 해주었기 때문에 인스턴스를 출력하면 다시 printName() 메서드도 출력된걸 확인할 수 있다.

정적 레벨의 프로퍼티,메소드 생성

생성자 함수 이름에 프로퍼티와 메소드를 붙여 생성한다.

클래스의 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
  • 그러나 undefined로 반환되는것을 알 수 있다.
    그 이유는 Human객체에 Kid의 name, face를 전달하지 않았기 때문이다.

코드 📄

function Kid(name,face,brother){
    // Class에서의 super(name,face) 와 유사
  	Human.call(this,name,face);
    this.brother =brother;
}
  • Class에서 super()로 부모의 생성자함수를 찾아가듯
    부모의 생성자에 자신의 name,face를 전달하면 된다.

코드 📄

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

여러 함수 상속 Mixin

객체는 단 하나의 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에 넣어주었기에 사용 할 수 있는 것이다.

profile
학습 기록.

0개의 댓글