\(@^0^@)/

[BOOK] 코어 자바스크립트, 프로토타입 1 본문

BOOKS/코어 자바스크립트

[BOOK] 코어 자바스크립트, 프로토타입 1

minjuuu 2022. 4. 7. 14:04
728x90

오늘 읽은 범위 : (프로토타입) p.146 ~ p.159


< 책에서 기억하고 싶은 내용 >

프로토타입

  • JS는 프로토타입 기반의 언어이다.
  • 클래스 기반 언어에서는 '상속'을 사용하지만, 프로토타입 기반 언어에서는 어떤 객체를 원형(prototype)으로 삼고, 이를 복제(참조)함으로써 상속과 비슷한 효과를 얻는다.
  • 유명한 프로그래밍 언어의 상당수가 클래스 기반인 것에 비교하면 프로토타입은 꽤나 독특한 개념이라 할 수 있음.

- 프토토타입의 개념 이해 -

Constructor, prototype, instance

위의 그림은 아래의 코드를 도식화한 것이다.

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

prototype은 객체이므로, 이를 참조하는 __proto__역시 당연히 객체이다.
prototype 객체 내부에는 인스턴스가 사용할 메서드를 저장한다.
그러면 인스턴스에서도 숨겨진 프로퍼티인 __proto__를 통해 이 메서드들에 접근할 수 있게 된다.

실무에서는 가급적 __proto__를 사용하지 않기를 권장한다.
대신 Object.getPrototypeOf( ) / Object.create( ) 등을 이용하도록 하자.


__proto__는 생략 가능한 프로퍼티이다.

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

// Person의 인스턴스는 __proto__ 프로퍼티를 통해 getName을 호출 할 수 있음.
var suzi = new Person('Suzi');
suzi.__proto__.getName();    // undefined

// instance의 __proto__가 Contructor의 prototype 프로퍼티를 참조하므로 같은 객체를 바라본다.
Person.prototype === suzi.__proto__    // true

메서드 호출 결과로 undefined이 나온 이유는 해당 변수가 '호출할 수 있는 함수'에 해당한다는 것을 의미.
만약 실행할 수 없는, 즉 함수가 아닌 다른 데이터 타입이었다면 TypeError가 발생했을 것.
그런데 에러가 아닌 다른 값이 나왔으니, getName은 실제로 실행됐으며, 함수라는 것이 입증됨.

그럼에도 불구하고 undefined이 나온 이유는, this에 바인딩된 대상이 잘못 지정됐다는 걸 의미한다.
어떤 함수를 '메서드로서' 호출할 때는 메서드명 바로 앞의 객체가 곧 this.
그러니까 suzi.__proto__.getName( )에서 getName 함수 내부에서의 this는 suzi가 아니라 suzi.__proto__라는 객체.
따라서, suzi.__proto__ 객체 내부에는 name 프로퍼티가 없으므로 undefined가 반환됨.

자바스크립트 규약 : 찾고자 하는 식별자가 정의돼 있지 않을 때는 Error 대신 undefined를 반환한다.

 

만약 __proto__객체에 name 프로퍼티가 있다면?

var suzi = new Person('suzi');
suzi.__proto__._name = 'SUZI__proto__';
suzi.__proto__.getName();    // SUZI__proto__

SUZI__proto__가 잘 출력되는 걸 볼 수 있다.

this를 인스턴스로, __proto__없이 인스턴스에서 곧바로 메서드를 써도 원하는 값이 나온다.

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

그 이유는 바로 __proto__가 생략 가능한 프로퍼티이기 때문이다. 원래부터 생략 가능하도록 정의되어 있음.
자바스크립트 창시자의 아이디어 이기 때문에, '그냥 그런가 보다' 하는 수밖에 없다고 한다.

suzi.__proto__.getName
-> suzi(.__proto__).getName
-> suzi.getName

__proto__를 생략하지 않으면 this는 suzi.__proto__를 가리키지만, 이를 생략하면 suzi를 가리킨다.


위에서 봤던 삼각형으로 다시 정리해보자면, new 연산자로 Constructor를 호출하면 instance가 만들어지는데,
이 instance의 생략 가능한 프로퍼티인 __proto__는 Constructor의 prototype을 참조한다.

JS는 함수에 자동으로 객체인 prototype 프로퍼티를 생성해 놓는데, 해당 함수를 생성자 함수로서 사용할 경우,
즉 new 연산자와 함께 함수를 호출할 경우, 그로부터 생성된 인스턴스에는 숨겨진 프로퍼티 __proto__가 자동으로 생성되며, 이 프로퍼티는 생성자 함수의 prototype 프로퍼티를 참조한다.

생성자 함수의 prototype에 어떤 메서드나 프로퍼티가 있다면
인스턴스에서도 마치 자신의 것처럼 해당 메서드나 프로퍼티에 접근할 수 있게 된다.


var arr = [1, 2];
console.dir(arr);
console.dir(Array);

왼쪽은 arr 변수를 출력한 결과이고, 오른쪽은 생성자 함수인 Array를 출력한 결과이다.

왼쪽을 먼저 보면, 첫 줄에 Array(2)라고 표기되어있다. 이것은 Array라는 생성자 함수를 원형으로 삼아 생성됐고, length가 2이라는 의미, 인덱스인 0, 1을 제외한 length, __proto__ 모두 옅은 색상으로 표기되어 있다.

오른쪽을 보면, 첫 줄에 함수라는 의미의 f가 표시되어 있고, 둘째 줄부터는 함수의 기본적인 프로퍼티들이 옅은 색상으로 표기되어 있음.

Array를 new 연산자와 함께 호출해서 인스턴스를 생성하든, 배열 리터럴을 생성하든, instance인 [1, 2]가 만들어진다.
이 인스턴스의 __proto__은 Array.prototype을 참조하는데, __proto__가 생략 가능하도록 설계되어 있기 때문에 인스턴스가 push, pop, forEach 등의 메서드를 마치 자신의 것처럼 호출할 수 있음.
한편 Array의 prototype프로퍼티 내부에 있지 않은 from, isArray 등의 메서드들은 인스턴스가 직접 호출할 수 없음.
이들은 Array 생성자 함수에서 직접 접근해야 실행이 가능하다.


constructor 프로퍼티

  • 해당 프로퍼티는 단어 그대로 원래의 생성자 함수(자기 자신)를 참조한다.
    굳이 자신을 참조하는 이유는 인스턴스로부터 그 원형이 무엇인지를 알 수 있는 수단이기 때문.
  • constructor를 변경하더라도 참조하는 대상이 변경될 뿐,
    이미 만들어진 인스턴스의 원형이 바뀐다거나 데이터 타입이 변하는 것은 아니다.


< 프로토타입 요약 >

  • 한 생성자 함수를 new 연산자와 함께 호출하면 Constructor에서 정의된 내용을 바탕으로 새로운 인스턴스가 생성된다.
    이 인스턴스에는 __proto__라는, Constructor의 prototype 프로퍼티를 참조하는 프로퍼티가 자동으로 부여됨.
    __proto__는 생략 가능한 속성이라서, 인스턴스는 Constructor.prototype의 메서드를 마치 자신의 메서드인 것처럼 호출할 수 있다.
  • Constructor.prototype에는 constructor라는 프로퍼티가 있는데, 이는 다시 생성자 함수를 가리킨다.
    이 프로퍼티는 인스턴스가 자신의 생성자 함수가 무엇인지를 알고자 할 때 필요한 수단이다.

[ 출처 : 코어 자바스크립트 ]

728x90