#2 프로토타입 - 심화 1

Jake Seo·2021년 9월 29일
1

함수의 prototype 프로퍼티

let animal = {
  eats: true
};

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

Rabbit.prototype = animal; // 프로토타입 객체 설정
let rabbit = new Rabbit("White Rabbit"); //  rabbit.__proto__ == animal
alert( rabbit.eats ); // true

위와 같이 함수에서 new 키워드를 이용해 새로운 객체를 생성하면, 함수의 .prototype에 존재하는 객체가 새롭게 생성된 객체의 프로토타입 객체가 된다.

참고로 자바스크립트에서는 함수도 객체이다. 단순히 객체 뒤에 괄호를 넣어 객체()와 같은 형태를 띄었을 때 무언가 실행할 뿐이다.

Rabbit.prototype에 들어있던 객체가 생성자로 생성한 객체의 __proto__에 할당된다.

Function.prototype과 new Function()

new Function() 코드를 통해 생성된 객체의 [[Prototype]]에는 Function.prototype이 참조하던 객체가 들어간다.

주의해야 할 것은 Rabbit.prototype 자체의 레퍼런스를 할당하는 것이 아니라 Rabbit.prototype이 가지고 있던 객체의 레퍼런스를 할당한다는 것이다. 다시 말하면 Rabbit.prototype === object일 때, object의 레퍼런스를 할당하는 것이지, Rabbit.prototype 자체를 할당하는 것이 아니다.

new Rabbit()과 같이 new Function()이 호출될 때만 Function.prototypenewObject.__proto__에 할당한다. 단, 프로토타입 객체의 레퍼런스를 할당해서 Function.prototype이 변경되면 newObject.__proto__도 변경된다.

단, Rabbit.prototype에 사용되었던 animal은 바뀌어도 상관없다. 왜냐하면 Rabbit.prototypeanimal을 직접적으로 참조하지 않기 때문이다.

new Rabbit()코드로 생성된 rabbit__proto__Rabbit.prototype가 가리키던 객체를 그대로 참조했기 때문에, 해당 객체의 내용을 바꾸고, rabbit.__proto__객체의 내용을 확인해보면 이 객체가 Rabbit.prototype가 가리키던 객체를 그대로 참조하고 있음을 다시 확인할 수 있다.

하지만, 위와 같이 Rabbit.prototype이 참조하는 객체를 바꿔버리는 경우에는 rabbit.__proto__에 변화가 없다. rabbit.__proto__는 이전에 Rabbit.prototype에 존재하던 객체를 여전히 가리키고 있을 것이다.

함수의 prototype 프로퍼티와 constructor 프로퍼티

모든 선언된 함수는 내부적으로 prototype 프로퍼티를 갖는다. 그리고 그 prototype 내부에는 constructor가 들어있다.

그래서 Function.prototype을 강제로 수정하지 않았다면, new Function()으로 생겨난 어떤 객체에서도 .constructor()를 통해 생성자를 호출할 수 있다.

대략 위와 같은 관계가 되는데, Function에서는 .prototype을 통해 Function.prototype에 접근 가능하다. 그리고 Function.prototype에서는 constructor를 통해 Function에 접근 가능하다. 그리고 new Function()으로 인해 생겨난 객체에서도 또 프로토타입을 통해 .constructor()를 사용하여 Function에 접근 가능하다.

서드파티 라이브러리를 사용하다가 자바스크립트 객체가 어디서 온지 모를 때, 이를 이용하면 유용하다.

단, 자바스크립트는 constructor의 값을 보장하지 않는다. 우리가 멋대로 object.prototype을 수정해버리면 constructor는 날아간다.

그래서, object.prototype = {} 식으로 프로토타입의 속성을 지어주는 것보다는, object.prototype.a = 'value'와 같은 형식으로 프로토타입을 지어줘야 constructor도 지키면서 프로토타입의 값도 넣어줄 수 있다.

예제 코드를 통해 특징 더 알아보기

Rabbit.prototype = {}

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

let rabbit = new Rabbit();

Rabbit.prototype = {};

alert( rabbit.eats ); // ?

위의 결과 값은 true이다 그 이유는 아무리 Rabbit.prototype{}라는 빈 객체를 할당해도 rabbit.__proto__Rabbit.prototype이 참조하던 객체를 그대로 참조한다. 되려 Rabbit.prototype.eats = false와 같은 코드를 입력했다면, rabbit.__proto__가 참조하던 객체가 직접 변경되어 결과는 false가 되었을 것이다.

Rabbit.prototype.eats = false

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

let rabbit = new Rabbit();

Rabbit.prototype.eats = false;

alert( rabbit.eats ); // ?

이건 위에 설명했듯, 참조하는 객체의 프로퍼티를 직접 바꾼 케이스라 결과도 false가 나온다.

delete rabbit.eats

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

let rabbit = new Rabbit();

delete rabbit.eats;

alert( rabbit.eats ); // ?

이 코드에서 출력하는 값은 true이다. rabbit.eats는 현재 프로토타입 객체를 거쳐 출력은 가능하지만, 실제 rabbit.eats에 직접적으로는 아무것도 없는 상태이기 때문이다.

delete Rabbit.prototype.eats

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

let rabbit = new Rabbit();

delete Rabbit.prototype.eats;

alert( rabbit.eats ); // ?

이 코드에서 출력하는 값은 undefined이다. rabbit.__proto__가 참조하던 객체의 eats를 직접 지워버렸다.

constructor의 활용

위의 로그 내용처럼 constructornew person1.constructor()와 같은 코드로 응용할 수도 있다. new라는 키워드를 이렇게 붙이는 것이 좀 낯설어보일 수 있다.

prototype = {}

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

Person.prototype = {};

const newPerson1 = new Person("Peter");
const newPerson2 = new newPerson1.constructor("jake");

console.log(newPerson1.name);
console.log(newPerson2.name);

위 코드는 Function.prototype.constructor의 값은 보장되지 않는다는 특성을 이용했다. 기본적으로 Function.prototype.constructor는 자동으로 생성되지만, 사용자에 의해 수정될 수 있다. 위의 경우에는 Function.prototype 자체가 {}로 변해버렸다.

위의 코드를 잘 보면 newPerson1.name을 로그로 찍어봤을 때는 Peter가 잘 나오는데, newPerson2.name을 로그로 찍어보면 jake가 나오지 않는다. 그 이유는 newPerson2를 생성할 때 newPerson1.constructor로 생성을 하였는데, 우리가 Person.prototype를 수정하지 않았다면 정상적으로 생성됐지만, 우리는 Person.prototype{} 값으로 바꾸어놓았다. 그래서 자바스크립트는 newPerson1.__proto__에서 constructor 프로퍼티를 찾아보지만 찾아낼 수 없다. 그래서 자바스크립트는 newPerson1.__proto__.__proto__와 같이 프로토타입 체인을 타고 constructor를 찾으려 한다. newPerson1.__proto__{} 이므로, {}는 일반 자바스크립트 객체이고 프로토타입으로 Object를 갖는다.

마침내 자바스크립트는 Object.constructor를 찾아 해당 코드를 수행한다. Object.constructor("jake") 가 실행되는데, Object.constructor()는 내부에 무슨 인자가 오더라도 오직 {}만 반환한다. 그러므로 {}.nameundefined가 되는 것이다.

레퍼런스

https://ko.javascript.info/function-prototype

profile
풀스택 웹개발자로 일하고 있는 Jake Seo입니다. 주로 Jake Seo라는 닉네임을 많이 씁니다. 프론트엔드: Javascript, React 백엔드: Spring Framework에 관심이 있습니다.

0개의 댓글