[JS] 생성자함수로 객체생성 과정 ( or 객체리터럴과 차이)

AREUM·2022년 12월 26일
2

Javascript이론

목록 보기
6/10
post-thumbnail

생성자 함수란 ?

new 연산자와 함께 호출하여 객체를 생성하는 함수를 생성자함수라고 한다.
생성자 함수에 의해 생성된 객체를 인스턴스라고 한다.

언제 사용되나요 ?
👉🏻 동일한 프로퍼티를 갖는 객체를 여러개 생성해야 하는 경우 사용된다.
ex) 복수의 사용자, 수 십개의 메뉴 내에 이름, 가격, 설명 등을 넣을 경우 등..

왜 사용하나요 ?
👉🏻 new 연산자를 이용해 생성자함수를 사용하면 유사한 객체 여러개를 쉽게 만들 수 있기 때문에 효율적이다.

생성자함수만의 특징이 있나요 ?
👉🏻 생성자 함수는 일반 함수와 기술적인 차이가 없다.

그렇기 때문에,
1. 함수의 이름의 첫 글자는 파스칼 케이스인 대문자로 시작한다.
2. 반드시 new 연산자를 붙힌다.

❗️잠깐의 TMI❗️
빌트인( built-in ) 생성자 함수 : Object 생성자 함수 이외에도 String Number Boolean Function Array Date RegExp Promise등.. 를 제공한다.

👉🏻 new연산자와 함께 호출되었는지를 확인 한 후 적절 한 값을 반환한다.
ObjectFunction생성자 함수는 new연산자 없이 호출해도 new연산자와 함께 호출 했을 때와 동일하다.
💡String Number Booleannew연산자를 붙히지 않는다면 문자열 숫자 불링언 값으로 반환한다.

생성자 함수와 객체 ( 인스턴스 )

💡 비유가 필요한 부분이다.
블로그를 꾸밀 때, 사용되는 공동 템플릿이 필요하다.

기본값으로,
이미지, 제목, 설명글, 소셜정보, 이메일 등을 적을 수 있게 틀을 만들어 놓았다.
이것이 생성자 함수의 함수 몸체이다.

그리고 여러 사용자 중 하나인 내가 이미지, 제목, 설명글, 소셜정보, 이메일정보 등 나의 정보를 넣어 하나의 나만의 블로그를 설정한 것이 객체 ( 인스턴스 ) 라고 보면 될 것이다.

생성자 함수를 사용하여 객체(인스턴스) 생성요약ver.

new 연산자와 함께 Object 생성자 함수를 호출하면 빈 객체를 생성하여 반환한다.
라는 이론적인 부분은 설명이 되었고 비유를 통해서 무엇을 의미하는지 알았다고 생각한다.

처음에 생성자함수의 함수몸체에서 해야 하는 일이 있다.
1. 당연하게도 인스턴스를 생성하는 것이다.
2. 생성된 인스턴스를 초기화 하는 것이다. ( 인스턴스 프로퍼티 추가 및 초기값 할당 )

❗️ 인스턴스를 생성하는 것은 필수, 초기화 하는것은 옵션 이다.

이론을 설명한 것을 간단예제로 보자.

// 생성자 함수의 함수 몸체
function Circle(radius) {
  // 인스턴스 초기화
  this.radius = radius;
  this.getDiameter = function() {
   	return 2 * this.radius; 
  }
}

// 인스턴스 생성
const circle1 = new Circle(5);
console.log(circle1);	// Circle {radius: 5, getDiameter: f}
console.log(circle1.getDiameter);	// 10

설명 : this에 프로퍼티를 추가하고 필요에 따라 인수를 프로퍼티의 초기값으로 할당하여 인스턴스 초기화를 한다.

자바스크립트의 엔진은 new연산자를 사용해 생성자 함수를 호출하면
1. 암묵적으로 인스턴스( 객체 )를 생성하고
2. 초기화한 후
3. 반환❗️한다.

이제 예제와 함께 내부적으로 객체(인스턴스) 생성이 어떻게 되는지 세세한 설명으로 과정을 정리해보자.

생성자 함수를 사용하여 객체(인스턴스)생성과정

