본문 바로가기
JavaScript Study

JavaScript - #20. 생성자 함수에 의한 객체생성

by KMS_99 2023. 9. 13.

JavaScript - #20. 생성자 함수에 의한 객체생성

객체의 생성법 중 이번에는 생성자 함수를 통한 객체의 생성에 대해서 알아보겠다.

 

이전에 배운 방법은 객체 리터럴을 통한 생성이였다.

그렇다면 두 방식은 어떤 차이점이 있으며, 무엇을 사용하는 것이 좋을까?

 

객체 내부에는 여러 프로퍼티와 매서드가 정의되어있다.

만약 유저의 정보를 담는 객체가 있다고 생각해보자.

그 안에는 유저의 이름, 나이, 사는 곳, 등이 정의되어있다.

유저가 한명일 때는 객체 리터럴을 통해서 충분히 만들 수 있지만, 유저가 몇백명이 넘는다고 가정해보자.

 

값은 다르겠지만 동일한 키의 프로퍼티, 매서드를 가지는 객체를 리터럴로 계속 선언한다면 효율적이지 못할 것이다.

이렇게 비슷한 객체를 여러개 선언할 때 생성자 함수에 의한 객체생성이 더욱 간편하고 효율적이다.

 

이제 사용법에 대해서 알아보자.

function Circle (radius){
    const radiusDouble = radius*2;
    this.radius = radius;
    this.doubleRadius = radiusDouble
    this.getDiameter = function(){
        return this.radius * 2;
    }
}

const circle1 = new Circle(5);

위 함수 선언문이 생성자 함수이며, 아래 circle1은 생성자 함수로 만들어진 객체이다.

천천히 코드를 읽어보자.

먼저 일반 함수와 다르게 함수의 이름의 첫글자가 대문자로 이루어져있다.

이는 일반 함수와 생성자 함수를 구별하고자 하는 약속이라고 생각하면된다.

일반 함수와 생성자 함수를 구별하는 이유는 생성자 함수라고 해도 일반함수처럼 사용할 수 있기 때문이다.

 

다음으로 radius라는 매개변수를 가지고 있다.

이는 생성자함수를 통해 객체를 선언할 때 매개변수를 이용해서 내부의 값을 변경할 수 있다는 것이다.

 

함수 내부를 확인해보자.

this 라는 키워드가 보일 것이다.

이는 선언된 객체를 뜻한다. 즉 const circle1 = new Circle(5);에서 생성된 객체 그자체가 되는 것이다.

따라서 내부에 있는 this.radius, this.doubleRadius, this.getDiameter는 "object.name"과 같은 모습을 띄기 때문에 생성된 객체의 프로퍼티, 매서드가 된다.

내부에서 const radiusDouble은 객체와 관련이 없는 변수이다.

 

이제 객체의 선언부를 확인해보자.

const circle1 = new Circle(5); 에서 circle1에 생성자 함수를 통해 만들어진 객체가 참조된다.

이때 눈에 띄는 부분은 new 키워드이다.

new 키워드는 생성자 함수를 생성하기 위한 연산자이다.

다음 코드를 보고 동작을 예측해보자.

function Circle (radius){
    const radiusDouble = radius*2;
    this.radius = radius;
    this.doubleRadius = radiusDouble
    this.getDiameter = function(){
        return this.radius * 2;
    }
}

const circle1 = new Circle(5);
const circle2 = Circle(10);

console.log (circle1);
console.log (circle2);

circle1과 circle2의 차이는 new의 유무이다.

결과는 다음과 같다.

Circle {
  radius: 5,
  doubleRadius: 10,
  getDiameter: [Function (anonymous)]
}
undefined

circle1에는 Circle 생성자함수를 통해 생성된 객체가 들어있지만,

circle2에는 아무것도 들어있지 않다.

 

이유는 생성자함수는 일반 함수처럼 사용할 수 있기 때문이다.

앞서서 생성자함수와 일반함수를 구별하는 것은 함수이름을 시작할 때 대문자를 사용하는 것이라고 했다.

하지만 근본적으로 해결방법은 아니다.

 

아무리 대문자로 시작한 생성자 함수라고 해도 선언 할 때 new 키워드와 같이 사용하지 않는다면, 

생성자함수가 아니라 일반 함수로 동작한다.

 

다음 코드를 보자

function Circle (radius){
    const radiusDouble = radius*2;
    this.radius = radius;
    this.doubleRadius = radiusDouble
    this.getDiameter = function(){
        return this.radius * 2;
    }
}

