TIL: proto 사용하지 않기

프로토타입을 설정하기 위한 방법

__proto__를 사용하여 프로토타입을 설정하는 것 보다 더 모던한 방법이 3 가지 존재한다.

Object.create(proto, [descriptors])

Object.create()을 사용하면, [[Prototype]]__proto__를 참조하는 객체를 만든다. 그리고 추가로 프로퍼티도 넘길 수 있다.

let animal = {
  eats: true,
};

// 프로토타입이 animal인 새로운 객체를 생성합니다.
let rabbit = Object.create(animal);

let rabbit = Object.create(animal, {
  jumps: {
    value: true,
  },
});

alert(rabbit.jumps); // true

Object.create()을 사용하면, 첫 번쨰 인자로 들어가는 객체의 모든 프로퍼티를 포함한 똑같은 사본이 만들어진다. 역시 [[Prototype]]도 복제된다.

정확히는 Object.create()을 사용하면 편리하게 주어진 프로토타입을 활용하여 객체를 만들 수 있었지만, 프로토타입을 얻어오거나 설정하는 것은 불가능하다. 그래서 브라우저에서는 __proto__를 사용하여 프로토타입을 얻어오거나 설정할 수 있도록 한 것이다.

그러다가 ES2015가 나오면서 Object.getPrototype(obj), Object.setPrototypeOf(obj, proto)가 나오면서 더 이상 __proto__를 통해 프로토타입에 접근할 필요가 없어진 것이다.

사실 기존 객체의 [[Prototype]]을 바꾸는 것은 객체 프로퍼티 접근 최적화를 망가뜨리기 때문에 되도록 하지 않는 것이 좋다.

그렇다면 왜 __proto__를 쓰지 말자고 하는 것일까?

아래를 보자.

let obj = {};

let key = prompt('입력하고자 하는 key는 무엇인가요?', '__proto__');
obj[key] = '...값...';

alert(obj[key]); // "...값..."이 아닌 [object Object]가 출력됩니다.

객체란 키-값으로 구성되어있는 연관 배열이다. __proto__는 객체이거나 null이여야한다. 그런데 위 글에서는 __proto__에 문자열을 넣고 있다. 결과를 보면 __proto__에 문자열 값이 아닌 [Object Object]가 출력된다. 이런식으로 값이 나올 수 있다는 것 자체가 버그가 된다.

위의 해결책은 이러하다.

let obj = Object.create(null);

let key = prompt('입력하고자 하는 key는 무엇인가요?', '__proto__');
obj[key] = '...값...';

alert(obj[key]); // "...값..."이 제대로 출력됩니다.

null로 만들어줌으로써 __proto__를 null로 만들어서 새로운 값을 넣어준 것이다. 프로토타입을 null로 객체를 만들 경우, __proto__는 getter, setter를 상속받지 않게 된다. 그래서 이는 평범한 객체가 되므로 값을 잘 할당받게 된다. 하지만 이 객체는 내장 메서드가 존재하지 않는다. 그렇게 아주 단순한 객체가 된다.

Object.getPrototypeOf(obj)

Object.getPrototypeOf(obj)의 값은 obj의 [[Prototype]]을 반환한다.

Object.setPrototypeOf(obj, proto)

Object.setPrototypeOf(obj, proto)를 사용하는 경우, 첫 번째 인자인 obj의 [[Prototype]]__proto__가 되도록 설정한다.

프로토타입 활용하여 메서드 빌리기

프로토타입의 고유 기능을 활용하여, 특정 객체나 원시값에 메소드를 추가하여 사용할 수 있다. 또한 네이티브 프로토타입까지 수정이 가능하다. 하지만 이 기능은 추천하지 않는 방법이다.

네이티브 프로토타입에 새 내장 메서드를 추가하는 경우는 폴리필을 만들 경우이다. 이는 자바스크립트 명세서와 동일한 기능을 달아놓지만, 특정 버전은 구현되어있지 않을 때만을 의미한다.

또한 프로토타입을 활용하여 메서드를 빌려오는 것도 가능하다. 개발을 하다보면, 다른 종류의 객체 프로토타입의 내장 메서드가 필요한 경우가 생기기 때문이다. 하지만 특정 객체가 이미 다른 객체의 프로토타입을 상속받고 있다면 이는 불가능하다.

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

obj.join = Array.prototype.join;

alert(obj.join(',')); // Hello,world!

Reference