인스턴스 생성과 this바인딩 👉🏻 인스턴스 초기화 👉🏻 인스턴스 반환

1. 인스턴스 생성과 this바인딩

// 생성자 함수의 함수 몸체
function Circle(radius) {
  
  console.log(this); // 1. Circle{}
  
  // 인스턴스 초기화
  this.radius = radius;
  this.getDiameter = function() {
   	return 2 * this.radius; 
  }
}

Circle = {};	// 2. 암묵적으로 생성된 빈 객체 즉, 인스턴스

생성자 함수를 만들면 암묵적으로 빈 객체가 생성이 되는데 그것이 생성자 함수를 이용해서 생성 된 객체, 즉 인스턴스다.

  1. this는 생성된 인스턴스를 바라본다. ( 바인딩 된다. )
  2. this가 생성된 인스턴스를 바라보는 이유는 함수 몸체에서 먼저 코드를 읽기전에 암묵적으로 생기는 빈 객체 즉, 인스턴스를 먼저 가르켜서 그런거 같다.

2. 인스턴스 초기화

인수로 전달받은 초기값을 프로퍼티에 할당하여 초기화 or 고정값을 할당한다.
이 부분은 개발자의 몫이다.

// 생성자 함수의 함수 몸체
function Circle(radius) {	// 인수
  // 1. 암묵적으로 인스턴스가 생성되고 `this`에 바인딩.
  console.log(this); // 1. Circle{}
  
  // 2. `this`에 바인딩되어 있는 인스턴스를 초기화
  this.radius = radius;	// 인수로 전달받은 초기값을 프로퍼티에 할당.
  this.getDiameter = function() {
   	return 2 * this.radius; 
  }
}

3. 인스턴스 반환

// 생성자 함수의 함수 몸체
function Circle(radius) {	// 인수
  // 1. 암묵적으로 인스턴스가 생성되고 `this`에 바인딩.
  console.log(this); // 1. Circle{}
  
  // 2. `this`에 바인딩되어 있는 인스턴스를 초기화
  this.radius = radius;	// 인수로 전달받은 초기값을 프로퍼티에 할당.
  this.getDiameter = function() {
   	return 2 * this.radius; 
  }
  // 3. this가 암묵적으로 반환
}

// 인스턴스 생성.
// Circle 생성자 함수는 암묵적으로 this를 반환.
const circle1 = new Circle(5);
console.log(circle1);	// Circle {radius: 5, getDiameter: f}
console.log(circle1.getDiameter());	// 10

3-1. 인스턴스 return

결론부터 말하자면,
👉🏻 생성자 함수 내부에서 return문 사용을 권장하지 않는다. ( 생략 해야한다. )

이유는
생성자 함수 내부의 기본동작이 훼손 될 수 있다.

아래의 코드처럼
객체를 반환할 경우 : this반환이 무시되고 반환한 객체를 반환한다.
원시값을 반환한 경우 : 원시값이 무시된다.

// 생성자 함수의 함수 몸체
function Circle(radius) {	// 인수
  // 1. 암묵적으로 인스턴스가 생성되고 `this`에 바인딩.
  console.log(this); // 1. Circle{}
  
  // 2. `this`에 바인딩되어 있는 인스턴스를 초기화
  this.radius = radius;	// 인수로 전달받은 초기값을 프로퍼티에 할당.
  this.getDiameter = function() {
   	return 2 * this.radius; 
  }
  // 3. this가 암묵적으로 반환
  return 100;	// 원시 값을 반환하면 원시값은 무시되고 this가 반환한다.
  return {};	// {}
}

// 인스턴스 생성.
// Circle 생성자 함수는 암묵적으로 this를 반환.
const circle1 = new Circle(5);

내부 메소드

일반함수로서 호출 : 함수 객체의 내부 메소드 [[Call]]이 호출
new연산자와 함께 생성자 함수로서 호출 : 내부 메소드 [[Construct]]이 호출

