JS는 기존 객체를 복사하여 새로운 객체를 사용하는 프로토타입 기반의 언어다.
프로토타입은 객체의 원형을 뜻하며 생성된 객체도 다른 객체의 원형이 될 수 있다.
JS는 객체를 상속하기 위해 프로토타입이라는 방식을 사용한다.
다른 객체로부터 메소드나 속성을 상속 받기 위해 프로토타입 객체를 가진다.
프로토타입 객체 역시 다른 프로토타입 객체를 상속 받을 수 있으며, 이를 프로토타입 체인이라 한다.
모든 객체는 [[Prototype]]
속성을 가지며 이는 null 이거나 다른 객체에 대한 참조가 된다.
객체에서 특정 속성이나 메소드를 찾을 때 객체에서 찾은 뒤 없는 경우 프로토타입에서 속성을 찾는다.
프로토타입에 대한 getter
와 setter
로 __proto__
또는 Object.getPrototypeOf
/ Object.setPrototypeOf
를 이용한다.
__proto__
는 기본적으로 브라우저에서만 지원하도록 규정되어있으나 대부분의 호스트 환경에서 지원한다.
객체에서 메소드나 속성을 참조하면 우선적으로 객체 자체에서 찾은 뒤 없는 경우 프로토타입에서 찾는다.
상속 받은 프로토타입에서 가지고 있는 메소드 및 속성은 자식도 참조할 수 있다.
const animal = {
name: '동물',
walk() {
console.log('걷기')
}
}
const rabbit = {
jumps: true,
__proto__: animal
}
rabbit.jumps // "true" rabbit에 jumps가 있으므로 rabbit에서 참조
rabbit.name // "동물" rabbit에 없고, __proto__의 animal에 있으므로, animal에서 참조
rabbit.walk() // log"걷기" animal 에서 상속받아 호출
프로토타입의 체인에서 순환 참조는 허용되지 않는다.
Uncaught TypeError: Cyclic proto value
프로토타입으로 객체나 null
이외의 자료형은 무시된다.
프로토타입에서의 this
는 호출 주체가 객체인지 프로토타입인지 상관 없이 .
앞의 객체가 대상이다.
const animal = {
getThis() {
return this
}
}
const rabbit = {
__proto__: animal
}
rabbit.getThis() // rabbit{}, 호출 주체가 rabbit이므로, this는 rabbit
대부분의 키-값 관련 메소드는 상속 프로퍼티를 제외하지만 for..in
은 상속 프로퍼티를 포함하여 순회한다.
const animal = {
name: 'animal'
}
const rabbit = {
jump: true,
__proto__: animal
}
for(const v of Object.keys(rabbit)) console.log(v) // 자체 프로퍼티만 순회
/*
"jump"
*/
console.log('---')
for(const k in rabbit) console.log(k) // 상속 프로퍼티까지 순회
/*
"jump"
"name"
*/
JS에서 함수를 정의하면 함수는 prototype
을 가지고, 그 함수를 constructor
로 가지는 함수의 프로토타입 객체가 생성된다.
new
키워드를 통해 객체를 생성하면 __proto__
는 함수의 프로토타입 객체로 최초 한 번 참조한다.
/**
* prototype: Person.prototype
*/
function Person(){}
/**
* constructor: Person
*/
Person.prototype = {
constructor: Person
}
/**
* __proto__: Person.prototype
*/
const person = new Person()
prototype
속성기본적으로 prototype
에는 constructor
가 생기고, 이는 생성자로서 동작한다.
하지만 JS는 올바른 constructor
를 보장하지 않기 때문에 사용에 주의가 필요하다.
따라서 프로토타입에 메서드나 속성을 추가하고 싶을 땐 prototype
을 덮어쓰지 않고 원하는 값을 추가 또는 제거하는 형태가 좋다.
prototype
을 덮어써 constructor
가 사라진 경우prototype
을 실수로 덮어썼다면, 수동으로 다시 할당하면 다시 사용할 수 있다.
Person.prototype = {
constructor: Person,
// ...
}
프로토타입은 최초 객체 생성 시 할당되므로 생성 이후에 새로 할당된 프로토타입은 적용되지 않는다
function Rabbit() {}
Rabbit.prototype = { eats: true }
const rabbit = new Rabbit()
Rabbit.prototype = {}
rabbit.eats // "true", rabbit이 생성되는 시점에서 {eats: true}가 할당되므로 이후에 새로 할당된 prototype은 반영되지않는다.
// 단 이후 새로 생성하는 Rabbit 객체는 영향을 받는다.
// 영향을 주고 싶다면 delete Rabbit.prototype.eats 형태로 사용해야한다.
일반적으로 대부분의 객체는 프로토타입 체인에 Object.prototype
을 가지고 있고, Object
의 프로토타입은 null
이다.
Array
, Function
등의 다양한 네이티브 객체들은 대부분 Object.prototype
를 프로토타입으로 가지고 있다.
프로토타입 내부에서 중복되는 메서드나 속성이 있다면 체인에서 더 가까운 값을 참조한다.
원시값은 객체가 아니기 때문에 프로퍼티에 접근하려 하면 임시 래퍼 객체를 통해 처리가 된다.
Number
, String
, Boolean
등의 래퍼 객체는 각자의 prototype
을 가지고 있으며, 이들은 Object.prototype
를 프로토타입으로 갖는다.
일반적으로 네이티브 프로토타입의 변경은 권장되지 않는다.
네이티브 프로토타입의 변경이 필요한 경우는 폴리필을 만드는 경우가 있다.
// Array.prototype.map의 폴리필
if (!Array.prototype.map) {
Array.prototype.map = function(callback, thisArg) {
var T, A, k;
if (this == null) {
throw new TypeError(' this is null or not defined');
}
var O = Object(this);
var len = O.length >>> 0;
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function');
}
if (arguments.length > 1) {
T = thisArg;
}
A = new Array(len);
k = 0;
while (k < len) {
var kValue, mappedValue;
if (k in O) {
kValue = O[k];
mappedValue = callback.call(T, kValue, k, O);
A[k] = mappedValue;
}
k++;
}
return A;
};
}
프로토타입에 있는 메서드도 빌려와서 사용할 수 있다.
예를 들어 유사배열에서 join
이 필요한 경우 아래처럼 활용할 수 있다.
Array.prototype.join.call({0: 'a', 1: 'b', length: 2}) // "a,b"
프로토타입이 null
인 경우 __proto__
를 갖지 않는 단순한 객체를 만들 수 있다.
일반 객체에 __proto__
에 문자열을 할당하면 적용되지 않는다면, 단순한 객체에서는 제대로 적용된다.
이는 프로토타입을 가지지 않으므로 toString
등의 메소드를 사용할 수 없지만 완전한 연관 배열의 역할을 하는 객체를 만든다.
const plainObj = Object.create(null)
const normalObj = Object.create(Object.prototype) // {}
plainObj['__proto__'] = 'plain'
normalObj['__proto__'] = 'normal'
console.log(plainObj['__proto__']) // "plain"
console.log(normalObj['__proto__']) // Object.prototype{}, __proto__에는 null이나 객체만 할당 가능함