JavaScript: 객체 Object(3)

Beautify.log·2021년 11월 12일
0
post-thumbnail

안녕하세요. 저번 포스팅에서 프로토타입의 상속에 대해 간략하게 살펴보았습니다.

이번 포스팅에서는 생성자를 new 연산자로 호출하여 인스턴스를 생성했을 때 객체 내부에서 어떤 변화가 일어나는지 알아보도록 하겠습니다.

new 연산자의 역할?

가령 아래와 같은 코드가 있다고 가정해봅시다.

// 1
function Circle (center, radius) {
  this.center = center;
  this.radius = radius;
}

// 2
Circle.prototype.area = function () {
  return Math.PI * this.radius * this.radius;
}

첫번째 함수는 원의 정보에 대해 나타내주는 함수입니다.
두번째 함수는 첫번째 함수 객체를 불러와 원의 넓이를 구해주는 함수입니다.

본래 우리가 new 연산자로 Circle 이라는 생성자를 사용한다고 할 때 아래와 같은 과정을 거쳤습니다.

// center(중심)이 (0, 2)이고 반지름이 2.0인 원
let circle = new Circle({x: 0, y: 2}, 2.0)

이렇게 new 연산자를 사용할 때 다음과 같은 절차를 거치게 됩니다.

let circleObj = {};

circleObj.__proto__ = Circle.prototype;

Circle.apply(circleObj, arguments);

return circleObj;

우선 빈 객체를 만들어 줍니다. 그리고 Circle.prototype을 새로 생성된 circleObj의 프로토타입으로 지정합니다.

이 때 Circle.prototype이 객체가 아닌 경우에는 Object.prototype을 프로토타입으로 설정해주게됩니다.

그 다음 Circle 생성자를 실행하여 circleObj를 초기화 해줍니다. 여기에서 apply()는 주어진 this 값과 배열로 제공되는 arguments를 호출합니다. arguments는 객체이며 함수에 전달된 인수에 해당하는 Array 형태의 객체입니다.

마지막으로 완성된 객체를 결과값으로 반환하게 됩니다.

우리가 circleObj.__proto__ = Circle.prototype; 와 같이 지정하는 부분에 대해 유심히 살펴보아야 합니다. 생성자의 프로토타입 프로퍼티를 인스턴스의 __proto__로 대입하고 있는데, 이렇게 하여 인스턴스의 프로토타입 체인이 정의되고, 생성자로 생성한 모든 인스턴스가 생성자의 프로토타입 객체의 프로퍼티를 사용할 수 있게 됩니다.

이와 같이 생성자를 new 연산자로 호출하게 되면 객체의 생성과 프로토타입 설정, 객체 초기화를 수행할 수 있게 되는 것입니다.

프로토타입 객체의 프로퍼티?

자바스크립트의 함수 객체는 기본적으로 prototype 프로퍼티를 갖고 있습니다. 이는 프로토타입 객체를 가리키고 이 프로토타입 객체는 constructor 프로퍼티와 내부 프로퍼티인 [[Prototype]] 또는 __proto__를 갖습니다.

constructor 프로퍼티란?

constructor 프로퍼티는 함수 객체의 참조를 값으로 갖습니다.

function P () {};
console.log(P.prototype.constructor);	// Function P () {}

생성자와 생성자의 프로토타입 객체는 서로를 참조하는 형태입니다. 생성자의 prototype 프로퍼티가 프로토타입 객체를 가리키고 이 프로토타입 객체의 constructor 프로퍼티가 생성자를 가리키는 체인으로 묶여 있습니다.

그러나 생성자로 생성한 인스턴스는 생성될 때 프로토타입 객체의 참조만 가지고 있지 생성자와 직접적인 체인관계는 없습니다.

인스턴스가 어떤 생성자로 생성되었는지 확인하는 방법으로 인스턴스가 가진 프로토타입의 constructor 프로퍼티를 확인하는 방법이 있습니다.
인스턴스가 프로토타입에서 constructor 프로퍼티를 상속받기 때문에 이를 인스턴스의 프로퍼티로 참조할 수 있는 것입니다.

function P () {};
myObj = new P();
console.log(myObj.constructor);		// Function P () {}

[[Prototype]]이란?

[[Prototype]]은 함수 객체가 가진 프로토타입 객체의 내부 프로퍼티입니다.

이것은 기본적으로 Object.prototype을 가리키는데 프로토타입 객체의 프로토타입이 됩니다.

예를 들어,

