본문 바로가기
JavaScript/JavaScript

Javascript) 상속, Pseudoclassical 상속 선언(자식 생성자 함수 & 프로토타입 처리), this binding 관련 함수(call, apply, bind), Object.create

by 박채니 2022. 5. 27.
SMALL

안녕하세요, 코린이의 코딩 학습기 채니 입니다.

 

개인 포스팅용으로 내용에 오류 및 잘못된 정보가 있을 수 있습니다.


상속

 

HTML 코드

<button onclick="test1();">상속</button>

 

Javascript 코드

<script>
const test1 = () => {
    const books = [
        new Book("매트릭스", 35000, .15),
        new Book('오라클 완전 정복', 30000, .2),
        new Book('네오클래식', 15000)
    ];
    console.log(books);
    books.forEach((book) => console.log(`${book}`)); // toString 호출
};

function Book(title, price, discountRate=0.05) {
    this.title = title;
    this.price = price;
    this.discountRate = discountRate;
};

Book.prototype.getSalePrice = function() {
    return this.price - (this.price * this.discountRate);
};

Book.prototype.toString = function() {
    return `제목 : ${this.title}, 판매가 : ${this.getSalePrice()}원 (정가 : ${this.price})`;
};
</script>

getSalePrice()와 toString()은 Book의 prototype에 선언하여 부모인 Book.prototype에서 꺼내와 사용하도록 하였습니다.

 

만일 더 구체화된 Novel 객체를 생성하고자 할 때는 어떻게 할까요?

(Book의 기능을 모두 가지고 있으면서, Novel만의 기능을 추가하고 싶을 때)

위와 같은 프로토체인 구조를 만들어보도록 하겠습니다.


Pseudoclassical 상속 선언

 

자식 생성자 함수 & 프로토타입 처리

① 자식 생성자 함수 안에서 부모 생성자 함수 호출

② 자식 생성자 함수의 프로토타입 객체를 부모 프로토타입 객체 복제본으로 지정

③ 2번에서 생성된 프로토타입 객체의 constructor 속성으로 자식 생성자 함수를 지정

 

const test1 = () => {
    const books = [
        // new Book("매트릭스", 35000, .15),
        // new Book('오라클 완전 정복', 30000, .2),
        // new Book('네오클래식', 15000)
        new Novel("매트릭스", 35000, .15, '연애'),
        new Novel('오라클 완전 정복', 30000, .2, '심리'),
        new Novel('네오클래식', 15000, undefined, 'SF')
    ];
    console.log(books);
    books.forEach((book) => console.log(`${book}`)); // toString 호출
};

function Book(title, price, discountRate=0.05) {
    this.title = title;
    this.price = price;
    this.discountRate = discountRate;
};

Book.prototype.getSalePrice = function() {
    return this.price - (this.price * this.discountRate);
};

Book.prototype.toString = function() {
    return `제목 : ${this.title}, 판매가 : ${this.getSalePrice()}원 (정가 : ${this.price})`;
};

function Novel(title, price, discountRate=0.05, type) {
    // 1. 부모 생성자 함수 호출
    Book.call(this, title, price, discountRate);

    //부모 생성자 함수에서 처리할 수 없는 속성 등록
    this.type = type;
};
// 2. 자식 생성자 함수의 프로토타입 객체를 객체 부모 프로토타입 객체 복제본으로 지정
Novel.prototype = Object.create(Book.prototype); // Novel.prototype = Book.prototype 시 문제될 수 있음

// 3. 2번에서 생성된 프로토타입 객체의 constructor 속성으로 자식 생성자함수를 지정
Novel.prototype.constructor = Novel;

call() 메소드를 이용하여 Book 생성자 함수를 현재 객체(this, Novel) 기준으로 실행하였습니다.

따라서 Novel의 title, price, discountRate가 지정됩니다.

그 후, Book에는 없는 Novel만의 type 속성을 등록해주었습니다.

 

원하는 프로토타입 체인은 new Novel → Novel.prototype → Book.prototype의 구조인데, 1번까지만 한다면

Novel.prototype은 Book.prototype의 자식 객체가 될 수 없습니다.

따라서 처리 순서 2번을 통하여 Novel의 prototype을 Book의 prototype의 자식으로 생성해줍니다.

그렇다면 원하는 구조가 만들어지겠죠.

 

하지만 처리 순서 2번까지만 하게 된다면, 새로 만들어낸 (Book.prototype을 바라보는 Novel.prototype) Novel.prototype의 constructor가 지정되지 않으므로 Novel 객체와 Novel.prototype이 서로 순환참조되지 않습니다.

그렇기 때문에 Novel.prototype의 constructor를 Novel로 지정하여 서로 참조되도록 해줍니다.

최종적으로 Novel의 prototype은 Book.prototype을 바라보고 있으며, constructor는 Novel이 됩니다.


this binding과 관련된 함수

☆ 화살표 함수는 this 재지정 불가하므로 사용 X!!!

 

 

this를 binding해서 호출

 

call(this객체, arg1, arg2, ...) - 함수의 매개인자를 나열

