[JS스터디] 17. 생성자 함수에 의한 객체 생성

2023. 4. 13. 19:14JavaScript

Object 생성자 함수

생성자 함수 : new 연산자와 함께 호출하여 객체를 생성하는 함수

이 때 생성자 함수를 통해 생성된 객체를 instance라 한다.

 

Object, String, Number, Boolean, Function, Array, Date, RegExp, Promise 등 빌트인 생성자 함수 제공

 

객체를 생성하는 방법으로는 객체 리터럴 {}을 사용하는 것이 더 간편하기 때문에, 특별한 이유가 없다면 굳이 Object 생성자 함수를 사용할 필요는 없다.

생성자 함수

객체 리터럴에 의한 객체 생성의 문제점

객체 리터럴{}에 의해 객체를 생성하면,

프로퍼티 구조가 동일함에도 불구하고 매번 같은 프로퍼티와 메소드를 기술해야 한다는 문제가 있다.

생성자 함수에 의한 객체 생성의 장점

생성자 함수를 마치 인스턴스를 생성하기 위한 클래스처럼 사용하여,

프로퍼티 구조가 동일한 객체 여러 개를 간편하게 생성할 수 있다.

// 생성자 함수 정의
function Circle(radius) {
	this.radius = radius; // 생성자 함수 내부의 this: 생성할 인스턴스를 가리킴!!
	this.getDiameter = function() {
		return 2 * this.radius;
	};
}
const circle1 = new Circle(5);  // radius가 5인 인스턴스 생성
const circle2 = new Circle(10); // radius가 10인 인스턴스 생성
💡 this (22장)

자기 참조 변수.
객체 자신의 프로퍼티나 메소드를 참조하기 위함.
this 바인딩 (this가 가리키는 객체값)은 동적으로 결정

- 일반 함수로서 호출 → this는 전역 객체
- 메소드로서 호출 → this는 메소드 호출한 객체
- 생성자 함수로서 호출 → 생성자 함수가 생성할 인스턴스

 

생성자 함수는 그 형식이 정해져있지 않고, (unlike 자바)

일반 함수와 동일하게 정의하고 new 연산자와 함께 호출하면 생성자 함수로 동작하여 인스턴스를 반환한다.

new 연산자 없이 호출하면 그냥 일반 함수로 동작한다.

생성자 함수의 인스턴스 생성 과정

생성자 함수의 역할: 인스턴스 생성, (생성된 인스턴스 초기화)

여기서 인스턴스 초기화란, 함수에 전달된 인수를 프로퍼티의 초기값으로 할당하는 것이다.

인스턴스 생성은 자바스크립트 엔진에 의해 암묵적으로 생성된다.

  1. 인스턴스 생성this 바인딩
    암묵적으로 빈 객체가 생성되어 인스턴스가 생성된다.
    이 인스턴스는 this에 바인딩된다.
    이는 런타임 이전에 실행된다.
  2. 인스턴스 초기화
    this에 바인딩 되어있는 인스턴스 초기화
    this에 바인딩 되어있는 인스턴스에 프로퍼티/메소드 추가하고 전달받은 인수로 초기화
    개발자가 처리
  3. 인스턴스 반환
    생성자 함수 내부의 모든 처리가 끝나면, this가 암묵적으로 반환된다.
    단, 다른 객체를 명시적으로 반환하면 this의 암묵적 반환은 무시된다.
    이 때 다른 ‘객체’가 아닌 ‘원시값’을 반환하면 값의 명시적 반환이 무시되고, this의 암묵적 반환이 무시되지 않는다.
function Circle1(radius) {
	this.radius = radius;
	this.getDiameter = () => {return 2 * this.radius};
	// 여기서 this 암묵적 반환
};
function Circle2(radius) {  // 객체를 명시적으로 반환하는 경우
	this.radius = radius;
	this.getDiameter = () => {return 2 * this.radius};
	// 여기서 this 암묵적 반환, but 명시적 반환으로 무시됨.
	return {}; // 객체 명시적 반환
};
function Circle3(radius) {  // 원시값을 명시적으로 반환하는 경우
	this.radius = radius;
	this.getDiameter = () => {return 2 * this.radius};
	// 여기서 this 암묵적 반환
	return 100; // 원시값 명시적 반환 - 원시값이라 무시됨.
};

