[JS] 프로토타입

서로·2024년 10월 17일
0

JS

목록 보기
11/15
post-thumbnail

➊ 프로토타입

① prototype, proto

자바스크립트는 프로토타입 기반 언어이다.
클래스 기반 언어에서는 '상속'을 사용하지만 프로토타입 기반 언어에서는
어떤 객체를 원형으로 삼고 이를 복제(참조)함으로써 비슷한 효과를 얻는다.

프로토타입이란 대체 무엇일까?

자바스크립트의 모든 함수는 prototype 객체를 프로퍼티로 갖는다.
그리고 이 함수를 생성자 함수로서 호출하여 인스턴스를 생성하면
이 인스턴스는 __proto__라는 프로퍼티를 갖는다.
__proto__는 생성자 함수의 prototype을 그대로 참조한다.

prototype__proto__ 프로퍼티가 왜 자동으로 생성되는 것이며
이 둘은 어떤 의미를 담고 있을까?

프로토타입에 대한 설명을 쭉 이어나간 뒤에 위 의문들을 다시 짚어보도록 하겠다.

var Person = function (name) {
    this.name = name;
};

Person.prototype.greet = function () {
    console.log(`안녕하세요, 저는 ${this.name}입니다.`);
};

var iu = new Person('지은');

iu.__proto__.greet(); // 안녕하세요, 저는 undefined입니다.
iu.greet(); // 안녕하세요, 저는 지은입니다.

Person 생성자 함수로부터 iu라는 객체를 생성하였다.
iu 객체는 name__proto__ 프로퍼티를 보유한다.

__proto__은 생성자 함수인 Personprototype을 참조한다.
따라서 __proto__을 통해 Personprototype에 정의된 함수를 자유롭게 사용할 수 있다.
= greet 함수를 자유롭게 호출할 수 있다.

위 코드에서 iu.__proto__.greet();을 실행하여 프로토타입에서 정의한 greet 함수를 실행한 결과 undefined가 출력되었다.
그 이유는 this.name을 출력하려고 할 때 thisiu.__proto__로 바인딩 되었기 때문이다.
Personprototype에는 name 프로퍼티가 존재하지 않기 때문에 undefined를 출력하였다.

그러나 __proto__을 생략하여 Personprototype의 함수를 호출할 수 있다!
따라서 iu.greet();을 실행하면 thisiu 객체로 바인딩되어 this.name을 정상적으로 출력하게 된다.

이처럼 객체는 __proto__을 생략하여 생성자 함수의 prototype에서 정의한 메소드들을
마치 자신의 메소드인 것처럼 자유롭게 사용할 수 있다.

② constructor

생성자 함수의 프로퍼티인 prototype 객체 내부에는 constructor라는 프로퍼티가 있다.
인스턴스의 __proto__ 객체 내부에도 마찬가지이다.
이 프로퍼티는 단어 그대로 원래의 생성자 함수(자기 자신)을 참조한다.
인스턴스로부터 그 원형이 무엇인지 알고 싶다면 이 프로퍼티를 사용하면 된다.

var iu = new Person('지은');
var arr = [1, 2, 3];

console.log(iu.constructor); // [Function: Person]
console.log(arr.constructor); // [Function: Array]

iuPerson 생성자 함수로부터 생성된 객체이다.
따라서 iu.constructor을 출력하면 그 원형인 Person이 출력된다.

arrArray 생성자 함수로부터 생성된 객체이다. (배열도 객체다.)
따라서 arr.constructor을 출력하면 그 원형인 Array가 출력된다.

➋ 프로토타입 체인

prototype 객체를 참조하는 __proto__을 생략하면
인스턴스는 prototype에 정의된 프로퍼티나 메서드를 마치 자신의 것처럼 사용할 수 있다.

그러나 만약 인스턴스가 동일한 이름의 프로퍼티 또는 메서드를 갖고 있다면 어떨까?

var Person = function (name) {
    this.name = name;
};

var iu = new Person('지은');

iu.greet = function () {
    console.log(`안녕! 난 ${this.name}이야!`);
};

Person.prototype.greet = function () {
    console.log(`안녕하세요, 저는 ${this.name}입니다.`);
};

iu.greet(); // 안녕! 난 지은이야!

Personprototype에도 greet 메서드를 정의하고
iu 객체에서도 동일한 이름의 greet 메서드를 정의해두었다.

