front-end/JavaScript

[JavaScript] 생성자 함수

Ash_O 2023. 4. 12. 19:32

 

객체 리터럴로 객체를 생성하는 방법은 일반적이고 간단한 방법이다

객체리터럴 외에도 객체를 만드는 방법은 다양하다.

  • 생성자함수
  • Object.create 메서드
  • 클래스

 

생성자 함수로 객체를 만드는 방법을 살펴본다

 

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

인스턴스 (instance) : 생성자 함수에 의해 생성된 객체

 

생성자 함수로 인스턴스를 공장에서 찍어내듯 만들 수 있다.

 

new 연산자와 함께 생성자 함수를 호출하면 객체를 생성하여 반환한다.

 

new 연산자

new 연산자사용자 정의 객체 타입 혹은 내장 객체 타입의 인스턴스를 생성한다.

 

빌트인 생성자 함수

Object 생성자 함수를 이용하여 객체를 만드는 방법

const person = new Object();

person.name = 'Park';
person.sayHello = function () {
	console.log('hello');
};

///////////////////////////////////

const person = new Object({
	name : 'Park',
	sayHello : function () {
		console.log('Hello')
	}
});

 

브라우저 콘솔에서 확인해보면 person의 프로토타입을 확인해보면 생성자 함수가 Object 임을 확인해볼 수 있다.

 

 

Object 생성자 함수 외에도 여러 빌트인 생성자 함수가 존재한다

 -> Number, String, Boolean, Function, Array, Date, RegExp, Promise

 

new 연산자와 함께 호출하면 객체를 만들어 낸다.

 

Number의 경우도 new 연산자와 함께 호출하면 Object를 만들어내는 것을 확인할 수 있다.

prototype을 확인해보면 constructor가 Number 생성자 함수임을 확인할 수 있었다

 

객체를 생성하는 방법은 객체 리터럴을 사용하는게 간편하긴 하다

Object 생성자 함수를 사용해서 객체를 생성하는 방식은 특별한 이유가 없다면 유용하지 않을 수 있다

 

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

객체 리터럴로 객체를 생성하는 방식은 단 하나의 객체만 생성한다.

동일한 프로퍼티를 갖는 객체를 여러 개 생성해야 하는 경우 매번 같은 프로퍼티를 작성해야 한다.

비효율적이다.

 

객체는 프로퍼티를 통해 그 객체의 특성을 나타낸다. 그래서 객체마다 프로퍼티의 값이 다를 수는 있다.

그러나, 메서드는 프로퍼티를 참조하고 조작하는 행위를 하는 함수다.

즉, 메서드는 객체마다 내용이 동일한 경우가 일반적이다.

 

const person1 = {
	name : 'Lee',
	sayHello : function () {
		console.log('Hello!')
	}
}
const person2 = {
	name : 'Park',
	sayHello : function () {
		console.log('Hello!')
	}
}
const person3 = {
	name : 'Kim',
	sayHello : function () {
		console.log('Hello!')
	}
}

 

위 예제의 person 객체들은 프로퍼티 구조가 같다.

이름을 나타내는 name 프로퍼티는 값이 객체마다 다를 수 있는데,

반면에 sayHello 메서드는 완전히 동일하다.

 

객체 리터럴에 의해 객체를 생성하는 경우 프로퍼티 구조가 동일함에도 매번 같은 프로퍼티와 메서드를 기술해야 한다.

이런 특징은 객체를 많이 만들게 될수록 문제가 커진다


this

메서드는 자신이 속한 객체의 프로퍼티를 참조 및 조작할 수 있어야 한다.

이를 위해서 메서드가 자신이 속한 객체의 프로퍼티를 참조하려면, 자신이 속한 객체를 가리키는 식별자를 참조할 수 있어야 한다.

 

객체 리터럴의 경우

객체 리터럴 방식으로 생성한 객체의 메서드가 내부에서 자신이 속한 객체를 재귀적으로 참조한다

 