callable 👉🏻 내부 메소드 [[Call]] 을 갖는 함수 객체 : 호출할 수 있는 객체 ( 함수를 말한다. )
constructor 👉🏻 내부 메소드 [[Construct]] 를 갖는 함수 객체 : 생성자 함수로서 호출할 수 있는 함수
non-constructor 👉🏻 [[Construct]]를 갖지 않는 함수 객체 : 객체를 생성자 함수로서 호출할 수 없는 함수

이 부분이 굉장히 이해 될 듯 말 듯 했어서 몇번을 읽어 보았다.
이미지와 함께 보면,
결론은

결국 모든 함수 객체는 내부 메소드 [[Call]]callable이지만,
온전히 일반 함수로서만 호출할 수 있는 객체( 어떻게 정의 하였는지에 따른 )가 있기에
그것은 non-constructor인 것이고,
new연산자와 함께 생성자 함수로서 호출 할 수는 constructor는 함수 본체에서 구분되지 않나 싶다.
( 생성자 함수에서 받아주지 않는 함수 또는 정의 법이지 않을까 ? 생각이 든다. )

밑에서 구분되는 이야기가 나온다.

constructor와 non-constructor의 구분.

자바스크립트 엔진이 constructornon-constructor를 어떻게 구분하는가 ?

🌟🌟🌟
constructor : 함수 선언문, 함수 표현식, 클래스 ( 클래스도 함수이다. )
non-constructor : 화살표 함수, 메서드 ( ES6 메서드 축약 표현 )

예시를 보고 무엇을 출력 하는지를 보면 이해가 빠를 것이다.

// 일반 함수 정의 : 함수 선언문, 함쑤 표현식
function foo() {}
const boo = function() {};
const soo = {	// 프로퍼티 x의 값으로 할당된 것은 일반함수로 정의된 함수다. 이는 메서드로 인정되지 않는다.
	x: function() {} 
}

new foo();	// -> foo{}
new boo();	// -> boo{}
new soo.x();	// -> x {}

// 화살표 함수 정의
const arrow = () => {};

new arrow();	// TypeError: arrow is a not a constructor

// 메서드 정의 : ES6의 메서드 축약 표현만 메서드로 인정된다.
const flex = {
	x() {}
};
new flex.x();	// TypeError: flex.x is a not a constructor

위 코드를 보시면 화살표 함수와, ES6의 메서드 축약 표현이 non-constructor라는걸 확인할 수 있다.

개발자의 실수를 막을 수 있는 new.target

개발자들의 암묵적인 룰인 파스칼 케이스를 사용하지만, 누구나 실수는 있기 때문에 ES6에서 new.target나왔다고 한다. ( IE는 지원되지 않는다. )

특징
1. 메타 프로퍼티라고 부른다고 한다.
2. this와 유사하게 지역변수를 바라 보며 함수를 호출하게 되면 함수 자신을 카리킨다.
3. new연산자 없이 일반 함수로서 함수를 호출하게 되면 new.targetundefind이다.

new연산자와 생성자 함수로서 호출을 예방 하기 위해 사용한다.
예제를 보자.

function Circle(radius) {
  if(!new.target) {
    // new 연산자와 함께 생성자 함수를 재귀 호출하여 생성된 인스턴스를 반환한다.
  	retturn new Circle(radius); 
  }
  this.radius = radius;
  this.getDiameter = function() {
   	return 2 * this.radius; 
  }
}

// new 연산자 없이 생성자 함수를 호출하여도 new.target을 사용해 재귀호출로 인해 생성자 함수로서 호출이 된다.
const circle1 = Circle(5);
console.log(circle1.getDiameter());	// 10

혼자 만들어 본 생성자 함수

1. 하나의 문자열 만들기.

function UserInfo(title, description, email) {
  this.title = title;
  this.description = description;
  this.email = email;
  this.getInfo = function () {
    return `나의 블로그 제목 : ${this.title}, 설명 : ${this.description}, 이메일 : ${this.email} 입니다.`
  }
}

2. 계산 합 만들기.

function Calculator() {
  this.read = function () {
    this.a = +prompt("a의 값은 ?");
    this.b = +prompt("b의 값은 ?");
  };

  this.sum = function () {
    return this.a + this.b;
  };

  this.mul = function () {
    return this.a * this.b;
  };
  this.square = function () {
    return this.a ** this.b;
  };
}
const calculator = new Calculator();
calculator.read();

