티스토리 뷰

8.2 F.prototype

이제껏 객체 내부에 직접 '__proto__'에 접근하여 prototype 상속을 적용했다면, 생성자 함수를 수행하는 시점에 바로 적용되도록 할 수도 있다. F.prototype 형식으로 해당 속성에 바로 접근하면 된다. 이는 '__proto__'랑은 분명히 다르다는 것에 주의하자. 아마 이 방법이 주로 쓰이는 것 같다.

let animal = {
  eats: true
};

function Rabbit(name) {
  this.name = name;
}

Rabbit.prototype = animal;

let rabbit = new Rabbit("흰 토끼"); //  rabbit.__proto__ == animal

alert( rabbit.eats ); // true

F.prototype은 new F 방식으로 객체를 생성할 때만 사용된다.

Default F.prototype, constructor property

만약 개발자가 직접 값을 할당하지 않았다면, 기본적으로 'property' 속성은 constructor 속성 하나만 있는 객체를 가리킨다. 또한 해당 객체의 constructor는 함수 자신을 가리킨다.

function Rabbit() {}

/* 디폴트 prototype
Rabbit.prototype = { constructor: Rabbit };
*/

 

 

function Rabbit() {}
// 함수를 만들기만 해도 디폴트 프로퍼티인 prototype이 설정됩니다.
// Rabbit.prototype = { constructor: Rabbit }

alert( Rabbit.prototype.constructor == Rabbit ); // true

new Rabbit을 통해 만든 토끼 객체에서 constructor 속성을 사용할 수도 있다.

function Rabbit() {}
// 디폴트 prototype:
// Rabbit.prototype = { constructor: Rabbit }

let rabbit = new Rabbit(); // {constructor: Rabbit}을 상속받음

alert(rabbit.constructor == Rabbit); // true ([[Prototype]]을 거쳐 접근함)

또한, 어떤 객체가 있는데 이 객체를 만들 때 어떤 생성자가 사용되었는지 알 수 없는 경우 아래와 같은 방법으로 생성할 수도 있겠다.

function Rabbit(name) {
  this.name = name;
  alert(name);
}

let rabbit = new Rabbit("흰 토끼");

let rabbit2 = new rabbit.constructor("검정 토끼");

이런 방법을 쓸 일이 많을지는 잘 모르겠다. 이번 개념은 이해가 잘 안 된다... 

 

특히, prototype 속성이 항상 적절한 constructor 값을 가지지는 않는다. 개발자가 원한다면 다른 값으로 변경이 가능하다. 즉, 기본값으로 constructor가 설정될 뿐 값은 얼마든지 변경될 수 있다.

function Rabbit() {}
Rabbit.prototype = {
  jumps: true
};

let rabbit = new Rabbit();
alert(rabbit.constructor === Rabbit); // false

// -----------------------

function Rabbit() {}

// Rabbit.prototype 전체를 덮어쓰지 말고
// 원하는 프로퍼티가 있으면 그냥 추가합니다.
Rabbit.prototype.jumps = true
// 이렇게 하면 디폴트 프로퍼티 Rabbit.prototype.constructor가 유지됩니다.

8.3 Native prototypes

Object.prototype

let obj = {};
alert( obj ); // "[object Object]" ?

위 예시에서 obj는 분명 빈 객체인데, "[object Object]"라는 문자열을 반환한다. 해당 문자열은 어디서 반환되는 걸까? Object라는 내장객체 생성자함수의 prototype이 toString이나 다른 함수가 구현되어 있는 객체를 참조한다. 따라서 아래와 같은 결과가 나온다.

let obj = {};

alert(obj.__proto__ === Object.prototype); // true

alert(obj.toString === obj.__proto__.toString); //true
alert(obj.toString === Object.prototype.toString); //true

Other built-in prototypes

Array, Date, Function 등 여러 내장 객체들도 프로토타입에 메서드가 저장되어 있다. '[1, 2, 3]'이라는 배열이 만들어지면, 해당 배열의 디폴트 생성자가 동작해 Array.prototype이 '[1, 2, 3]'의 프로토타입이 된다.

또한, 자바와 비슷하게 모든 내장 프로토타입의 상속 꼭대기에는 Object.prototype이 있어야 한다고 명세되어 있다. 모든 것은 객체를 상속받는 셈이다. 

let arr = [1, 2, 3];

// arr은 Array.prototype을 상속받았나요?
alert( arr.__proto__ === Array.prototype ); // true

// arr은 Object.prototype을 상속받았나요?
alert( arr.__proto__.__proto__ === Object.prototype ); // true

// 체인 맨 위엔 null이 있습니다.
alert( arr.__proto__.__proto__.__proto__ ); // null

만약 프로토타입 체인에서 같은 메서드가 구현되어 있는 경우에는 가장 가까운 곳에 있는 메서드가 사용된다. 

Primitives

number, string, boolean은 객체가 아니지만, 해당 타입 값의 속성에 접근하려고 하면 String, Number, Boolean과 같은 wrapper 객체가 생성된다. 이러한 wrapper 객체 덕분에 기본타입에서도 쉽게 메서드를 사용할 수 있겠다. 

Changing native prototype

별로 권장되지 않는 방법이라고는 하지만, 네이티브 프로토타입을 수정할 수 있다. 예를 들어 String.prototype에 메서드를 추가하면 모든 문자열에서 해당 메서드를 사용할 수 있다.

String.prototype.show = function() {
  alert(this);
};

"BOOM!".show(); // BOOM!

명세에는 있으나 특정 자바스크립트 엔진에는 구현되어 있지 않을 때, 폴리필(?)이란 걸 사용해서 네이티브 프로토타입을 변경하는 건 허용된다.

if (!String.prototype.repeat) { // repeat이라는 메서드가 없다고 가정합시다
  // 프로토타입에 repeat를 추가

  String.prototype.repeat = function(n) {
    // string을 n회 반복(repeat)합니다.

    // 실제 이 메서드를 구현하려면 더 복잡한 코드가 필요합니다.
    // 전체 알고리즘은 명세서에서 확인할 수 있는데,
    // 명세서를 완벽히 구현하지 않은 폴리필이라도 충분히 쓸만하니 예시는 이 정도로만 작성해보겠습니다.
    return new Array(n + 1).join(this);
  };
}

alert( "라".repeat(3) ); // 라라라

Borrowing from prototypes

아래 예시와 같이 네이티브 프로토타입의 메서드를 빌려서 사용할 수도 있다. 

let obj = {
  0: "Hello",
  1: "world!",
  length: 2,
};

obj.join = Array.prototype.join;

alert( obj.join(',') ); // Hello,world!
댓글
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday