자바스크립트를 배워보자 18일차 - 프로토타입(prototype) 2편

0

Javascript

목록 보기
18/30
post-custom-banner

프로토타입 2편

1. 리터럴 표기법에 의해 생성된 객체의 생성자 함수와 프로토타입

이전 포스팅에서 살펴본 것과 같이, 생성자 함수에 의해 생성된 인스턴스는 프로토타입의 constructor 프로퍼티에 의해 생성자 함수와 연결된다.

이때, constructor 프로퍼티가 가리키는 생성자 함수는 인스턴스를 생성한 생성자 함수와 같다.

const obj = new Object()
console.log(obj.constructor === Object) // true

const add = new Function('a' , 'b', 'return a + b')
console.log(add.constructor === Function) // true

function Person(name){
    this.name
}

const me = new Person("lee")
console.log(me.constructor === Person) // true

위는 new를 이용한 객체 생성 방식이다. new를 이용한 객체 생성 방식의 경우에는 생성자 함수를 통해 객체를 만드므로, 객체의 constructor 프로퍼티가 생성자 함수와 같을 수 밖에 없다.

그럼, 리터럴로 생성한 경우는 어떻게 될까??

const obj = {} // 객체 리터럴

const add = function(a,b){return a + b} // 함수 리터럴

const arr = [1,2,3] // 배열 리터럴

const regexp = /is/ig // 정규 표현식 리터럴

리터럴 표기법에 의해 생성된 객체도 프로토타입이 존재한다. 하지만 리터럴 표기법에 의해 생성된 객체의 경우 프로토타입의 constructor 프로퍼티가 가리키는 생성자 함수가 반드시 객체를 생성한 생성자 함수라고 단정할 수 없다.

const obj = {}

console.log(obj.constructor === Object) // true

객체 리터럴에 의해 생성된 객체인 obj의 constructor 프로퍼티가 Object와 같다는 것은 obj가 생성되는 방식이 Object 생성자 함수와 연관된 것이 아닌가 의심이 든다.

1-1. object의 생성자 함수

obejct 생성자 함수에 의해 생성된 객체에는 다음과 같은 규칙이 있다.

  1. new.target이 undefined나 Object가 아닌 경우

  2. Object 생성자 함수에 의한 객체 생성 시, 인수가 전달되지 않았다면 추상 연산 OrdinaryObjectCreate를 호출하여 빈 객체를 생성한다.

  3. 인수가 전달된 경우에는 인수를 객체로 변환한다.


//2 번 경우, 인수가 없는 경우는 추상연산 OrdinaryObjectCreate를 호출하여 빈 객체를 생성한다.
let obj = new Object()
console.log(obj) // {}
// 1번 경우, new.target이 undefined이거나 Object가 아닌 경우
class Foo extends Object {}
new Foo()
//3 번 경우 인수가 있는 경우에는 인수를 객체로 변환한다.
obj = new Object(123)
console.log(obj) // [Number: 123]

obj = new Object('12312')
console.log(obj) // [String: '12312']

재밌는 것은 Object를 통한 객체 생성이 아닌,객체 리터럴로 빈 객체를 생성할 때도 추상 연산OrdinaryObjectCreate를 호출하여 빈 객체를 생성하고 프로퍼티를 추가하도록 정의되어 있다.

이처럼 Object 생성자 함수 호출과 객체 리터럴의 평가는 추상 연산 OrdinaryObjectCreate를 호출하여 빈 객체를 생성하는 점에서 동일하나, new.target의 확인이나 프로퍼티를 추가하는 처리 등 세부 내용은 다르다.

따라서, 객체 리터럴에 의해 생성된 객체는 Object 생성자 함수가 생성한 객체가 아니다.

그러나, 큰 틀에서 생각해보면 리터럴 표기법으로 생성한 객체도 생성자 함수로 생성한 객체와 본직적인 면에서 큰 차이는 없다.

미묘한 차이는 있지만 객체로서 동일한 특징을 갖기 때문이다.

따라서, 프로토타입의 constructor 프로퍼티를 통해 연결되어 있는 생성자 함수를 리터럴 표기법으로 생성한 객체를 생성한 생성자 함수로 생각해도 큰 무리는 없다.

즉, 다음과 같이 생각해도 된다는 것이다.

|리터럴|        |생성자 함수|       |포로토타입|
-----------------------------------------------
객체리터럴         Object         Object.prototype

2. 프로토타입의 생성 시점