console.log(new Circle1(1)); // Circle1 {radius: 1, getDiameter: f}
console.log(new Circle2(1)); // {}
console.log(new Circle3(1)); // Circle1 {radius: 1, getDiameter: f}

따라서 this의 반환이 정상적으로 이루어지게 하려면 다른 값을 반환해서는 안 된다. return문을 생략해야 한다.

내부 메소드 [[Call]]과 [[Construct]]

함수는 객체이므로 일반 객체와 동일하게 동작할 수 있다.

일반 객체가 가지고 있는 내부 슬롯과 내부 메소드를 모두 가지고 있기 때문이다.

function foo() {}

foo.prop = 10; // 함수는 객체 -> 프로퍼티 소유 가능
foo.method = function () { // 함수는 객체 -> 메소드 소유 가능
	console.log(this.prop);
};

foo.method(); // 10

다만 일반 객체는 호출할 수 없지만 함수는 호출할 수 있다는 차이가 있다.

따라서 일반 객체의 내부 메소드 외에도 [[Call]], [[Construct]] 메소드를 추가로 가지고 있다.

[[Call]]은 모든 함수 객체가 가지고 있어 호출이 가능하지만,

[[Construct]]는 생성자 함수로 호출할 수 있는 함수만 가지고 있다.

일반 함수를 호출하면 [[Call]]이, 생성자 함수로 호출하면 [[Construct]]가 호출된다.

  • callable
  • constructor : 일반 함수 또는 생성자 함수로서 호출할 수 있는 객체
    • [[Construct]]를 가진다.
      1. 함수 선언문, 2. 함수 표현식, 3. 클래스에 의해 정의된 함수
  • non-constructor : 일반 함수로서만 호출할 수 있는 객체
    • [[Construct]]를 갖지 않는다.
      1. 메소드(ES6 메소드 축약 표현), 2. 화살표 함수에 의해 정의된 함수
// constructor: 일반 함수로 선언된 함수 
function foo() {} // 함수 선언문
const bar = function () {}; // 함수 표현식
const baz = {
	x: function () {} // 프로퍼티의 값으로 할당된 함수는 일반 함수, 메소드 X
};
//생성자 함수로서 호출
new foo(); // foo {}
new bar(); // bar {}
new baz.x(); // x {} -- 똑같이 일반 함수로 정의됨

// non-constructor: 메소드나 화살표 함수로 선언된 함수
const arrow = () => {};
const obj = {
	x() {} // 이게 메소드!
};
//생성자 함수로서 호출
new arrow(); // TypeError
new obj.x(); // TypeError

new 연산자

new 연산자로 함수를 호출하면 함수는 일반 함수가 아닌 생성자 함수로 동작한다.

즉, [[Call]]이 아닌 [[Construct]]가 호출된다.

이 때 non-constructor 함수는 new와 함께 호출되지 못한다.

function Circle(radius) {
	this.radius = radius;
	this.getDiameter = function () {
		return 2 * this.radius;
	};
}
const circle = Circle(5); // new X, 일반 함수로 호출
console.log(circle); //undefined
console.log(radius); //일반 함수 내부의 this는 전역 객체 window 가리킴
console.log(getDiameter()); //10, 일반 함수 내부의 this는 전역 객체 window 가리킴
circle.getDiameter(); //TypeError

new.target

new연산자와 함께 생성자 함수로 호출되었는지 확인

O → new.target은 함수 자신을 가리킴

X → new.target은 undefined

new.target을 함수 안에서 사용하여 new와 함께 생성자 함수를 호출했는지 확인하고, 그렇지 않으면 new와 함께 재귀 호출하여 생성자 함수로 호출

function Circle(radius) {
	if (!new.target) { // new와 함께 호출되지 않아 new.target이 undefined라면
		return new Circle(radius); // new와 함께 호출!
	}
	this.radius = radius;
	this.getDiameter = function() {
		return 2 * this.radius;
	};
}
const circle = Circle(5); // new 없이 호출해도,
console.log(circle.getDiameter()); // 생성자 함수로서 호출됨 !!

대부분의 빌트인 생성자 함수 Object, String, Number, Boolean, …는 new와 호출되었는지 확인한 후 적절한 값을 반환한다. 따라서 이들은 new 없이 호출해도 생성자 함수처럼 작동한다.

다만 String, Number, Boolean은 new와 함께 호출하면 객체를 반환하지만 그렇지 않으면 타입을 변환하여 문자열, 숫자, 불리언 값을 반환한다.


스터디 교재