apply(this객체, [arg1, arg2, ...] - 함수의 매개인자를 배열로 전달

 

HTML 코드

<button onclick="test2();">call | apply | bind</button>

 

Javascript 코드

const test2 = () => {
    const foo = function(a, b) {
        console.log(this, this.id, a, b);
    };
    foo(10, 20);
};

일반 함수에서의 this는 window객체이므로, window객체의 id 속성 값이 없기 때문에 undefined가 출력되는 것을 확인할 수 있습니다.

 

this 지정

call()

const test2 = () => {
    const foo = function(a, b) {
        console.log(this, this.id, a, b);
    };
    // foo(10, 20);

    const obj = {
        id : 'qwerty123'
    };
    foo.call(obj, 10, 20);
};

call()메소드를 이용하여 this를 obj로 지정해준 후 출력해보니, this는 obj인 {id : 'qwerty123'}을 가져왔으며,

따라서 this.id 값도 'qwerty123'인 것을 확인할 수 있습니다.

 

this 지정

apply()

const test2 = () => {
    const foo = function(a, b) {
        console.log(this, this.id, a, b);
    };
    // foo(10, 20);

    const obj = {
        id : 'qwerty123'
    };
    // foo.call(obj, 10, 20);
    foo.apply(obj, [10, 20]);
};

apply()메소드 또한 call()메소드와 동일한 역할을 하지만 매개인자를 배열로 넘겨준다는 점에서 차이가 있습니다.

 

 

this가 binding된 함수 리턴

bind(this객체)

const test2 = () => {
    const foo = function(a, b) {
        console.log(this, this.id, a, b);
    };

    const obj = {
        id : 'qwerty123'
    };
    
    const foo1 = foo.bind(obj);
    foo1(10, 20);
};

바인딩 처리된 함수를 리턴해주기 때문에 this가 obj로 바인딩된 함수를 담고 있으므로 foo1(10, 20) 또한 동일한 결과를 가져옵니다.

 


Object.create()

- 생성자 호출 없이 해당 객체를 생성하는 메소드 (Object 생성자 함수의 static 메소드)

 

HTML 코드

<button onclick="test3();">Object.create</button>

 

Javascript 코드

const test3 = () => {
    // 1. new Room() 객체 - Room.prototype의 자식 객체 생성
    // 생성자 함수를 호출하였으므로 속성 추가
    const room1 = new Room(1);
    console.log(room1);

    // 2. Object.create(Room.prototype) - Room.prototype의 자식 객체 생성
    // 생성자 함수 호출 안 했으므로 속성 없음
    const room2 = Object.create(Room.prototype);
    console.log(room2)
};

function Room(no) {
    this.no = no;
};

Room.prototype의 자식 객체를 생성하였으며, new Room()은 생성자 함수를 직접 호출하였기 때문에 속성이 추가되었습니다.

 

console.log(room1.__proto__ === Room.prototype);
console.log(room2.__proto__ === Room.prototype);

 

 


@실습 - Comic 생성자 함수 만들어보기

const test1 = () => {
    const books = [
        // new Book("매트릭스", 35000, .15),
        // new Book('오라클 완전 정복', 30000, .2),
        // new Book('네오클래식', 15000)
        new Novel("매트릭스1", 35000, .15, '연애'),
        new Novel('오라클 완전 정복1', 30000, .2, '심리'),
        new Novel('네오클래식1', 15000, undefined, 'SF'),
        new Comic('원피스', 3000, 0, false),
        new Comic('드래곤볼', 2500, .1, true)
    ];
    console.log(books);
    books.forEach((book) => console.log(`${book}`)); // toString 호출
};

function Book(title, price, discountRate=0.05) {
    this.title = title;
    this.price = price;
    this.discountRate = discountRate;
};

Book.prototype.getSalePrice = function() {
    return this.price - (this.price * this.discountRate);
};

Book.prototype.toString = function() {
    return `제목 : ${this.title}, 판매가 : ${this.getSalePrice()}원 (정가 : ${this.price})`;
};

function Novel(title, price, discountRate=0.05, type) {
    // 1. 부모 생성자 함수 호출
    Book.call(this, title, price, discountRate);

    //부모 생성자 함수에서 처리할 수 없는 속성 등록
    this.type = type;
};
// 2. 자식 생성자 함수의 프로토타입 객체를 객체 부모 프로토타입 객체 복제본으로 지정
Novel.prototype = Object.create(Book.prototype); // Novel.prototype = Book.prototype 시 문제될 수 있음

// 3. 2번에서 생성된 프로토타입 객체의 constructor 속성으로 자식 생성자함수를 지정
// Novel.prototype.constructor = Novel;

function Comic(title, price, discountRate=0.05, end) {
    Book.call(this, title, price, discountRate);
    this.end = end;
};
Comic.prototype = Object.create(Book.prototype);
Comic.prototype.constructor = Comic;
Comic.prototype.toString = function() {
    return `[${this.end ? '완결' : '연재중'}] ${Book.prototype.toString.call(this)}`;
};

Book의 prototype에 toString 메소드를 this 현재 객체 기준으로 바인딩해서 가져와 출력하였습니다.

LIST