new
연산자와 함께 호출하여 객체를 생성하는 함수를 생성자함수라고 한다.
생성자 함수에 의해 생성된 객체를 인스턴스라고 한다.
언제 사용되나요 ?
👉🏻 동일한 프로퍼티를 갖는 객체를 여러개 생성해야 하는 경우 사용된다.
ex) 복수의 사용자, 수 십개의 메뉴 내에 이름, 가격, 설명 등을 넣을 경우 등..
왜 사용하나요 ?
👉🏻 new
연산자를 이용해 생성자함수를 사용하면 유사한 객체 여러개를 쉽게 만들 수 있기 때문에 효율적이다.
생성자함수만의 특징이 있나요 ?
👉🏻 생성자 함수는 일반 함수와 기술적인 차이가 없다.
그렇기 때문에,
1. 함수의 이름의 첫 글자는 파스칼 케이스인 대문자로 시작한다.
2. 반드시 new
연산자를 붙힌다.
❗️잠깐의 TMI❗️
빌트인( built-in ) 생성자 함수 :Object
생성자 함수 이외에도String
Number
Boolean
Function
Array
Date
RegExp
Promise
등.. 를 제공한다.
👉🏻new
연산자와 함께 호출되었는지를 확인 한 후 적절 한 값을 반환한다.
Object
와Function
생성자 함수는new
연산자 없이 호출해도new
연산자와 함께 호출 했을 때와 동일하다.
💡String
Number
Boolean
은new
연산자를 붙히지 않는다면문자열
숫자
불링언
값으로 반환한다.
💡 비유가 필요한 부분이다.
블로그를 꾸밀 때, 사용되는 공동 템플릿이 필요하다.
기본값으로,
이미지, 제목, 설명글, 소셜정보, 이메일 등을 적을 수 있게 틀을 만들어 놓았다.
이것이 생성자 함수의 함수 몸체이다.
그리고 여러 사용자 중 하나인 내가 이미지, 제목, 설명글, 소셜정보, 이메일정보 등 나의 정보를 넣어 하나의 나만의 블로그를 설정한 것이 객체 ( 인스턴스 ) 라고 보면 될 것이다.
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
바인딩 👉🏻 인스턴스 초기화 👉🏻 인스턴스 반환
this
바인딩// 생성자 함수의 함수 몸체
function Circle(radius) {
console.log(this); // 1. Circle{}
// 인스턴스 초기화
this.radius = radius;
this.getDiameter = function() {
return 2 * this.radius;
}
}
Circle = {}; // 2. 암묵적으로 생성된 빈 객체 즉, 인스턴스
생성자 함수를 만들면 암묵적으로 빈 객체가 생성이 되는데 그것이 생성자 함수를 이용해서 생성 된 객체, 즉 인스턴스다.
this
는 생성된 인스턴스를 바라본다. ( 바인딩 된다. )this
가 생성된 인스턴스를 바라보는 이유는 함수 몸체에서 먼저 코드를 읽기전에 암묵적으로 생기는 빈 객체 즉, 인스턴스를 먼저 가르켜서 그런거 같다.인수로 전달받은 초기값을 프로퍼티에 할당하여 초기화 or 고정값을 할당한다.
이 부분은 개발자의 몫이다.
// 생성자 함수의 함수 몸체
function Circle(radius) { // 인수
// 1. 암묵적으로 인스턴스가 생성되고 `this`에 바인딩.
console.log(this); // 1. Circle{}
// 2. `this`에 바인딩되어 있는 인스턴스를 초기화
this.radius = radius; // 인수로 전달받은 초기값을 프로퍼티에 할당.
this.getDiameter = function() {
return 2 * this.radius;
}
}
// 생성자 함수의 함수 몸체
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
결론부터 말하자면,
👉🏻 생성자 함수 내부에서 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
를 어떻게 구분하는가 ?
🌟🌟🌟
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
라는걸 확인할 수 있다.
개발자들의 암묵적인 룰인 파스칼 케이스를 사용하지만, 누구나 실수는 있기 때문에 ES6에서
new.target
나왔다고 한다. ( IE는 지원되지 않는다. )
특징
1. 메타 프로퍼티
라고 부른다고 한다.
2. this
와 유사하게 지역변수를 바라 보며 함수를 호출하게 되면 함수 자신을 카리킨다.
3. new
연산자 없이 일반 함수로서 함수를 호출하게 되면 new.target
은 undefind
이다.
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
function UserInfo(title, description, email) {
this.title = title;
this.description = description;
this.email = email;
this.getInfo = function () {
return `나의 블로그 제목 : ${this.title}, 설명 : ${this.description}, 이메일 : ${this.email} 입니다.`
}
}
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 값은 ?");
이런 식으로 타입을 변환 시켜줘야 하는데,,
해답을 보았을 때+
를 이용했더니 값을 숫자형으로 계산되어 반환되었다.
이 점은 구글링을 해봐도 공식문서, 블로그를 봐도 잘 나오지 않아서 .. 아직 까지 이 궁금증은 가지고 있다.
궁금증을 찾으면 수정을 해서 이유를 올려보겠다.
결론은
자바스크립트는 동적언어이고, 타입이 지정되어 있지 않아 생긴 문제이다.
'+'을 붙여줬을 때, 자동으로 숫자라고 인식되어 원하는 값을 얻은 것이다.
이 문제는 타입스크립트를 사용하게 되면 해결 할 수 있다.
타입스크립트를 사용하는 이유.
( 사용자가 입력한 값과 내가 입력한 출력값의 합의 값 )
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로 찍어준다.
*/
객체리터럴 방식으로 객체 생성을 할 경우에는
👉🏻 객체 리터럴로 객체를 생성하면 모든 객체를 모여주는 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);
객체 리터럴
장점
👉🏻 직관적이고 간편하게 사용된다.
단점
👉🏻 단 하나의 객체만 생성되기 때문에 동일한 프로퍼티를 갖는 객체를 여러 개 생성해야 하는 경우에는 비효율 적이다.
생성자 함수
장점
👉🏻 동일한 프로퍼티를 갖는 객체를 여러개 생성해야 하는 경우 템플릿 역할을 하기 때문에 효율적이다.
단점
👉🏻 일반 함수와 차이가 크게 없다.
(new
연산자를 붙히지 않고 호출을 하게 되면 일반함수를 호출하는 것이 된다. )
new
연산자를 붙히지 않았을 경우에는 결국 일반 함수로 호출하는 것이 된다고 단점에서 이야기 했는데, 그럼 this
는 무엇을 가르키는지 정리해보자.
일반함수로서 호출 👉🏻 전역객체
메소드로서 호출 👉🏻 메소드를 호출한 객체 ( 마침표 앞의 객체 )
생성자 함수로서 호출 👉🏻 생성자 함수가 생성할 인스턴스 ( 객체 )