[JS] 프로토타입

Yuno·2021년 3월 25일
0

모던JS

목록 보기
4/16
post-thumbnail

개발을 하다보면, 기존 기능을 가져와 확장할 필요가 생깁니다.
자바스크립트의 고유 기능인 프로토타입 상속으로 이를 구현할 수 있습니다.

[[prototype]]

자바스크립트의 객체는 [[prototype]] 이라는 숨김 프로퍼티를 갖습니다.
이는 null이거나 객체만 허용하는데, 그 객체를 프로토타입이라 합니다.

객체에서 어떤 프로퍼티를 읽는데, 없다면 자동으로 프로토타입에서 찾습니다.
Element NodeNode의 메서드를 사용할 수 있는 이유도 이와 같습니다.

[[prototype]]은 숨김 프로퍼티 이지만, 여러 방법을 통해 설정할 수 있습니다.
__proto__[[prototype]]의 접근자 프로퍼티로 이 방법 중 하나입니다.
(다만, 다소 구식입니다. 표준에도 관련 내용이 명시되어 있습니다.)

let animal = {
	eats : true
}

let rabbit = {
	jumps : true,
  	ears : 2
}

rabbit.__proto__ = animal;

console.log(rabbit.eats); // true

rabbit 객체의 [[prototype]]에서 animal을 참조하기 때문에 eats 프로퍼티를 사용할 수 있습니다.

이러한 체인은 더 길어질 수 있습니다.
...-> rabbit -> mammal -> animal ->...

열거

프로토타입의 프로퍼티가 enumerable:true라면,
for...in상속 받은 프로퍼티도 순회에 포함시킵니다.

obj.hasOwnProperty(key) 를 사용하면, obj의 직접 구현된 key만 true를 반환합니다.
이를 통해 구분할 수 있습니다.

for(let key in obj) {
	let isOwn = obj.hasOwnProperty(key);
}

Object.keys(obj)는 자신의 프로퍼티만 반환합니다.
Object.valuesObject.entires 역시 상속 프로퍼티를 제외하고 동작합니다.

함수의 prototype 프로퍼티

동일한 종류의 여러 객체를 생성할 때, 생성자 함수를 활용합니다.
생성자 함수와 new 연산자를 통해 객체를 생성할 수 있습니다.

생성하려는 객체가 상속을 받는다면,
모두 같은 객체를 상속받아야 할 것 입니다.

함수에 prototype프로퍼티를 설정하면, 함수로 인해 생성되는 객체[[prototype]]을 해당 객체로 설정합니다.

let animal = {
	eats : true;
}

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

Rabbit.prototype = animal;

let rabbit = new Rabbit('bunny');
rabbit.eats // true

함수의 prototype 기본 값

따로 할당하지 않아도 모든 함수는 prototype 프로퍼티를 갖습니다.
기본 값으로 constructor 프로퍼티 하나만 있는 객체를 가르킵니다.
또한, 이 constructor는 함수 자기 자신을 가르킵니다.

function Rabbit(name) {
	this.name = name;
}
// Rabbit.prototype = {controctor : Rabbit}

때문에, 생성된 객체를 통해서 새로운 객체를 만드는 것이 가능합니다.

let rabbit = new Rabbit('bunny');

let rabbit2 = new rabbit.contructor('tommy')

다만, 자바스크립트는 올바른 contructor를 보장하지 않습니다
함수에 기본으로 해당 객체가 설정되지만, prototype 프로퍼티에 다른 값으로 덮어 씌우면,
contructor가 없거나 변할 것입니다.

따라서, 이러한 상황을 방지하려면 prototype 프로퍼티 전체를 덮어 쓰기 보다
원하는 프로퍼티를 추가/제거 해야 합니다.

function Rabbit(){};

Rabbit.prototype.jumps = true;
// 기본 prototype의 contructor는 유지가 됨

내장 프로토타입

Object

우리는 객체를 출력하려 할 때, 문자로 형 변환 되는 과정을 알고 있습니다.
함수에 따라 hint가 결정되고, 그에 따라 특정 메서드가 호출되어 반환합니다.

