TIL 21. JS 프로토타입과 프로토타입 체인

rahula·2021년 5월 26일
0

javascript

목록 보기
13/15
post-thumbnail

자바스크립트의 프로토타입 개념에 대해서 알아보겠습니다. 이 글은 MDN과 책 코어자바스크립트를 토대로 작성됐습니다.

prototype

Prototypes are the mechanism by which JavaScript objects inherit features from one another.

객체 지향은 왜 쓰는걸까?

비슷한 객체들을 몇백게 만들어야 할때. createObject라는 function을 만들듯이, 매번 나름의 인자값과 return값을 갖는 함수로 객체를 만들 수 있다. 그러나 이건 너무 반복적이다.

const createPerson = (name, score) => {
 const newPerson = {};
 newPerson.name = name;
 newPerson.score = score;
 return newPerson;
};
const user1 = createPerson("user1", 30);
const user2 = createPerson("user2", 100);
// ... 반복되는 코드.

prototype 기반 언어

javascript는 prototype기반 언어이다. prototype은 꽤나 특이한 개념이다.

클래스 기반 언어에서는 상속이라는 개념을 쓰지만, prototype기반에서는 어떤 객체를 원형 즉 prototype으로 삼고 이를 복제(참조)함으로써 상속과 비슷한 효과를 얻는다.

생성자(constructor) 함수와 인스턴스

생성자 함수는 어떤 공통된 성질을 지니는 객체들을 생성하는데 사용하는 함수이다. 객체지향 언어에서는 생성자를 클래스, 클래스를 통해 만든 객체를 인스턴스라고 한다.

프로그래밍적으로 생성자는 구체적인 인스턴스를 만들기 위한 일종의 틀이다.

자바스크립트는 함수에 생성자로써의 역할을 함께 부여했다. new명령어와 함께 함수를 호출하면 해당 함수가 생성자로써 동작하게 된다.

그리고 인스턴스는 해당 생성자의 prototype 프로퍼티를 참조하는 __proto__라는 프로퍼티가 있는 객체이다.

prototype의 구조를 추상화로 이해하기

// 생성자 함수 정의
const Person = function (name) {
  this._name = name;
};
// Person이라는 생성자 함수의 prototype에 getName이라는 메서드 지정
Person.prototype.getName = function () {
  return this._name;
};

prototype의 생성과정을 단계별로 설명하면 다음과 같다.

  1. javascript는 함수(function expression에 한해서. ES6의 class에도 똑같이 적용되는건가?)에 자동으로 객체인 prototype property를 생성해 놓는다.
  2. 특정한 생성자 함수 (Constructor)을 new operator과 함께 호출하면
  3. Constructor에서 정의된 내용을 바탕으로 새로운 instance가 생성된다.
  4. 이때 instance에는 __proto__라는 property가 자동으로 부여되는데,
  5. 이 property는 Constructor의 prototype이라는 property를 참조한다.

주목! prototype__proto__사이의 관계가 prototype개념의 핵심이다.

// new 키워드와 함께 생성된 instance
const me = new Person("me");
me.getName()

__proto__는 생략이 가능하다.

한 마디로 요약하자면, new 연산자로 생성자(Constructor)를 호출하면 인스턴스가 만들어지는데, 이 인스턴스의 생략 가능한 프로퍼티인 __proto__는 생성자의 prototype을 참조한다.

__proto__ 프로퍼티는 생략 가능하도록 구현되어 있기 때문에, 생성자 함수의 prototype에 어떤 메서드나 프로퍼티가 있다면 인스턴스에서도 마치 자신의 것처럼 해당 메서드나 프로퍼티에 접근할 수 있게 된다.

주의 : [[prototype]]이 프로토타입의 원래 명칭이다. __proto__는 사실 브라우저들이 [[prototype]]을 구현한 대상에 불과하다. 따라서 실무에서는 __proto__보다는 Object.getPrototypeOf() 혹은 Object.create()등을 사용하도록 권장된다.

리터럴 생성에서의 prototype 상속

생성자 함수 외에, 리터럴로 생성했을 때에도 각각의 변수들은 하나의 인스턴스로 취급되어 prototype을 상속받는다. 즉, Array의 모든 메서드와 프로퍼티를 간단하게 배열 하나 정의하는 것으로 상속받을 수 있다는 것이다!

const arr = [1,2,3,4,5]

arr.map((item)=>console.log(item)) // 1 2 3 4 5

이제 위의 코드가 이전과 다르게 보인다. arr라는 변수는 Array로부터 모든 프로퍼티와 메서드를 상속받았고, __proto__는 생략 가능하므로, 바로 뒤에 Array의 프로퍼티를 자신의 프로퍼티인 것처럼 쓸 수 있게 된다.

한편, Array의 prototype 프로퍼티 내부에 있지 않은 from, isArray같은 메서드들은 인스턴스가 직접 호출할 수 없다. Array생성자 함수에서 직접 접근해야 실행이 가능하다.

constructor 프로퍼티

생성자 함수의 프로퍼티인 prototype 객체 내부에는 constructor이라는 프로퍼티가 있다. 즉 인스턴스의 __proto__ 객체 내부에도 존재한다. Constructor프로퍼티는 단어 그대로 생성자 함수를 참조한다. 이는 생성자쪽에서 자신을 참조할 목적이 아니라, 인스턴스쪽에서 그 원형이 무엇인지를 알 수 있는 수단이기 때문이다.

const arr = [1,2,3,4,5]

arr.constructor === Array // true

프로토타입 체인

만약 인스턴스가 생성자의 것과 동일한 이름의 프로퍼티 혹은 메서드를 갖고 있는 상황이라면?

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

어떤 데이터의 __proto__ 프로퍼티 내부에 다시 __proto__프로퍼티가 연쇄적으로 이어진 것을 프로토타입 체인이라고 하고, 이 체인을 따라가며 검색하는 것을 프로토타입 체이닝이라고 한다.

어떤 메서드를 호출하면 자바스크립트 엔진은 데이터 자신의 프로퍼티들을 검색해서 원하는 메서드가 있으면 그 메서드를 실행하고, 없으면 __proto__를 검색해서 있으면 그 메서드를 실행하고, 없으면 다시 __proto__를 검색해서 실행하는 식으로 진행한다.

profile
백엔드 지망 대학생

0개의 댓글