const obj = {};
alert(obj);
위의 코드를 실행시키면 결과가 어떤 것이 나올지 예측이 가능한가? 정답은 아래와 같다.
응? 나는
alert('[object Object]')
를 입력한 적이 없는데?
이전에 배웠던 내용이 잘 기억난다면, 위의 내용은 잘 풀어썼을 때 아래와 같다는 것을 생각해낼 수 있을 것이다.
const obj = new Object();
alert(obj.toString());
{}
가 할당된 obj
는 자동적으로 Object
객체의 프로토타입인 Object.prototype
을 상속 받게 된다. 그말인 즉, 던더(더블 언더) 프로토(__proto__
)에 Object.prototype
이 들어가게 된다는 이야기이다.
위의 그림과 같은 현상이 일어난다.
alert(Object.prototype.toString());
위와 같은 코드를 작성한 것과 같은 결과이다.
참고로
Object.prototype.__proto__
에는 아무것도 없음을 잠깐 생각해두자. 당연히prototype
은__proto__
로 상속될 것들이 모여있는 건데, 이중으로 상속할 일은 없다.
Array
, Date
, Function
을 비롯한 다른 내장 객체들도 프로토타입에 메서드를 저장해놓는다.
위와 같이 [1, 2, 3]
이라는 배열을 만들어보면, __proto__
에 들어가는 내용이 Array.prototype
에 있는 내용과 일치한다는 것을 알 수 있다.
이렇게 프로토 타입을 상속받음으로써, push()
, pop()
등 다양한 배열 메소드도 제공받으며, 이런 내부동작 덕에 메모리 효율도 높아진다.
100
이라는 임의의 숫자가 들어있는 상수를 만들고, __proto__
에 들어가는 내용을 Number.prototype
과 비교하면 또 일치한다.
역시 이번에도 프로토 타입을 통해 toFixed()
와 같은 숫자 전용 메소드를 제공받는다.
위와 같은 방식으로 프로토타입은 계층 구조로 연결되어 있다.
위 코드 예제를 보면, 쉽게 이해할 수 있을 것이다.
한번 더 상위로 가면 null
이 존재하는 것도 확인할 수 있다.
일단 프로토타입이 상속되면, 동일한 이름의 메소드가 있을 때, 나와 가장 가까운 거리에 있는 상속된 메소드를 사용한다.
이를테면 아래의 코드에서는,
const array = [1, 2, 3];
console.log(array.toString());
toString()
을 할 때, Object.prototype.toString()
도 있지만, 가장 가까이에 있는 Array.prototype.toString()
을 사용하게 된다.
이제까지 사용했던 문자열, 숫자, 불린 값은 내장 생성자인 String
, Number
, Boolean
을 사용하는 임시 래퍼(wrapper) 객체에 의해 감싸졌다. 임시 래퍼 객체는 이러한 메서드를 제공한 이후에는 사라진다.
래퍼 객체는 보이지 않는 곳에서 만들어진다. 최적화는 엔진이 담당한다. 명세서에서는 각 자료형에 해당하는 래퍼 객체의 메서드를 프로토타입 안에 구현해놓고, String.prototype
, Number.prototype
, Boolean.prototype
을 사용해 쓸 수 있도록 규정한다.
null
과 undefined
도 자바스크립트에서는 원시 타입으로 분류되는데, 이에 대응하는 래퍼 객체는 존재하지 않는다.
String.prototype.log = function() {
console.log(this.toString());
}
"안녕하세요".log();
위와 같은 형식의 조작이 가능하지만, 라이브러리에서 중복으로 네이티브 프로토타입을 바꿔버리면 충돌의 가능성이 매우 크기 때문에 반드시 주의해야 한다.
네이티브 프로토타입이 변경되어야 한다면, 보통 그 이유는 오직 폴리필밖에 없다.
특정 브라우저에서 구현되지 않은 메소드를 직접 구현할 때 쓰면 유용하다.
const obj = {
0: "Hello",
1: "World",
length: 2
};
obj.join = Array.prototype.join;
alert(obj.join(' ')); // Hello World!
내장 메서드인 join
의 특성을 이용한 코드이다. 인덱스가 있는지와 length
프로퍼티가 있는지만 확인하기 때문에 이와 같은 동작이 가능하다.
또는, obj.__proto__ = Array.prototype
을 통해서도 가능하긴하다. 다만, obj
가 이미 다른 객체를 상속받고 있을 때는 불가능하다. 자바스크립트는 단일상속만을 허용하기 때문이다.
Function.prototype.defer = function(ms) {
setTimeout(this, ms);
};
위 코드를 작성하게 되면, 모든 함수(Function
객체)에 .defer()
메소드를 사용할 수 있게 된다.
Function.prototype.defer = function(ms) {
const f = this;
return function(...args) {
setTimeout(() => f.apply(this, args), ms);
}
}
위와 같이 apply
를 이용하면, 넘어온 this
를 기억하고, 넘어온 args
를 이용하여 데코레이팅 함수도 만들 수 있다.