on
[Javascript]Prototype(2)
어떻게 우리는 빌트인 메서드를 쓸수 있는 것일까?
리터럴 표기법에 의해 생성된 객체의 생성자 함수와 프로토타입
생성자 함수에 의하여 생성된 인스턴스의 [[prototype]]
말고, 리터럴 표기법에 의해 생성된 객체에 대해서 알아보자.
// 객체 리터럴
const obj = {};
// 함수 리터럴
const add = function (a, b) {
return a + b;
};
// 배열 리터럴
const arr = [1, 2, 3];
// 정규표현식 리터럴
const regexp = /is/gi;
리터럴 표기법에 의해 생성된 객체도 물론 [[prototype]]
이 존재한다. 하지만 리터럴 표기법에 의해 생성된 객체의 경우 [[prototype]]
의 constructor
가 가리키는 생성자 함수가 반드시 객체를 생성한 생성자 함수라고 단정할 수는 없다.
Object
생성자 함수에 인수를 전달하지 않거나 undefined
또는 null
을 인수로 전달하면서 호출하면 내부적으로는 추상 연산 OrdinaryObjectCreate
를 호출하여 Object.prototype
을 [[prototype]]
으로 갖는 빈 객체를 생성한다.
객체 리터럴 역시 OrdinaryObjectCreate
를 호출하여 Object.prototype
을 [[prototype]]
으로 갖는 빈 객체를 생성하는 점에서는 동일하나 new.target
의 확인이나 프로퍼티를 추가하는 처리 등 세부 내용이 다르다. 따라서 객체 리터럴에 의해 생성된 객체는 Object 생성자 함수가 생성한 객체가 아니다.
다만 리터럴 표기법에 의해 생성된 객체도 상속을 위해 [[prototype]]
이 필요하다. 따라서 리터럴 표기법에 의해 생성된 객체도 가상적인 생성자 함수를 갖는다. [[prototype]]
은 가상의 생성자 함수와 함께 생성되어 생성자 함수의 prototype
과 constructor
에 의해 연결되어 있기 때문이다. 즉, [[prototype]]
과 생성자 함수는 단독으로 존재할 수 없고 언제나 쌍으로 존재한다.
프로토타입의 생성 시점
prototype
과 생성자 함수는 단독으로 존재할 수 없기 때문에, prototype
은 생성자 함수가 생성되는 시점에 생성된다.
1. 사용자 정의 생성자 함수와 프로토타입 생성 시점
일반 함수(함수 선언문, 함수 표현식)로 정의한 함수 객체는 new
연산자와 함께 생성자 함수로서 호출할 수 있다.
생성자 함수로서 호출할 수 있는 함수, 즉 constructor는 함수 정의가 평가되어 함수 객체를 생성하는 시점에 prototype 프로퍼티도 함께 생성된다.
// 함수 Person이 평가될 때 자동으로 ptototype도 함께 생성됨
console.log(Person.prototype); // {constructor: ƒ}
// 생성자 함수
function Person(name) {
this.name = name;
}
Person.prototype을 자세히 보자.
생성된 prototype
은 Person
의 prototype
프로퍼티에 바인딩 되고 prototype
프로퍼티는 객체이고, 모든 객체는[[prototype]]
을 가지므로 prototype
자체도[[prototype]]
을 가진다. 이 [[prototype]]
은 Object.prototype
이다.
따라서 사용자 정의 생성자 함수는 자신이 평가되어 함수 객체로 생성되는 시점에서 prototype
프로퍼티를 가지게 되고 이 프로퍼티의 [[prototype]]
은 언제나 Object.prototype
이므로 Object
의 메서드를 사용할 수 있게 되는 것이다.
2. 빌트인 생성자 함수와 프로토타입 생성 시점
빌트인 생성자 함수도 일반 함수와 마찬가지로 빌트인 생성자 함수가 생성되는 시점에 프로토타입이 생성된다. 모든 빌트인 생성자 함수는 전역 객체가 생성되는 시점에 생성된다.
즉, 생성자 함수 또는 리터럴 표기법으로 생성한 객체가 생성되기 이전에 객체의 생성자 함수와 프로토타입은 이미 객체화 되어 있다. 이후 생성자 함수 또는 리터럴 표기법으로 객체를 생성하면 프로토타입은 생성된 객체의 [[prototype]]
내부 슬롯에 할당된다.
객체 생성 방식에 따른 프로토타입의 결정
1. 생성자 함수에 의해 생성된 객체의 프로토타입
new
연산자와 함께 생성자 함수를 호출하여 인스턴스를 생성하면 추상 연산 OrdinaryObjectCreate
가 호출된다. 이때, OrdinaryObjectCreate
에 전달되는 프로토타입은 생성자 함수의 prototype 프로퍼티에 바인딩되어 있던 객체이다. 즉, 생성자 함수에 의해 생성되는 객체의 [[prototype]]
은 생성자 함수의 prototype프로퍼티에 바인딩되어 있는 객체이다.
function Person(name) {
this.name = name;
}
const me = new Person("Lee");
위 처럼 프로포타입의 별다른 정의 없사용자 정의 생성자 함수와 함께 생성된 prototype
의 프로퍼티는 constructor
뿐이다.
프로토타입은 객체이므로 프로퍼티를 추가/삭제할 수 있다. 그리고 이 변경 사항은 프로토타입 체인에 즉각 반영된다.
function Person(name) {
this.name = name;
}
// 프로토타입 메서드
Person.prototype.sayHello = function () {
console.log(`Hi! My name is ${this.name}`);
};
const me = new Person("Lee");
const you = new Person("Kim");
me.sayHello(); // Hi! My name is Lee
you.sayHello(); // Hi! My name is Kim
2. 객체 리터럴에 의해 생성된 객체의 프로토타입과 Object
생성자 함수에 의해 생성된 객체의 프로토타입
객체 리터럴에 의해 생성된 객체의 프로토타입과 Object
생성자 함수에 의해 생성된 객체의 프로토타입은 결과적으로는 같은 구조를 가진다. 다만, 프로퍼티 추가 방식에는 차이가 있다. 객체 리터럴 방식은 객체 리터럴 내부에 프로퍼티를 추가하지만, Object
생성자 함수 방식은 일단 빈 객체를 생성한 이후 프로퍼티를 추가해야 한다.
const obj = { x: 1 };
// 객체 리터럴에 의해 생성된 obj 객체는 Object.prototype을 상속받는다.
console.log(obj.constructor === Object); // true
console.log(obj.hasOwnProperty("x")); // true
const obj = new Object();
obj.x = 1;
// Object 생성자 함수에 의해 생성된 obj 객체는 Object.prototype을 상속받는다.
console.log(obj.constructor === Object); // true
console.log(obj.hasOwnProperty("x")); // true