console.log("sum ?", calculator.sum());
console.log("mul ?", calculator.mul());
console.log("square ?", calculator.square());

a b값 모두 '4'를 넣었다.

잘 맞게 나왔는데

궁금증이 하나 생겼다.
prompt는 문자열을 반환한다.
그래서 Numer(prompt("a 값은 ?"); parseInt(prompt("a 값은 ?"); 이런 식으로 타입을 변환 시켜줘야 하는데,,
해답을 보았을 때 +를 이용했더니 값을 숫자형으로 계산되어 반환되었다.
이 점은 구글링을 해봐도 공식문서, 블로그를 봐도 잘 나오지 않아서 .. 아직 까지 이 궁금증은 가지고 있다.
궁금증을 찾으면 수정을 해서 이유를 올려보겠다.

궁금증 해결 ❗️❗️❗️

결론은
자바스크립트는 동적언어이고, 타입이 지정되어 있지 않아 생긴 문제이다.
'+'을 붙여줬을 때, 자동으로 숫자라고 인식되어 원하는 값을 얻은 것이다.

이 문제는 타입스크립트를 사용하게 되면 해결 할 수 있다.
타입스크립트를 사용하는 이유.

3. 누산기 만들기

( 사용자가 입력한 값과 내가 입력한 출력값의 합의 값 )

function Accumulator(startingValue) {
  this.value = startingValue;
  this.read = function () {
    this.value += +prompt("고정값 적으신 숫자와 합해집니다.");
  }
}
const accumulator = new Accumulator(4);
accumulator.read();
console.log(accumulator.value);	// 사용자가 입력한 value값과 고정출력값인 4의 합

/*
* accumulator.read(); 하나 더 적어주면
* 한번 더 호출을 해주는것이기 때문에
* 두개의 value값과 고정출력값인 4의 값을 console.log로 찍어준다.
*/

생성자함수 vs 객체리터럴

1. 차이점

체리터럴 방식으로 객체 생성을 할 경우에는
👉🏻 객체 리터럴로 객체를 생성하면 모든 객체를 모여주는 Object객체와 연결이 되어 있다.

const animal = {
	name: 'nabi',
  	age: 2,
  	gender: 'man',
}

console.log(animal);	// {name: 'nabi', age: 2, gender: 'man'}
console.log(annimal.age);	// 2

생성자 함수로 객체 생성을 할 경우에는
생성자 함수를 사용해 객체를 생성하면 자신을 만든 생성자 객체만 연결된다.

const Animal = function (name, age, gender) {
	this.name = name;
  	this.age = age;
  	this.gender = gender;
}
const animal22 = new Animal('nabi', 2, 'man');
console.log(animal22);

2. 장단점

객체 리터럴
장점
👉🏻 직관적이고 간편하게 사용된다.
단점
👉🏻 단 하나의 객체만 생성되기 때문에 동일한 프로퍼티를 갖는 객체를 여러 개 생성해야 하는 경우에는 비효율 적이다.

생성자 함수
장점
👉🏻 동일한 프로퍼티를 갖는 객체를 여러개 생성해야 하는 경우 템플릿 역할을 하기 때문에 효율적이다.
단점
👉🏻 일반 함수와 차이가 크게 없다.
(new 연산자를 붙히지 않고 호출을 하게 되면 일반함수를 호출하는 것이 된다. )

번외 : new연산자를 붙히지 않았을 경우 this

new연산자를 붙히지 않았을 경우에는 결국 일반 함수로 호출하는 것이 된다고 단점에서 이야기 했는데, 그럼 this는 무엇을 가르키는지 정리해보자.

일반함수로서 호출 👉🏻 전역객체
메소드로서 호출 👉🏻 메소드를 호출한 객체 ( 마침표 앞의 객체 )
생성자 함수로서 호출 👉🏻 생성자 함수가 생성할 인스턴스 ( 객체 )

profile
어깨빵으로 부딪혀보는 개발끄적이는 양씨 인간

0개의 댓글