객체는 리터럴 표기법 또는 생성자 함수에 의해 생성되므로 결국 모든 객체는 생성자 함수와 연결되어 있다.

  • 참고: Ojbect.create 메서드와 클래스에 의한 객체 생성
    아직까지 나온 내용은 아니지만, Object.create메서드와 클래스로 객체를 생성하는 방법도 있다. 이들도 객체 생성자 함수와 연결되어 있으니 추후에 알아보도록 하자

프로토타입은 생성자 함수가 생성되는 시점에 더불어 생성된다. 프로토타입과 생성자 함수는 단독으로 존재할 수 없고, 쌍으로 존재한다.

생성자 함수는 사용자가 직접 정의한 사용자 정의 생성자 함수와, 자바스크립트가 기본적으로 제공하는 빌트인 생성자 함수로 구분할 수 있다.

사용자 정의 함수와 빌트인 생성자 함수를 구분하여 프로토타입 생성 시점에 대해 살펴보자

2.1 사용자 정의 생성자 함수와 프로토타입 생성 시점

이전에 살펴본 바와 같이 내부 메서드 [[Construct]]를 갖는 함수 객체, 즉 화살표 함수나 ES6의 메서드 축약 표현으로 정의하지 않고, 일반 함수(함수 선언문, 함수 표현식)으로 정의한 함수 객체는 new 연산자와 함께 생성자 함수로서 호출할 수 있다.

생성자 함수로서 호출할 수 있는 함수, 즉 constructor는 함수 정의가 평가되어 함수 객체를 생성하는 시점에 프로토타입도 더불어 생성된다.

그렇다면 함수 선언문은 런타임 이전에 자바스크립트 엔진에 의해 먼저 실행된다는 것을 알 수 있었다. 따라서, 함수 선언문으로 정의된 함수는 어떤 코드보다 먼저 평가되어 함수 객체가 될 것이다.

console.log(Person.prototype) // {constructor: ƒ}

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

이에 따라 Person 생성자 함수가 아래에 있는데도 불구하고 함수가 먼저 생성되었다는 것을 확인할 수 있다.

const Person = name => {
    this.name = name
}

console.log(Person.prototype) // undefined

Arrow 함수는 non-constructor 이므로 prototype 호출 시 나오는 것이 없다.

이처럼 빌트인 생성자 함수가 아닌, 사용자 정의 생성자 함수는 자신이 평가되어 함수 객체로 생성되는 시점에 프로토타입도 더불어 생성되며, 생성된 프로토타입의 프로토타입은 언제나 Object.prototype 이다.

3. 빌트인 생성자 함수와 프토토타입 생성 시점

Object, String, Number, Function, Array, RexExp, Date, Promise 등과 같은 빌트인 생성자 함수도 일반 함수와 마찬가지로 빌트인 생성자 함수가 생성되는 시점에 프로토타입이 생성된다. 모든 빌트인 생성자 함수는 전역 객체가 생성되는 시점에 생성된다. 생성된 프로토타입은 빌트인 생성자 함수의 prototype 프로퍼티에 바인딩된다.

4. 생성자 함수에 의해 생성된 객체의 프로토타입

new 연산자와 함께 생성자 함수를 호출하여 인스턴스를 생성하면 다른 객체 생성 방식과 마찬가지로 추상 연산 OrdinaryObjectCreate 함수가 호출된다. 이때 추상 연산 OrdinaryObjectCreate에 전달되는 프로토타입은 생성자 함수의 prototype 프로퍼티에 바인딩되어 있는 객체이다. 즉, 생성자 함수에 의해 생성되는 객체의 프로토타입은 생성자 함수의 prototype 프로퍼티에 바인딩되어 있는 객체다.

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

Person.prototype.hello = function(){
    console.log(`my name is ${this.name}`)
}

const me = new Person('park')
const you = new Person('kang')

me.hello() // my name is park
you.hello() // my name is kang

Person.prototype 안에 constructor 프로퍼티로 function Person{}을 가지고 있고, 생성자 함수 Person안의 프로퍼티 prototype으로 Person.prototype을 가지고 있다.

여기서 Person.prototype.hello 프로퍼티에 함수를 추가하면, meyou 객체는 prototypeproperty를 사용할 수 있어 hello를 부를 수 있다.

이는 흔한 객체 지향 프로그래밍의 모습인데, java/kotlin에서의 객체 지향과는 달리 재밌는 것은 name프로퍼티가 Person.prototype에 없는데도 불구하고 hello 함수에서 this.name으로 호출할 수 있다는 것이다.

심지어 name 프로퍼티는 me, you에 있는 프로퍼티인데, 상위 prototype이 이를 호출할 수 있다는 것이다.

지금은 생성자 함수를 통해 생성된 모든 객체는 프로토타입의 property를 사용할 수 있다는 것만 알아두자

post-custom-banner

0개의 댓글