const person1 = {
	name : 'Park',
        sayHi : function () {
            console.log(`myname is ${person1.name}`);
        }
}

 

메서드내부에서 자신이 속한 객체인 person1 식별자를 참조할 수 있는 이유는 다음과 같다.

  1. 객체 리터럴은 person1 변수에 할당되기 직전에 평가가 된다.
  2. sayHi 메서드가 호출되는 시점에서는 객체 리터럴의 평가가 완료된 상황이다
  3. 메서드 호출 시점에는 이미 person1 식별자에 평가된 객체가 할당되었다.

그러나 이런 방법은 일반적이지 않고 좋은 방법은 아니다

생성자 함수 방식으로 인스턴스를 만들게 된다면 이런 방법은 사용할 수 없다.

 

const Person = function(name) {
	????.name = name;
	????.sayHi = function() {
		console.log(`Myname is ${????.name}`)
	}
}
// 위 상황에서는 생성자 함수가 자신이 생성할 인스턴스에 대한 정보를 모른다.

const person1 = new Person('Lee');
// 생성자 함수로 인스턴스를 생성하려면 생성자 함수가 정의되어야 한다.

 

생성자 함수에 의한 객체 생성 방식은 생성자 함수를 먼저 정의하고 new 연산자로 생성자 함수를 호출하는 단계가 필요하기 때문이다.

근데 생성자 함수에 프로퍼티나 메서드를 추가하기 위해서는 자신이 앞으로 생성할 인스턴스를 참조할 수 있어야 한다.

 

다시 말해, 생성자 함수로 인스턴스를 생성하려면 먼저 생성자 함수가 존재해야 하는데 이 시점에선 인스턴스가 만들어지기 이전이라서
객체 리터럴 방식과는 다르게 생성할 인스턴스에 대한 식별자를 알 방법은 없기 때문에 생성할 인스턴스를 참조할 수 없다는 말이다.

 

그래서 this가 제공된다.

 

this는 객체 자신의 프로퍼티나 메서드를 참조하기 위한 자기 참조 변수다.

 

생성자 함수에서 this는 자신이 속한 객체나 자신이 생성할 인스턴스를 가리키기도 하며,

this를 통해서 자신이 속한 객체나 인스턴스의 프로퍼티와 메서드를 참조할 수도 있다

 

함수 내에서 this가 가리키는 값은 함수 호출 방식에 따라 다르다

  • 일반 함수로서 호출하면 this는 전역 객체를 가리킨다
  • 메서드로서 호출하면 this는 메서드를 호출한 객체를 가리킨다
  • 생성자 함수로서 호출하면 this는 생성자 함수가 생성할 인스턴스를 가리킨다.

 

즉 this를 통해서 생성자 함수는 자신이 생성할 인스턴스를 참조할 수 있다

 


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

사용자 정의 객체 생성은 두가지 단계가 필요하다

  1. 함수를 작성하여 객체 타입 정의
  2. new 연산자로 객체의 인스턴스를 생성

 

function Foo(bar1, bar2){
	this.bar1 = bar1;
    	this.bar2 = bar2;
}

const foo = new Foo('Bar1', 'Bar2');

 

new 키워드가 함수와 호출되면, 그 함수는 생성자 함수로 사용된다

 

function Person(name){
	this.name = name;
	this.sayHi = function () {
		console.log(`Hello! my name is ${this.name}`)
	};
}

const person1 = new Person('Lee');
const person2 = new Person('Park');
const person3 = new Person('Kim');

person1.sayHello() // Hello! my name is Lee
person2.sayHello() // Hello! my name is Park
person3.sayHello() // Hello! my name is Kim

 

생성자 함수에 의한 객체 생성 방식은 객체를 생성하기 위한 템플릿처럼 생성자 함수를 사용한다.

위와 같이 생성자 함수를 통해 프로퍼티 구조가 동일한 객체를 여러 개 생성할 수 있다.

 