function P () {};
console.log(P.prototype.__proto__);

라고 한다면 콘솔에는 Object {} : Object.prototype 이 출력될 것입니다.

생성자로 생성한 인스턴스가 Object.prototype의 프로퍼티를 사용할 수 있는 것, Object.prototype의 프로토타입은 null을 가리킨다는 사실을 꼭 기억해둡시다!

프로토타입 객체를 교체하는 방법과 constructor 프로퍼티

생성자가 가진 prototype을 개로운 객체로 교체할 때 주의해야할 점이 있습니다. 프로퍼티만 정의된 새로운 객체를 prototype으로 대입하게 되면 인스턴스와 생성자 사이의 체인이 끊어집니다. 왜냐하면 그 객체에는 constructor가 부재하게 되기 때문입니다.

이 때문에 인스턴스와 생성자 사이의 체인을 유지하고자 한다면 prototype으로 사용할 객체에 constructor를 정의하고 그 프로퍼티에 생성자의 참조를 대입해 주는 과정이 필요합니다.

이전에 사용했던 예시를 다시 가져와 보겠습니다.

function Circle (center, radius) {
  this.center = center;
  this.radius = radius;
}

이 함수의 프로토타입 객체에 constructor를 직접 지정해보겠습니다. 생성자를 constructor로 대입하는 것입니다.

Circle.prototype = {
  constructor: Circle,
  area: function () { return Math.PI * this.radius * this.radius }
}

이제 circleCircle 함수를 사용하여 원의 넓이를 구하는 함수를 변수로 지정해보겠습니다.

let circle = new Circle({x: 0, y: 2}, 2.0);

자 그럼 결과를 확인해보겠습니다.

console.log(circle.constructor);	// Function Circle
console.log(circle instanceof Circle);	// true

첫번째는 constructor 즉 인스턴스의 프로토타입을 만든 참조를 반환하는데 Function Circle인 것을 보아 circleCircle 함수의 프로토타입을 참조하고 있다는 사실이 명확해집니다.

두번째는 instanceof 즉 인스턴스 circle이 생성자 Circle로 생성된 것인지 확인해주는 연산자인데 true로 반환되는 것으로 보아 circleCircle로 생성되었음을 알 수 있습니다.

인스턴스 생성 이후 생성자의 프로토타입을 수정하거나 교체했을 때

인스턴스의 프로토타입은 생성자가 인스턴스를 만들 때 갖고 있던 프로토타입 객체입니다. 인스턴스를 생성한 후 생성자의 prototype 프로퍼티를 바꾸더라도 인스턴스의 프로토타입은 바뀌지 않습니다.

인스턴스의 프로퍼티는 생성될 당시 시점의 프로토타입을 상속받기 때문에 생성된 이후에 생성자의 프로토타입을 바꾼다고 해도 교체한 객체로부터 프로퍼티를 상속받지 않습니다.

예를 들어 위에서 사용한 코드를 다시 가져와보겠습니다.

function Circle (center, radius) {
  this.center = center;
  this.radius = radius;
}

let circle = new Circle({x: 0, y: 2}, 2.0);

Circle.prototype = {
  constructor: Circle,
  area: function () { return Math.PI * this.radius * this.radius }
}

여기에서 바로 circle.area()를 호출해보면 어떻게 나올까요?

circle.area();
// Uncaught TypeError: circle.area is not a function

이렇게 에러가 발생하지요? 그렇지만 생성자가 기존에 갖고 있던 프로토타입 객체에 프로퍼티를 추가한 경우에는 생성자와 인스턴스의 체인이 끊어지지 않습니다.

Circle.prototype.area = function () {
  return Math.PI * this.radius * this.radius;
}

circle.area()	// 12.56637.....

자 이렇게 new 연산자의 역할 그리고 프로토타입 객체의 프로퍼티에 대해 살펴보았습니다.
constructor로 생성자와 생성자의 프로토타입 객체가 서로를 참조하는지 확인할 수 있고, 인스턴스와 생성자의 체인관계를 유지하기 위해 이것을 별도로 정의해 주어야 한다는 점까지 알 수 있었습니다. 또한 인스턴스를 생성한 후 프로토타입 프로퍼티를 수정하더라도 인스턴스 자체의 프로토타입은 바뀌지 않는다는 점도 알 수 있었지요.

다음 포스팅에서는 프로토타입을 확인하는 방법에 대해 살펴보겠습니다.

profile
tried ? drinkCoffee : keepGoing;

0개의 댓글