모던 자바스크립트 Deep Dive 17장 내용을 발췌, 요약한 글 입니다.
17.1 Object 생성자 함수
- 생성자 함수 :
new
연산자와 함께 호출하여 객체(인스턴스)를 생성하는 함수. - 인스턴스 : 생성자 함수에 의해 생성된 객체.
- 자바스크립트는
Object
생성자 함수 이외에도String, Number, Boolean, Function, Array, Date, RegExp, Promise
등의 빌트인 생성자 함수 제공. - 특별한 이유가 없는 이상 객체 리터럴을 사용하는게 더 간편하다.
// 빈 객체 생성
const person = new Object();
// 프로퍼티, 메서드 추가
person.name = 'Park';
person.sayHello = function() {
return `Hi, ${this.name}`;
};
// 다른 생성자 함수 예시
const func = new Function('x', 'return x * x'); // Function 객체(함수) 생성
const date = new Date(); //현재 시간
17.2 생성자 함수
[객체 리터럴 방식 vs 생성자 함수 방식]
- 객체 리터럴로 생성하는 방식은 동일한 프로퍼티를 갖는 객체를 여러개 생성할 경우, 같은 코드를 중복해서 작성해야 하기 때문에 비효율적이다.
- 객체는 프로퍼티를 통해 객체 고유의 상태 state 를 표현한다. 그리고 메서드를 통해 상태 데이터인 프로퍼티를 참조하고 조작하는 동작behavior 을 표현한다.
- 따라서, 객체마다 프로퍼티 값은 다를 수 있지만 메서드는 내용이 동일한 경우가 일반적이다. 만약 이런 객체를 수십개 작성해야 한다면, 객체 리터럴 방식으로는 문제가 있다.
- 생성자 함수에 의한 방식은, 생성자 함수로 객체 생성을 위한 템플릿(클래스)을 만들어 객체(인스턴스)를 생성하는 방식이다. 이를 이용해 프로퍼티 구조가 동일한 여러 객체를 간편하게 생성할 수 있다.
// 생성자 함수
function Circle(radius) {
// 생성자 함수 내부의 this는 생성자 함수가 생성할 인스턴스를 가리킨다.
this.radius = radius;
this.getDiameter = function() {
return 2 * this.radius;
};
}
const circle1 = new Circle(5); // 반지름 5
const circle2 = new Circle(10);
circle1.getDiameter(); // 10
- 자바스크립트의 생성자 함수는 다른 클래스 기반 객체지향 언어와 다르게, 일반 함수와 동일한 방법으로 정의한다. 단, 호출시 반드시 new연산자를 붙여야 한다. 이를 생략할 시 일반 함수로 동작한다.
- (일반 함수로 호출된 인스턴스는 반환문이 없기 때문에, 암묵적으로 undefined를 리턴한다.)
- (일반 함수로 호출된 인스턴스에서 this는 undefined 혹은 전역 객체를 가리킨다.)
[생성자 함수의 인스턴스 생성 과정]
function Circle(radius) {
// 1. 암묵적으로 인스턴스(빈 객체)가 생성되고 this에 바인딩된다.
// 이 객체는 생성자 함수가 생성한 인스턴스이다.
// 이로 인해 생성자 함수 내부 this가 인스턴스를 가리키는 것이다.
console.log(this); //Circle {}
// 2. this에 바인딩되어 있는 인스턴스를 초기화한다.
this.radius = radius;
this.getDiameter = function() {
return 2 * this.radius;
};
// 3. this(완성된 인스턴스가 바인딩되어 있음)가 암묵적으로 반환된다.
// 이 때 리턴문을 사용해 다른 객체를 명시적으로 반환하면, 그 객체가 리턴된다.
// 따라서 생성자 함수 내부에서 return 문은 반드시 생략해야 한다.
// 단 명시적으로 원시 값을 리턴하면 이는 무시된다.
return {};
}
[내부 메서드 [[Call]] 과 [[Construct]] ]
- 함수 또한 객체이기 때문에 일반 객체가 가지고 있는 내부 슬롯과 내부 메서드를 가지고 있다. 단, 일반 객체와의 차이점은 함수는 “호출할 수 있다” 는 점이다.
- 이 때문에 일반 객체의 내부 슬롯/내부 메서드 + 함수 객체만을 위한 내부 슬롯들과 [[Call]], [[Construct]]와 같은 내부 메서드를 갖는다.
- 함수가 일반 함수로서 호출되면 내부 메서드 [[Call]] 이 호출되고, new 연산자와 함께 생성자 함수로서 호출되면 [[Construct]]가 호출된다.
- 호출될 수 있는 함수 ([[Call]]을 가진 함수객체)를 callable, [[Construct]]를 갖는 함수 객체를 constructor 라고 한다. 또한 [[Construct]]를 갖지 않는 함수는 non - constructor 라고 한다.
- 호출할 수 없는 객체는 함수가 아니기 때문에, 함수 객체는 반드시 callable 이어야 한다. 그리고 [[Construct]]를 갖는가 여부에 따라 constructor, non - constructor 을 나눈다.
function foo() {};
// callable, non - constructor
foo();
// callable, constructor( [[Construct]]를 가질 경우.)
new foo();
[constructor 와 non - constructor 구분]
자바스크립트 엔진은 함수의 정의 방식에 따라 이를 구분한다.
- constructor : 함수 선언문, 함수 표현식, 클래스
- non - constructor : 메서드(ES6 메서드 축약표현), 화살표 함수
- 일반적으로 함수를 프로퍼티의 값으로 사용하면 메서드로 통칭한다. 그러나 ECMAScript 사양에서는, 메서드란 ES6 메서드 축약 표현만을 의미한다. 즉, 함수의 할당 위치가 아니라 함수의 정의 방식에 따라 구분된다.
- 함수를 new 연산자와 함께 생성자 함수로서 호출하면, 내부에 [[Construct]]가 호출된다. non-constructor 인 함수 객체는 내부 메서드 [[Construct]]가 없기 때문에 에러가 발생하는 것이다.
////// constructor //////
// 함수 선언문, 함수 표현식
function foo() {};
const bar = function () {};
// 일반 함수로 정의된 메서드. (메서드로 인정되지 않음)
const bax = {
x: function () {};
};
////// non - constructor //////
// 화살표 함수
const arrow = () => {};
// ES6 메서드 축약형으로 정의된 메서드. (메서드로 인정)
const obj = {
x() {}
};
[new 연산자]
- 생성자 함수를 new연산자 없이 호출하면, [[Construct]] 가 호출되는 것이 아니라 [[Call]]이 호출된다.
따라서 생성자 함수를 호출할때는 반드시 new 연산자를 붙여주어야 한다.
[new 연산자 없이 호출하는 것을 방지하는 방법 : 파스칼 케이스, new.target]
- 생성자 함수의 이름을 첫글자가 대문자인 파스칼 케이스로 명명하여 일반 함수와 구분한다.
- 이렇게 해도
new
를 빠트리고 호출하는 오류가 생길 수 있는데, 이 때 사용할 수 있는 방법은new.target
이다. - new.target은 this와 유사하게 constructor인 함수 내부에서 암묵적인 지역 변수와 같이 사용되며 메타 프로퍼티라고도 한다. ES6에서 지원되는 속성이며, IE는 지원하지 않는다.
- 함수 내부에서
new.target
을 사용하면 이 함수가new
연산자와 함께 호출되었는지 확인할 수 있다. new 연산자와 함께 호출되면 new.target은 함수 자신을 가리키고, new 연산자 없이 일반 함수로서 호출되었다면 new.target은undefined
이다.
function Circle(radius) {
// 함수가 `new` 없이 호출되었다면 `new.target`은 `undefined` 이다.
if (!new.target) {
// 이 조건에 해당한다면 다시 new를 붙여 재귀적으로 호출한다.
return new Circle(radius);
}
this.radius = radius;
this.getDiameter = function() {
return 2 * this.radius;
};
}
const circle = Circle(5); // new 연산자 없이도 constructor로서 호출할 수 있음.
- 대부분의 빌트인 생성자 함수는
new
연산자로 호출되었는지를 확인한 후 적절한 값을 리턴한다. 따라서new
연산자 없이 호출해도,new
연산자로 호출했을 때와 동일하게 동작한다. - 그러나
String, Number, Boolean
생성자 함수는new
연산자 있을때는 객체를 생성하여 반환하지만,new
없이 호출하면 문자열, 숫자, 불리언 값을 리턴한다. 이런 원리로 데이터 타입을 변환할 수 있는 것!
const str = String(123);
console.log(str, typeof str); // 123 string
SUMMARY
- 생성자 함수로 객체를 생성하는 방식은, 주로 프로퍼티의 구조는 같고 내용만 다른 객체 인스턴스를 생성할 때 사용한다.
- Object, Function 이외에 다양한 빌트인 함수를 제공하며, 이들은
new
연산자를 붙여 호출한다. * JS에서 생성자 함수는 다른 OOP 언어와 다르게 일반 함수와 동일한 방법으로 정의한다. - 인스턴스의 틀이 되어주는 생성자 함수를 작성한 후,
new
를 붙여 호출하여 인스턴스를 생성한다. - 1. 런타임 이전에 빈 객체가 암묵적으로 생성되고, 이 객체가 생성자 함수가 생성한 인스턴스가 된다. 그리고 이 객체가
this
에 바인딩 된다. 이로 인하여 생성자 함수 내부의 this가 인스턴스를 가리키는 것이다. - 2.
this
에 바인딩되어있는 객체를 개발자가 작성한 코드로 초기화한다. - 3.
this
(완성된 인스턴스가 바인딩 되어 있는)를 암묵적으로 리턴한다. 이 때 명시적으로 다른 객체를 리턴하면 그 객체가 리턴되고, 리턴값이 명시적으로 원시값일 때는 무시된다. 따라서 생성자 함수에서는return
문을 쓰지 않는다. - 함수는 일반 객체의 내부 메서드 / 내부 슬롯 + 함수객체의 고유한 내부 메서드 / 내부 슬롯을 갖는다. 함수가 일반 함수로서 호출되면 [[Call]], 생성자 함수로서 호출되면 [[Construct]] 내부 메서드가 호출된다.
- callable 이면서 [[Construct]] 가 있으면 constructor , [[Construct]] 가 없으면 non - constructor 이다.
- JS 는 이 둘의 차이를 정의 방식으로 구별한다. 일반 함수 정의 방식 및 일반적인 메서드 정의 방식은 constructor, 화살표 함수와 ES6 축약형 표현으로 정의된 메서드는 non - constructor.
- 생성자 함수를
new
없이 실행할 경우, 일반적인 함수처럼 실행되며 이를 방지하려면 1. 생성자 함수는 파스칼 케이스 컨벤션을 사용하여 명명한다. 2. ES6에서 지원하는new.target
을 사용한다. new.target
은 생성자 함수에서 new와 함께 호출된 함수이면 자기 자신을, 아닐 경우undefined
를 가진다. 이를 활용해 new 사용 여부를 확인하고,new
없이 호출되도록 재귀적으로 생성자 함수를 호출할 수 있다.
'JavaScript > 모던 자바스크립트 Deep Dive' 카테고리의 다른 글
[모던 자바스크립트 딥다이브] 함수 선언문, 함수 표현식 (0) | 2022.05.23 |
---|---|
[모던 자바스크립트 딥다이브] switch문, while문 (0) | 2022.02.22 |
[모던 자바스크립트 딥다이브] 4.변수 (0) | 2022.02.02 |