대부분은, toString() 메서드를 통해 "[object Object]"라는 문자열로 변환됩니다.

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

그럼 여기서 toString()은 어디 있을까요?

우리는 앞서, 객체의 찾을 수 없는 프로퍼티는 프로토타입에서 찾는다는 것을 배웠습니다.

let obj = {}let obj = new Object()의 축약입니다.
Object는 내장 생성자 함수이며, 객체를 생성합니다.
이 생성자 함수의 prototype 프로퍼티가 toString()을 비롯한 다양한 메서드가 구현된 객체를 참조합니다.

이로 인해, 이 생성자 함수로 생성된 객체가 프로토타입으로 Object.prototype을 갖는 것입니다.

원시 값

원시 값인 숫자, 문자, 불린은 객체가 아닙니다.
다만, 편의성을 위해 언어적으로 프로퍼티에 접근하는 것을 허용하였습니다.

원시 값의 프로퍼티의 접근하려 하면,
String, Number, Boolean과 같은 내장 생성자가 래퍼 객체를 생성합니다.
래퍼(Wrapper) 객체는 메서드를 제공한 뒤 사라집니다.

이러한 래퍼 객체의 prototype에 메서드들이 구현되어 있습니다.
문자열의 slice(), split() 등이 이에 해당합니다.

내장 객체

마찬가지로 Array, Date, Fucntion 등 내장 객체들 역시 프로토타입에 메서드가 저장됩니다.

let arr = [1,2,3] 에서 join등 메서드를 쓸 수 있는 이유가 이것입니다.


내장 객체들의 계층을 그리면 다음과 같습니다.
Object.prototype의 위의 체인 [[prototype]]은 없다는 걸 주의해야 합니다.

내장객체 프로토타입 역시 수정할 수 있습니다.

String.prototype.show() {
	console.log(this);
}

이렇게 하면, 모든 문자열에서 해당 메서드를 사용할 수 있습니다.

하지만, 말 그대로 모든 문자열에서 사용할 수 있듯 프로토타입은 전역으로 영향을 미치기에 충돌 가능성이 커집니다.

네이티브 프로토타입을 수정하는 경우는 폴리필을 만들 때 뿐입니다.

프로토타입을 수정하는 메서드

__proto__를 통해 프로토타입을 수정할 수 있지만, 호환성을 위해 남아있는 방식입니다.

대신, 메서드를 사용합니다.

  • Object.create(proto, [descriptors]) : [[prototype]]proto를 참조하는 객체를 만듦니다.
  • Object.getPrototypeOf(obj) : obj[[prototype]]을 반환합니다.
  • Object.setPrototypeOf(obj, proto) : 프로토타입을 설정합니다.

create()descriptors도 인자로 받기 때문에, 프로토타입과 플래그까지 복사할 수 있습니다.

let clone = Object.creat(Object.getPrototypeOf(obj),Object.getOwnPropertyDescriptors(obj));

2015년에 getPrototypeOfsetPrototypeOf가 표준에 추가되어, __proto__가 대체 되었습니다.
그 이유는 무엇일까요?

__proto__

객체를 생성하면 Object.prototype을 참조한 __proto__ 접근자 프로퍼티가 생깁니다.
__proto__ 에 접근하면, 대응되는 getter setter가 호출되어 [[prototype]]을 읽거나 설정합니다.

때문에, __proto__를 key로 사용할 수 없다는 결함을 가집니다.
객체나 null외에는 무시되고, 객체를 넣으면 프로토타입이 바뀔 수 있습니다.

let plain = Object.creat(null);

이렇게 프로토타입이 없는 빈 객체를 만들면__proto__는 getter와 setter를 상속받지 않습니다.
또한, __proto__를 key로 사용할 수 있습니다.

이러한 객체는 아주 단순한 객체 혹은 순수 사전식 객체라고 부릅니다.
다만, 내장 메서드가 없다는 단점이 있습니다.

plain.toString = Object.prototype.toString;

내장 메서드는 이렇게 하면 가져올 수 있습니다.

profile
web frontend developer

0개의 댓글