카테고리 없음

[Js 딥다이브] 17. 생성자 함수에 의한 객체 생성

hw.kr 2022. 11. 20. 13:48

Js 에서 객체를 생성하는 방법은 여러가지가 있다.

객체는 프로퍼티를 사용해서 객체의 상태를 표현하고, 객체의 메서드를 활용해서 프로퍼티를 참조하거나 프로퍼티 상태를 변경한다.

 

리터럴로 생성하는 방법은 간단하고 직관적이라는 장점이 있지만, 동일한 프로퍼티를 갖는 객체를 여러개 생성해야 하는 경우, 같은 코드를 반복적으로 써야 하므로 굉장히 비효율적이다.

 

이를 해결할 수 있는 생성자 함수로 객체를 생성하는 방법을 알아보자!

 

1. new 연산자 + Object 생성자 함수

 

const my_obj = new Object();

my_obj.Id = 'woong';

console.log(my_obj); // {Id : woong}

 

🧐 생성자 함수?

new 연산자와 함께 호출하여 객체를 생성하는 함수를 뜻한다 꼭 new 연산자를 사용해서 호출해야 함을 기억하자.

생성자 함수에 의한 객체 생성은, 객체지향 언어(C++, Java) 처럼 객체를 생성하기 위한 클래스를 만들어 두는 것과 비슷하다.

생성자 함수 호출을 통해서 프로퍼티 구조가 동일한 객체를 간편하게 여러개 생성할 수 있다.

 

📌 인스턴스 생성과 this 바인딩

 

function circle(radius){
    console.log(this); // {}
    
    this.number = 100;
   
    this.radius = radius;
    this.getDiameter = function() {
        return 2 * this.radius;
    }
}

const circle1 = new circle(5); 
const circle2 = new circle(10);
 
console.log(circle1.number, circle1.radius, circle1.getDiameter()) // 100 5 10
console.log(circle2.number, circle2.radius, circle2.getDiameter()) // 100 10 20

 

언어에 상관없이, 함수는 어떤 값을 반환하고 반환하지 않는 경우는 NULL 이나 undefined 을 반환한다.

하지만 생성자 함수의 구조를 잘 살펴보면 return 문이 없다.

 

🧐 그렇다면 ndefined를 암묵적으로 반환할텐데 왜 정상적으로 객체 생성이 되는걸까?

 

➡️ Js 에서 생성자 함수는 클래스 처럼 동작하지만 객체지향 언어와는 다르게 형식이 정해져 있는 것이 아니라 일반함수 호출를 정의하는 것과 동일하게 생성자 함수를 정의하고 호출할때 앞에 new 연산자를 붙이는 것으로 일반 함수 호출과 생성자 함수 호출을 구별한다.

앞에 new 연산자를 붙인다면, 자바스크립트 엔진이 암묵적으로 인스턴스를 생성하고 return 문이 없더라도 인스턴스를 반환해주는 것이다. 

new 연산자를 붙인다 -> 생성자 함수 호출이다 -> 인스턴스를 암묵적으로 생성하고 반환해준다

 

🧐 암묵적으로 반환 해주지만, 생성자 함수에 명시적으로 리턴문을 쓰면 어떻게 될까?

 

원시타입을 반환하는 리턴문은 무시되고,

객체를 반환하는 리턴문은 반영되기 때문에 리턴문 이전에 위에서 생성하려고 했던 프로퍼티와 메서드는 의도대로 생성되지 않는다.

생성자 함수에선 리턴문을 사용하지 않는 것이 좋겠다..!!

 

const circle3 = circle(3);
console.log(circle3); // undefined

 

🧐 this?

 

this 는 자기 참조 변수, 말 그대로 자신을 참조하는 변수라고 생각하면 편하다.

변수 : 최현웅

자기 참조 변수 : 나(me)

 

생성자 함수에서의 this 는 생성자 함수에 의해 생성될 인스턴스를 가르킨다

위의 코드에서 생성자 함수 circle 을 통해서 인스턴스 circle1 을 생성했다.

생성자 함수 내부의 this 를 인스턴스 circle1 에 대입해서 생각하면 이해하기 쉬울 것 같다.

 

🧐 this 바인딩?

 

바인딩이란, 식별자와 값을 연결하는 것을 말한다.

this 바인딩은 this 가 가리킬 객체를 this 에 알려준다(?) 로 생각하면 이해하기 쉬울 것 같다.

 

🤷‍♂️ (this) : 제가 가르킬게 뭐죠?

🙋‍♂️ (circle1) : 저를 가르키면 됩니다..!

 

 

2. [[call]] 과 [[constructor]]

 

Js 에서 함수는 객체이다

 

function foo() {};

foo.prob = 10; // 프로퍼티를 가질 수 있다 
foo.method = function(){ // 메서드를 가질 수 있다
    console.log(this.prob);
}

foo.method(); // 10
foo();

 

하지만, Js 에서의 함수는 일반 객체와는 다르게 호출할 수 있다.

그렇기 때문에 내부 메서드 [[call]] 과 [[constructor]] 을 가진다

모든 함수 객체는 [[call]] 을 가지고 있기 때문에 호출할 수 있지만 모든 함수 객체가 [[constructor]] 을 가지고 있는 것은 아니다

[[constructor]] 을 가지고 있지 않은 함수를 non-constructor 이라고 한다

 

📌 constructor 와 non-constructor 의 구분

 

함수 정의 방식에 따라서 constructor 와 non-constructor 을 구분할 수 있다.

 

1. non-constructor

 

const arrow_function = () => {}
new arrow_function(); //Uncaught TypeError: arrow_function is not a constructor

const test = {
    x(){
        console.log("hello");
    }
}

new test.x(); // Uncaught TypeError: test.x is not a constructor

 

화살표 함수와 ES6 에서 지원하는 메서드 축약 표현 함수는 [[constructor]] 내부 메서드를 가지고 있지 않기 때문에,

new 연산자와 함께 호출하면 에러가 발생한다.

나머지 함수 정의 방식은 [[constructor]] 내부 메서드를 가지고 있기 때문에 생성자 함수로 호출할 수 있다.

 

2. constructor

 

function circle(radius){
    console.log(this); // {}
    
    this.number = 100;
  
    this.radius = radius;
    this.getDiameter = function() {
        return 2 * this.radius;
    }
}

const circle1 = circle(5);
console.log(radius);
console.log(circle.radius);  // Uncaught TypeError: Cannot read properties of undefined (reading 'radius')

 

[[constructor]] 내부 메서드를 가지고 있고, 객체를 생성할 목적으로 만든 함수를 new 연산자를 사용하지 않고 호출 한다면

this 에 브라우저 전역객체 window가 바인딩 되고 의도대로 동작하지 않는다.

일반함수 호출에서 this 는 전역객체 window를 뜻한다.

 

[[constructor]] 을 호출하려고 했지만, [[call]] 을 호출했기 때문이다.

 

이 문제를 해결하기 위해서 생성자 함수를 파스칼 케이스(단어의 앞이 대문자)로 다른 함수와 구분한다.

이렇게 구분해도 실수로 new 연산자를 빼먹는 것을 해결하기 위해서 new.target 을 사용할 수 있다.

 

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