const circle1 = new Circle(5);
console.log(radius); // ReferenceError
const circle2 = Circle(10);
console.log(radius); // 10

방금 전의 코드와 동일하다.

중간에 console.log를 통해서 radius라는 식별자의 값을 출력하는 부분이 있다.

왜 다른 값이 나올까?

 

일단 radius는 전역에 선언된 변수가 아니다.

Circle 생성자 함수 내부에 있는 this.radius를 가리키는 것이다.

circle1 다음에 오는 출력문이 실행되면 ReferenceError를 일으킨다.

선언되지 않았기에 당연한 이야기일 수 있다.

 

그렇다면 circle2 이후에 오는 출력문에서는 왜 오류가 발생하지 않을까?

위에서 이야기 한 것 처럼 circle2는 생성자함수가 아닌 일반함수의 return 이 참조되었다.

일반함수 Circle에서는 this 키워드가 Circle 객체가 아닌 전역객체를 가리킨다.

따라서 전역객체에 radius 라는 프로퍼티가 생겨난다.

 

마지막 출력문에서 출력한 radius는 전역객체의 프로퍼티 radius라고 볼 수 있다.


함수는 객체이다.

따라서 일반 객체와 동일한 내부슬롯과 내부 매서드를 가지고 있다.

하지만 함수는 호출이 가능하다는 일반객체와의 차이를 가지고 있다.

따라서 함수는 다음과 같은 추가 내부 슬롯과 내부 매서드를 가진다.

  • [[Environment]]
  • [[FormalParameters]]
  • [[Call]]
  • [[Construct]]

함수가 호출 되는 과정에서 일반함수로 호출이 되면 [[Call]]이 호출되고 생성자 함수로 호출되면 [[Construct]]가 호출된다.

이를 기준으로 [[Call]] 내부 매서드를 가지는 함수 객체를 callable이라고 하며, [[Construct]] 내부 매서드를 갖는 함수 객체를 constructor, [[Construct]]를 갖지 못하는 함수 객체를 non-constructor라고 한다.

 

정리하면 모든 함수객체는 callable 이지만 생성자 함수를 호출하는 constructor를 가질 수도 있고 못가질 수 도 있다는 것이다.

그렇다면 non-constructor와 constructor는 어떻게 구분이 될까?

간단하다.

 

- constructor : 함수선언문, 함수 표현식, 클래스

- non-constructor : 매서드, 화살표함수

 

위 구분만 봐서는 너무 간단하게 되어있다.

하지만 여기서 이야기하는 매서드의 범위를 조심해야한다.

다음 예시를 보자.

const baz = {
    x : function(){}
}

const x = new baz.x();
console.log(x); // x{}

분명 x는 baz 객체 내부의 메서드인데 오류가 발생하지 않고 생성자 함수의 역할을 한다.

이유는 무엇일까?

 

ES6에서는 메서드를 축약표현을 통한 것만 인정을 한다.

다음을 확인해보자.

const baz = {
    x(){}
}

const x = new baz.x();
console.log(x); // TypeError : baz.x is not a constructor

위의 메서드를 축약표현으로 변경한 코드이다.

아까와 결과가 다르게 TypeError가 발생한다.

 

이 뿐만아니라 화살표 함수도 non-constructor이다.


new를 안썼을 때 일반함수로 동작하는 생성자 함수의 성질 때문에 오류가 발생할 가능성이 있다.

이를 해결하기 위해 예외처리를 하기도 한다.

 

- new.target

new.target은 new 연산자와 함께 생성자 함수로 호출되었는지 확인할 수 있다.

new 연산자와 함께 생성자 함수로서 호출되면 함수 내부의 new.target은 함수 자신을 가리키며 new 연산자 없이 호출되면 undefined를 호출한다.

이러한 성질을 통해 다음과 같은 예외처리 및 해결이 가능하다.

function Circle(radius){
    if(!new.target){
        return new Circle(radius);
    }else{
        //...
    }
}

 

Internet Explore에서는 ES6의 최신문법을 모두 지원하지 않기 때문에 new.target을 사용할 수 없다.

이 때는 instanceof를 사용할 수 있다.


빌트인 생성자 함수 (String, Number, Boolean 등..) 은 new 연산자와 함께 사용했을 때 함수 객체를 반환한다.

new 연산자 없이 사용을 해도 확인 후 함수 객체를 반환해주는  Object, Function 생성자 함수와 다르게 나머지 빌트인 생성자 함수들은 new를 사용하지 않을 시 각각의 데이터 타입으로 반환된다.