ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [코어 자바스크립트] 프로토타입
    Javascript 2024. 4. 7. 18:28

    프로토타입 개념 이해

    1. constructor, prototype, instance

    저자 정재남선생님께서, 프로토타입을 다음 그림과 같이 하나의 도식으로 추상화를 하였는데, 이 단순한 추상화는 프로토타입의 개념을 압축한다. 아래의 코드 내용을 다음과 같이 도식화한 내용이다.

    var instance = new Constructor();

    코어자바스크립트 프로토타입 도식

    왼쪽 꼭지점에는 Constructor(생성자 함수)를, 오른쪽 꼭짓점에는 Constructor.prototype이라는 프로퍼티를 위치시켰다. 왼쪽 꼭짓점으로부터 아래를 향한 화살표 중간에 new가 있고, 화살표의 종점에는 instance가 있다. 오른쪽 꼭짓점으로부터 대각선 아래로 향하는 화살표의 종점에는 instance.__proto__이라는 프로퍼티를 위치시켰다. 자 흐름을 다시한번 살펴보자.

    1. 어떤 생성자 함수(Constructor)new 연산자와 함께 호출하면
    2. Constructor에서 정의된 내용을 바탕으로 새로운 인스턴스(Instance)가 생성된다.
    3. 이때 instance에는 __proto__라는 프로퍼티가 자동으로 부여되는데,
    이 프로퍼티는 Constructor의 prototype이라는 프로퍼티를 참조한다.

     

    프로토타입의 개념을 좀 더 상세히 설명하면 이렇다.

    자바스크립트는 함수에 자동으로 객체인 prototype 프로퍼티를 생성해 놓는데 해당 함수를 생성자 함수로서 사용할 경우, 즉 new 연산자와 함께 함수를 호출할 경우 그로부터 생성된 인스턴스에는 숨겨진 프로퍼티인 __proto__가 자동으로 생성되며, 이 프로퍼티는 생성자 함수의 prototype 프로퍼티를 참조한다. __proto__ 프로퍼티는 생략가능하도록 구현돼 있기 떄문에 생성자 함수의 prototype에 어떤 메서드나 프로퍼티가 있다면 인스턴스에서도 마치 자신의 것처럼 해당 메서드나 프로퍼티에 접근할 수 있다.

     

    예를 들어서, Person 이라는 생성자 함수의 prototype에 getName이라는 메서드를 지정했다고 해보자.

    var Person = function (name) {
    	this._name = name;
    }
    
    Person prototype.getName = function() {
    	return this._name;
    }

    이제, Person의 인스턴스는 __proto__ 프로퍼티를 통해 getName을 호출할 수 있다.

    왜냐하면 instance의 __proto__가 Constructor의 prototype 프로퍼티를 참조하므로 결국 둘은 같은 객체를 바라보기 때문

    var suzi = new Person('Suzi');
    suzi.__proto__.getName() // undefined

    오잉? undefined..? 어렵다. instance의 __proto__는 Constructor의 prototype을 참조하지 않는가..? 비교해보자.

    Person.prototype === suzi.__proto__ // true

    역시 참조한다. 같다. 그럼 왜 'undefined' 값이 나왔을까?

    'Suzi'라는 값이 나오지 않았다는 것보다, '에러가 발생하지 않았다'는 점이 중요하다. 어떤 변수를 실행해 에러가 발생하지 않고 'undefined'가 나왔다는 것은 이 변수를 호출할 수 있다는 것이다.

    그렇다면, 원하는 'Suzi'가 나오지 않은 이유는 바로 this에 바인딩된 대상이 잘못 지정됐기 때문이다.

    어떤 함수를 '메서드로서' 호출할 때는 메서드명 바로 앞의 객체가 곧 this가 된다. 그러니까 suzi.__proto__.getName() 에서 getName 함수 내부에서의 this는 suzi.__proto__라는 객체가 되는 것이다. 이 객체 내부에는 name 프로퍼티가 없으므로 '찾고자 하는 식별자가 정의돼 있지 않을 때는 Error 대신 undefined를 반환한다.' 라는 자바스크립트 규약에 의해 undefined가 반환됐다.

     

    그렇다면, suzi.__proto__.getName()을 통해서 의도한 값을 얻고 싶다면, __proto__ 객체에 name 프로퍼티가 있으면 되지 않을까?

    var suzi = new Person('Suzi');
    suzi.__proto__.name = 'Suzi';
    suzi.__proto__.getName() = // Suzi;

     

    예상대로 잘 출력된다. 그러니까 관건은 this이다.

     

    그렇다면, __proto__ 없이 this를 인스턴스로 하여 곧바로 메서드를 쓰는건 어떨까?

    var suzi = new Person('Suzi', 28);
    suzi.getName(); // Suzi

     

    이번에도... 띠용? __proto__를 빼면 this는 instance를 가리키는게 맞지만, 이대로 호출되고 심지어 원하는 값이 나오는게 정상적일까? 혼란의 여지가 있다. 하지만, 정상입니다. 그 이유는 바로 __proto__가 생략가능한 프로퍼티이기 때문이다.

     

    자, 이제 프로토타입의 도식을 살짝 바꿔 정의해본다.

    코어 자바스크립트 프로토타입 도식

     

    new 연산자로  Constructor를 호출하면 instance가 만들어지는데, 이 instance의 생략 가능한 프로퍼티인 __proto__는 Constructor의 prototype을 참조한다.

     

    정리

    어떤 생성자 함수를 new 연산자와 함께 호출하면 Constructor에서 정의된 내용을 바탕으로 새로운 인스턴스가 생성되는데, 이 인스턴스에는 __proto__라는 Constructor의 prototype 프로퍼티를 참조하는 프로퍼티가 자동으로 부여된다. __proto__는 생략 가능한 속성이라서, 인스턴스는 Constructor.prototype의 메서드를 마치 자신의 메서드인 것처럼 호출할 수 있다.

     

    Constructor.prototype에는 constructor라는 프로퍼티가 있는데, 이는 다시 생성자 함수 자신을 가리킵니다. 이 프로퍼티는 인스턴스가 자신의 생성자 함수가 무엇인지를 알고자할 때 필요한 수단이다.

     

    직각삼각형의 대각선 방향, 즉 __proto__ 방향을 계속 찾아가면 최종적으로는 Object.prototype에 당도하게 된다. 이런식으로 __proto__ 를 찾아가는 과정을 프로토타입 체이닝이라고 하며, 이 프로토타입 체이닝을 통해 각 프로토타입 메서드를 자신의 것처럼 호출할 수 있다. 이때 접근 방식은 자신으로부터 가장 가까운 대상으로 부터 점차 먼 대상으로 나아가며 원하는 값을 찾으면 검색을 중단한다.

     

    Object.prototype에는 모든 데이터 타입에서 사용할 수 있는 범용적인 메서드만이 존재하며, 객체 전용 메서드는 여느 데이터 타입과 달리 Object 생성자 함수에 스태틱하게 담겨있다.

     

    프로토타입 체인은 반드시 2단계로만 이뤄지는 것이 아니라 무한대의 단계를 생성할 수도 있다.

     

Designed by Tistory.