new 연산자를 통해 Person 생성자 함수로 만든 인스턴스를 살펴보면 Object 타입을 가지고 있다

또한, constructor가 Person 생성자 함수를 가리키고 있음을 알 수 있다.

생성자 함수는 객체를 생성하는 함수다

클래스 기반 객체지향언어와 다르게 자바스크립트의 경우 일반 함수와 동일한 방법으로 생성자 함수를 정의한다.

그리고 new 연산자와 함께 그 함수를 호출하면 생성자 함수로 동작한다.

 

만약 new 연산자와 함께 생성자 함수를 호출하지 않으면 일반 함수로 동작한다.

(대표적으로 Number 가 있다)

 

그리고 위 예제 Person 함수의 경우, new 연산자 없이 일반 함수로 호출되면 this는 전역 객체를 가리키게 된다.

따라서 name 프로퍼티와, sayHi 메서드는 전역 객체의 프로퍼티와 메서드가 된다.

const Person = function(name) {
	this.name = name;
	this.sayHi = function() {
		console.log(`Myname is ${this.name}`)
	}
}

const person1 = Person('new 연산자가 없어요');

console.log(name) // new 연산자가 없어요
sayHi() // Myname is new 연산자가 없어요

 

 

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

생성자 함수의 역할

  • 프로퍼티 구조가 동일한 인스턴스들을 생성하기 위한 템플릿으로서 동작
  • 생성된 인스턴스를 초기화 (인스턴스 프로퍼티 추가 및 초기값 할당)

 

생성자 함수가 인스턴스를 만드는 것은 필수이다.

그리고 인스턴스를 초기화하는 것은 옵션이다.

 

function Circle(radius) {
	this.radius = radius;
	this.getDiameter = function () {
		return 2 * this.radius;
	}; // 지름
}

const circle1 = new Circle(5);

 

위 코드에서는 인스턴스를 생성하고 반환하는 코드는 없다

그러나 자바스크립트 엔진은 암묵적인 처리를 통해 인스턴스를 생성하고 반환한다.

 

new 연산자와 함께 생성자 함수를 호출하면 내부에서 암묵적으로 인스턴스가 생성되고 this와 연결된다
그리고 this에 연결되어 있는 인스턴스를 초기화하고 암묵적으로 인스턴스를 반환한다.

 

 

Person 예시로 생성자 함수로 인스턴스가 만들어지는 과정을 그려보았다.

우선 암묵적으로 빈 객체가 생성된다.

이때 생성되는 빈 객체가 생성자 함수가 생성할 인스턴스이다

그리고 이렇게 생성된 빈 객체가 this와 연결된다

해당 처리는 런타임 이전에 진행된다

 

 

이후 생성자 함수에 기술되어 있는 코드가 한 줄씩 실행되어서 this에 바인딩 되어있는 인스턴스를 초기화 한다.

즉,

  • this에 연결되어 있는 인스턴스에 프로퍼티나 메서드를 추가한다.
  • 생성자 함수가 인수로 전달받은 초기값을 인스턴스의 프로퍼티에 할당한다.

 

 

그리고 생성자 함수 내부의 모든 처리가 끝나면 완성된 인스턴스에 연결된 this를 암묵적으로 반환한다

만약 this가 아닌 다른 객체를 명시적으로 반환하면 this가 반환되지 못하고 return문에 명시한 객체가 된다.

 

주의할 점은 this가 아닌 다른 객체를 명시적으로 반환하는 코드를 작성해놓으면, this가 반환되지 못한다.


참고 : deepdive

 

'front-end > JavaScript' 카테고리의 다른 글

[JavaScript] Array.prototype.forEach , map, filter  (0) 2023.04.20
[JavaScript] 콜백함수, 화살표 함수  (0) 2023.04.20
[JavaScript] 객체 리터럴  (0) 2023.04.10
[JavaScript] 타입 변환  (0) 2023.04.08
[JavaScript] 변수  (1) 2023.04.06