그동안 여기 저기서 생성자 함수에 대한 예고편을 봤었는데 ~
아직은 몰라서 어렵지만 조금 더 익숙한 단어로 만들기 가보자고 !
참고
객체를 만드는 방법에는 총 5가지가 있다. 오늘은 2,3번에 대해 알아볼 예정 !
let newObject = { ... }
const newObject = new Object();
new 연산자
+ Object 생성자 함수
호출 = 빈 객체 생성하여 반환생성자 함수
에 의해 생성된 객체를 인스턴스
라고 함생성자 함수
란, new 연산자와 함께 호출하여 객체를 생성하는 함수다.
Object 생성자 함수
외에도, String, Date, Promise 등의 빌트인 생성자 함수가 제공된다.
빈 객체를 생성해야할 때, 반드시 생성자 함수를 사용하지 않아도 된다.
사실, 객체를 만드는 방법은 객체 리터럴이 훨씬 간편하다. (모던딥다이브 피셜~)
따라서 Object 생성자 함수
로 객체를 생성하는 것은, 특별한 이유가 없다면 그닥 유용하진 않다.
그 특별한 이유를 찾으러 고고씽 !
// 빈 객체 생성
const person = new Object();
// 프로퍼티 추가
person.name = 'sozzang';
console.log(person); // {name: 'sozzang'}
/* 다른 생성자 함수 예시 */
const strObj = new String('sozzang');
console.log(typeof strObj); // object
console.log(strObj); // String {"sozzang"}
const date = new Date();
console.log(typeof date); // object
console.log(date); // Tue Jan 16 2024 18:39:17 GMT+0900 (한국 표준시)
객체 리터럴에 의한 객체 생성 장점
직관적이고 편리함객체 리터럴에 의한 객체 생성 단점
단 하나의 객체만 생성객체 리터럴은 편리하지만, 단 하나의 객체만 생성할 수 있다.
여러 개의 객체를 손쉽게 만들기 위해, 우리가 굳이 편한 객체 리터럴을 두고 !
생성자 함수로 객체를 만들려고 하는 것이다 !
동일한 프로퍼티를 갖는 객체를 여러 개 생성해야 할 경우,
객체리터럴
방식은 매번 같은 프로퍼티를 기술해야 한다는 비효율적인 면이 존재한다.
const circle1 = {
radius: 5,
getDiameter(){ // 1️⃣ 같은 프로퍼티 두번 작성 필요
return 2 * this.radius;
}
}
const circle2 = {
radius: 10,
getDiameter(){ // 2️⃣ 같은 프로퍼티 두번 작성 필요
return 2 * this.radius;
}
}
객체는 프로퍼티를 통해 고유한 상태를 표현한다. 메서드를 통해 프로퍼티를 참조하고 동작을 표현하기 때문에
위의 예시처럼, 프로퍼티
값은 다를 수 있지만 메서드
는 내용이 동일한 경우가 많다.
객체 리터럴
에 의해 객체를 생성한다면,
프로퍼티 구조가 동일해도 매번 같은 프로퍼티와 메서드를 기술해야 하는 번거로움이 생긴다.
생성자 함수에 의한 객체 생성 장점
구조가 동일한 객체(인스턴스) 여러 개를 간편하게 생성 가능생성자 함수
는 마치 객체(인스턴스)
를 생성하기 위한 템플릿처럼 사용할 수 있다.
// 생성자 함수 : 같은 프로퍼티를 중복 작성하지 않음
function Circle(radius){
this.radius = radius;
this.getDiameter = function(){
return 2 * this.radius;
}
}
// 인스턴스 생성
const circle1 = new Circle(5);
const circle2 = new Circle(10);
console.log(circle1.getDiameter()); // 10
console.log(circle2.getDiameter()); // 20
this
객체 자신의 프로퍼티나 메서드를 참조하기 위한 자기 참조 변수
함수 호출 방식 - this가 가리키는 값 22장에서 자세히 다룰 예정 !
1. '일반 함수'로서 호출 - this는 전역 객체
2. '메서드'로서 호출 - this는 메서드를 호출한 객체
3. '생성자 함수'로서 호출 - this는 생성자 함수가 (미래에) 생성할 인스턴스
생성자 함수는 이름 그대로 ! 객체(인스턴스)를 생성하는 함수다. (참쉽죠?)
클래스기반 객체지향 언어
(자바)의 생성자 함수는 형식이 정해져있으나,
자바스크립트의 생성자 함수
는 new 연산자와 함께 호출하기만 하면 생성자 함수로 동작한다.
만약, new 연산자와 함께 호출하지 않으면 일반 함수로 동작하는 것을 유의하자 !
const circle3 = Circle(15); // new 연산자 없음 = 일반 함수로서 호출됨
console.log(circle3); // undefined : 일반 함수 Circle은 return문이 없어서
console.log(radius); // 일반 함수로 호출한 this는 전역 객체를 가리켜서 30(15*2)이 아님
생성자 함수
는 다음의 역할을 수행한다.
필수
인스턴스 생성선택
인스턴스 초기화(인스턴스 프로퍼티 추가 및 초기값 할당)// 생성자 함수
function Circle(radius){
// 1️⃣ 암묵적으로 빈 객체가 생성되어 this에 바인딩됨
// 2️⃣ 인스턴스 초기화 (선택)
this.radius = radius;
this.getDiameter = function(){
return 2 * radius;
}
// 3️⃣ 완성된 인스턴스가 바인딩된 this에 암묵적으로 반환됨
}
// 인스턴스 생성 (필수) : Circle 생성자는 암묵적으로 this를 반환함
const circle = new Circle(5); // 반지름이 5인 Circle 객체 생성
console.log(circle); // Circle { radius: 5, getDiameter: f }
생성자 함수 내부의 코드를 살펴보면, this에 프로퍼티를 추가하고 필요에 따라 전달된 인수를 프로퍼티 초기값으로 할당하여 인스턴스를 초기화한다.
new 연산자와 생성자 함수를 호출하면, 자바스크립트 엔진은 암묵적으로 인스턴스를 생성하고 반환한다.
다음의 과정을 살펴봐보자.
new 연산자와 생성자 함수를 함께 호출하면, 아래의 단계가 실행된다.
이와 같은 과정에 의해, 생성자 함수 내부의 this
가 생성할 인스턴스를 가리킨다.
바인딩
식별자와 값을 연결하는 과정
- 변수 선언 : 변수 이름과 확보된 메모리 공간의 주소를 바인딩
- 생성자 함수의 this : this(식별자 역할)와 가리킬 객체가 바인딩
개발자는 this에 바인딩된 인스턴스
에 프로퍼티나 메서드를 추가한다.
생성자 함수
가 인수로 전달받은 초기값을 인스턴스 프로퍼티에 할당하여 초기화하거나 고정값을 할당하기도 한다.
이 과정은 개발자가 직접 코드를 작성하여 진행한다.
생성자 함수 내부에서 모든 처리가 끝나면 아래의 내용이 반환된다.
return 생략
암묵적으로 인스턴스가 바인딩된 this 반환return { }
명시적으로 this가 아닌 객체를 반환하면 명시한 객체 반환return 100
명시적으로 this가 아닌 원시값을 반환하면 무시되어 this 반환생성자 함수
내부에서 명시적으로 this가 아닌 다른 값을 반환한다면, 생성자 함수의 기본 동작이 훼손된다.
생성자 함수 내부에서 return문은 반드시 생략되어야 한다.
function Circle(radius){
...
1️⃣ // this 반환
2️⃣ return {}; // 명시한 객체 반환
3️⃣ return 100; // 명시한 원시값이 무시되어 this 반환
}
함수
(함수 선언문, 함수 표현식으로 정의한)는
1️⃣ 일반적인 함수로도 호출 가능하고 2️⃣ 생성자 함수로도 호출 가능하다.
함수도 객체이므로, 일반 객체와 동일하게 동작할 수 있다.
함수 객체
는 일반 객체가 가진 내부 슬롯
과 내부 메서드
를 모두 가지고 있기 때문이다.
// 함수는 객체 !
function foo () {};
// 객체인 함수는 '프로퍼티' 소유 가능
foo.prop = 10;
// 객체인 함수는 '메서드' 소유 가능
foo.method = function () {
console.log(this.prop);
}
foo.method(); // 10
일반 객체
호출 불가함수 객체
호출 가능함수 객체
는 일반 객체가 가지고 있는 내부 슬롯은 물론,
함수로서 동작하기 위해 함수 객체만을 위한 내부 슬롯도 가지고 있다.
[[Call]]
[[Call]]
을 갖는 함수 객체를 callable
이라고 함즉 [[Call]]을 갖는 함수 객체는 호출 가능하다는 의미를 가진다.
이때, 호출이 불가능한 객체는 함수 객체라고 할 수 없다.
따라서 함수로서 기능하는 함수 객체는 반드시 callable
이다.
일반 함수
로 호출된 함수 객체도, 생성자 함수
로 호출된 함수 객체도 호출이 가능하다.
[[Construct]]
[[Construct]]
을 갖는 함수 객체를 constructor
이라고 함[[Construct]]
을 갖지 않는 함수 객체를 non-constructor
이라고 함[[Construct]]을 갖는 함수 객체는 생성자 함수로서 호출이 가능한지의 여부를 의미한다.
따라서 함수 객체는 아래의 두 가지 경우로 나뉜다.
일반 함수나 생성자 함수로서
호출 가능한 객체 : callable
, constructor
일반 함수
로서만 호출 가능한 객체 : callable
, non-constructor
그렇다면 자바스크립트 엔진은 constructor
과 non-constructor
을 구분할까?
정답은 바로 함수가 어떻게 정의되었는지에 따라 구분된다.
constructor
함수 선언문, 함수 표현식, 클래스non-constructor
메서드(ES6 메서드 축약 표현), 화살표 함수즉 함수가 어디에 할당되었는지가 아닌, 함수 정의 방식에 의해 위의 두 가지가 구분된다.
함수를 일반 함수
로서 호출하면, 함수 객체의 내부 메서드 [[Call]]이 호출된다.
new 연산자와 생성자 함수
로 호출하면 내부 메서드 [[Construct]]가 호출된다.
따라서 non-construct인 함수 객체를 생성자 함수로 호출하면,
[[Construct]] 메서드를 갖지 않기 때문에 에러가 발생한다.
// 프로퍼티 x의 값으로 할당된 것은 일반 함수 (메서드 X)
const baz = {
x = function () {}
}
new bax.x(); // x {}
// 화살표 함수 정의
const arrow = () => {};
new arrow(); // TypeError: arrow is not a constructor
// 메서드 정의
const obj = {
x() {}
}
new obj.x(); // TypeError: obj.x is not a constructor
지금까지 살펴본 내용에 의하면, 사실상 일반 함수와 생성자 함수의 차이는 new 연산자
이다.
함수를 일반 함수 또는 생성자 함수로 정의했는지가 아닌,
new 연산자
와 함께 함수를 호출했는지의 여부에 따라 생성자 함수가 된다.
함수 객체의 내부 메서드 [[Call]]
이 아닌 [[Construct]]
가 호출된다.
new 연산자와 호출하기만 한다면, 일반 함수도 생성자 함수처럼 작동된다.
이때 일반 함수가 객체를 반환하지 않는다면, 작성된 반환값이 무시되고 빈 객체를 반환한다.
// 1️⃣ 일반 함수로 정의됨, 반환값은 객체가 아님
function add(x, y) { return x + y; }
// 일반 함수를 new 연산자와 함께 호출
let inst = new add();
// 일반 함수가 객체를 반환하지 않기 때문에 반환문 무시됨
// 빈 객체가 생성되어 반환됨
console.log(inst); // {}
// 2️⃣ 일반 함수로 정의됨, 반환값은 객체
function createUser (name, role) { return { name, role }; }
// 일반 함수를 new 연산자와 함께 호출
inst = new createUser('lee', 'sozzang');
// 일반 함수의 객체 반환
console.log(inst); // { name: 'lee', role: 'sozzang' }
함수 객체의 내부 메서드 [[Construct]]
가 아닌 [[Call]]
이 호출된다.
생성자 함수를 일반 함수로서 호출하면, 함수 내부의 this
는 전역 객체 window
를 가리킨다.
// 생성자 함수
function Circle(radius){
this.radius = radius;
this.getDiameter = function(){
return 2 * this.radius;
}
}
// new 연산자 없이 호출 = 일반 함수로서 호출됨
const circle = circle(5);
console.log(circle); // undefined
위의 예제에서 circle 함수 내부의 this는 window를 가리킨다.
따라서 radius 프로퍼티와 getDiameter 메서드는 전역 객체의 프로퍼티, 메서드가 된다.
이렇듯, 일반 함수와 생성자 함수에는 특별한 형식적 차이가 없다.
따라서 일반적으로 생성자 함수
는 첫 문자를 대문자로 기술하여 일반 함수와 구별하려 노력한다.
생성자 함수의 첫 문자를 대문자로 작성하여도 new 연산자
없이 호출하는 실수가 언제나 발생할 수 있다.
이러한 위험을 회피하기 위해 new.target
이 ES6에서 등장했다.
** IE는 지원하지 않음, IE를 고려해야 한다면 스코프 세이프 생성자 패턴 사용 필요)
new.target
메타 프로퍼티, this와 유사하게 함수 내부 지역 변수와 같이 사용됨생성자 함수
로서 호출되면 new.target
는 함수 자신을 가리킴일반 함수
로서 호출되면 new.target
는 undefined 반환위와 같은 특성으로 인해, new.target
을 활용하여 new 연산자와 함께 호출했는지를 확인할 수 있다.
function Circle(radius) {
// 만약 new 연산자와 호출되지 않음 = !undefined = true
// = 생성자 함수를 재귀 호출하여 생성된 인스턴스를 반환함
if(!new.target){ return new Circle(radius); }
...
}
// new 연산자 없이 생성자 함수를 호출하여도 new.target으로 생성자 함수로서 호출됨
const circle = Circle(5);
console.log(circle.getDiameter());
다른 빌트인 생성자 함수
도 new 연산자
와 함께 호출되었는지를 확인한 후 값을 반환한다.
Object
Function
: new 연산자 없이 호출해도 new 연산자와 함께 호출했을 때와 동일하게 동작String
Number
Boolean
어느덧 멋사 시작한지 한달이 지났고, 자바스크립트 수업을 시작했다 ..! 두둥
멋사 수업 기간 동안 모던 자스 1회독이 목표인데 ,,, 생각보다 모던 자스는 많이 두껍다. (ㅎ)
그래도 이제 자스 수업이니까! 모던 자스로 더 달려보자고 ~!
많이 낯설었지만 사실 많이 마주쳤던 .. ! 생성자 함수와 친해지기 성공 !