prototype 개념 정리

정현섭·2021년 7월 7일
0

다른 객체지향 언어(Java, C++)들은 class기반 객체지향이지만

JavaScript는 prototype기반 객체지향 언어다.

1. 기본 개념

  • 모든 객체는

    1. __proto__ 프로퍼티와
    2. constructor 프로퍼티를 가진다.
  • 모든 함수는

    1. prototype 프로퍼티를 가진다. (또한 함수도 객체이므로 __proto__constructor 를 가짐)

    2. Function에 의해 생성되었다. 즉,

    • fn.__proto__ === Function.prototype

    • fn.constructor === Function

    • 예시

      function normal() {};
      function Person() {};
      
      // 전부 true
      console.log(normal.__proto__ === Function.prototype);
      console.log(Person.__proto__ === Function.prototype);
      console.log(Function.__proto__ === Function.prototype);
      console.log(Object.__proto__ === Function.prototype);
      
      //전부 true
      console.log(normal.constructor === Function);
      console.log(Person.constructor === Function);
      console.log(Function.constructor === Function);
      console.log(Object.constructor === Function);
  • obj.__proto__ 는 obj의 생성자 함수의 prototype (obj.constructor.prototype) 을 가리킨다.

    • 이것을 이용해서 상속을 흉내낸다.
    • 그러니까 상속하고 싶은 멤버들은 생성자함수.prototype.<멤버이름> 에 정의하면 된다.
  • 리터럴로 정의한 객체는 내부적으로 Object 생성자 함수에 의해 생성되고

  • 함수는 내부적 Function 생성자 함수에 의해 생성된다.

2. Prototype chain

JavaScript에서는 객체의 맴버(프로퍼티와 메소드)에 접근할 때,

해당 객체에 해당 맴버가 없으면 __proto__를 통해 부모 객체 (프로토타입 객체)로 올라가서 검색하는 것을 반복한다.

이를 프로토타입 체인이라 한다.

프로토타입 체인을 도식화한 모습

여기서 맨 오른쪽 세로 라인이 a1객체의 prototype chain이라고 볼 수 있다.
(a1 → Asian Prototype → Person Prototype → Object Prototype → null)

체인 동작 조건

  • 멤버를 참조할때

    • 객체에 해당 멤버가 없는경우, 프로토타입 체인이 동작.
  • 멤버에 값을 할당할 때

    • 객체에 해당 맴버가 없는경우, 해당 객체에 맴버를 동적으로 할당한다.
    • 즉, 프로토타입 체인 동작 X

확인 문제

const Person = function Person (first, last) {
  this.firstName = first;
  this.lastName = last;
}

const p1 = new Person('Gildong', 'Hong');
const p2 = new Person('Sunsin', 'Lee');

console.log(p1.gender);  // undefined
console.log(p2.gender);  // 'male'
console.log(p1.firstName); // 'Gildong'
console.log(p2.firstName); // 'Sunsin'
console.log(p1.constructor); // ① Person(first, last)
console.log(p2.constructor); // ② Object()

위 코드를 객체형태로 도식화 할 수 있고

console.log출력이 왜 그런지 이해가 되면

prototype 개념을 완벽히 이해한거다.

헷갈리면 아래랑 비교해보자.

const Person = function Person (first, last) {
  this.firstName = first;
  this.lastName = last;
}

const p1 = new Person('Gildong', 'Hong');
p1.__proto__ = {gender: 'male'};
const p2 = new Person('Sunsin', 'Lee');

console.log(p1.gender);  // 'male'
console.log(p2.gender);  // undefine
console.log(p1.firstName); // 'Gildong'
console.log(p2.firstName); // 'Sunsin'
console.log(p1.constructor); // ① Object()
console.log(p2.constructor); // ② Person(first, last)

3. 프로토타입 객체의 확장

const Person = function Person (first, last) {
  this.firstName = first;
  this.lastName = last;
}

const p1 = new Person('Gildong', 'Hong');
const p2 = new Person('Sunsin', 'Lee');
const p3 = new Person('Muya', 'Ho');

Person.prototype.printFullName = function () {
  console.log(`제 이름은 ${this.firstName} ${this.lastName} 입니다.`);
};

p1.printFullName();
p2.printFullName();
p3.printFullName();

프로토타입 객체에 멤버를 추가하거나 삭제해서 위와 같은 식으로 사용할 수 있다.

  • 결과

Object.prototype.printFullName = function () {
  console.log(`제 이름은 ${this.firstName} ${this.lastName} 입니다.`);
};

p1.printFullName();
p2.printFullName();
p3.printFullName();
  • p1, p2, p3 가 가 결국 Object.prototype으로 연결되기 때문에 (prototype chain)

  • 위 코드도 같은 결과를 출력한다.

  • 결과

Function.prototype.printFullName = function () {
  console.log(`제 이름은 ${this.firstName} ${this.lastName} 입니다.`);
};

p1.printFullName();
p2.printFullName();
p3.printFullName();
  • p1, p2, p3 부터 시작하는 프로토타입 체인에 Function.prototype은 없기 때문에

  • printFullName() 메소드를 찾을 수 없어서 오류가 난다.

  • 결과

4. 원시 타입(Primitive data type) 확장

JavaScript에서는 5가지 원시 타입을 제외한 모든 것은 객체다.

원시타입 종류

  • 숫자
  • 문자열
  • boolean
  • null
  • undefine

하지만 원시타입으로 프로퍼티를 호출할 때 그와 연관된 프로토타입 객체로 연결된다.

아래 예시를 보자.

var str = 'test';
console.log(typeof str);                 // string
console.log(str.constructor === String); // true
console.dir(str);                        // test

var strObj = new String('test');
console.log(typeof strObj);                 // object
console.log(strObj.constructor === String); // true
console.dir(strObj);
// {0: "t", 1: "e", 2: "s", 3: "t", length: 4, __proto__: String, [[PrimitiveValue]]: "test" }

console.log(str.toUpperCase());    // TEST
console.log(strObj.toUpperCase()); // TEST

str은 분명 object가 아닌 string 타입인데도 불구하고

String으로 생성된 object인 것 마냥 이용할 수 있다.

이처럼 원시 타입도 내장 객체의 프로토타입에 연결된다. (string말고 number 등도 됨.)

참고

0개의 댓글