iu.greet();을 호출하면 prototypegreet 메서드는 무시되고
iu 객체에서 정의한 greet 메서드가 호출된다.

이는 greet 메서드가 오버라이딩 되었기 때문이다!

자바스크립트 엔진이 greet 메서드를 찾는 방식은
가장 가까운 대상인 자신의 프로퍼티를 검색하고,
없으면 그 다음으로 가까운 대상인 __proto__를 검색하는 순서로 진행된다.

🚨 여기서 중요한 점
모든 prototype에는 Object.prototype을 참조하는 __proto__ 프로퍼티가 존재한다.
이는 prototype이 객체이기 때문이다!

따라서 위의 예제 코드에서 greet 메서드를 찾기 위해,
iu → Person → Object 순서로 메서드를 검색한다.
만약 greet 메서드를 찾았다면 검색을 멈춘다.

이처럼 어떤 데이터의 __proto__ 프로퍼티 내부에
다시 __proto__ 프로퍼티가 연쇄적으로 이어진 것을 프로토타입 체인 이라고 한다.

또한 이 체인을 따라가며 검색하는 것을 프로토타입 체이닝이라고 한다.

➌ 프로토타입 체인을 이용한 상속

생성자 함수와 객체에서 prototype__proto__ 프로퍼티가 왜 자동으로 생성되는 것이며
이 둘은 어떤 의미를 담고 있을까?

prototype은 생성자 함수를 통해 생성한 여러 객체 혹은 이를 상속받은 여러 객체가
공통적으로 사용할 메소드를 정의하는 역할을 한다.

객체는 __proto__을 통해 상위 프로토타입 체인에 접근할 수 있고,
이를 통해 상속받은 메서드를 자신의 것처럼 사용할 수 있다!

아래는 StudentPerson을 상속받기 위해 프로토타입을 활용한 예시이다.

var Person = function (name) {
    this.name = name;
};

var Student = function (name, grade) {
    Person.call(this, name); // Person의 생성자 호출
    this.grade = grade;
};

Person.prototype.greet = function () {
    console.log(`안녕하세요, 저는 ${this.name}입니다.`);
};

Student.prototype.study = function() {
    console.log(`${this.name} 학생이 공부 중입니다.`);
};

// Student가 Person을 상속하도록 설정
Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;

var iu = new Student('지은', 3);

iu.greet(); // 안녕하세요, 저는 지은입니다.
iu.study(); // 지은 학생이 공부 중입니다.

Person.call(this, name);

전달받은 name 인자를 Studentthis.name에 저장해주기 위해 call을 이용하였다.
이를 통해 thisiu 객체를 바라보도록 한다.

Student.prototype = Object.create(Person.prototype);

Object.create을 통해 Person.prototype을 원본 객체로 하는 새로운 객체를 생성하였다.
그리고 이 객체를 Studentprototype 객체로 할당해주었다.

위 과정을 통해 iu 객체는 부모 객체인 Personprototype에 접근할 수 있다.

Student.prototype.constructor = Student;

부모 생성자 함수인 Person의 프로토타입 객체를 토씨 하나 안바꾸고 그대로 복제했기 때문에,
새롭게 생성한 자식 생성자 함수인 Student의 프로토타입 객체의 constructor 프로퍼티는
여전히 부모 생성자 함수인 Person을 참조하고 있다.

하지만 자식 생성자 함수인 Student를 통해 생성된 객체가
Person를 사용하여 생성된 것처럼 처리되면 안되므로,
다시 constructor 프로퍼티를 Student로 변경해줘야한다.

이처럼 위의 프로토타입을 이용하면 상속 관계를 정의할 수 있고
Student 생성자 함수를 통해 생성된 iu 객체가 greet() 함수를 마치 자신의 함수인 것처럼 사용할 수 있다!

prototype__proto__ 프로퍼티는 이처럼 여러 객체가 공통적으로 사용할 기능을 정의할 수 있도록 돕기 때문에
자바스크립트에서 상속과 재사용의 패턴을 효율적으로 구현할 수 있게 해준다!

📎 참고 자료
https://evan-moon.github.io/2019/10/27/inheritance-with-prototype/

profile
읽기 쉬운 코드와 글을 작성해요 📝

0개의 댓글