최종 수정 : 2024-04-05
필요한 지식
- 객체 / this
- 추후 링크 연결 예정
코드를 볼 때마다, new가 나오면 긴장한 상태로 바라봤다. 무지에서 오는 막연한 두려움이 싫어서 정리 시작!
1. 생성자 함수(constructor function)
자바스크립트 함수는 재사용한 코드의 묶음으로 사용하는 것 외에 객체를 생성하기 위한 방법으로도 사용된다. 직접 객체를 반환해도 되지만, new 키워드를 사용하여 함수를 호출하면 return 문 없이도 새로운 객체가 반환된다.
이처럼 객체를 생성하는 역할을 하는 함수를 '생성자 함수'라고 한다. 관례적으로 일반 함수와 구분하기 위해 대문자로 시작하는 함수명을 작성한다. 또한 생성자 함수는 새로운 타입을 정의하는 데 사용된다. new 키워드로 만들어진 객체는 해당 타입의 인스턴스가 된다.
// Book의 생성자 함수를 정의
function Book(name, page, subject) {
this.name = name;
this.page = page;
this.subject = subject;
this.teach = function (student) { // 메소드 정의
console.log(student + '에게 ' + this.subject + '를 가르칩니다.');
};
}
const js = new Book('js', 967, 'JavaScript');
console.log(js); // js에 대한 객체를 반환
js.teach('A'); // "A에게 JavaScript를 가르칩니다."
console.log(js.constructor);
// f Book() ==> 모든 객체는 constructor 속성을 가진다. 이 속성은 객체를 만든 생성자 함수를 가리킨다.
console.log(js instanceof Book);
// true ==> js 객체가 Book 생성자 함수의 인스턴스 여부를 확인한다.
const ts = Book('ts', 785, 'TypeScript'); // new 키워드를 빼고 Book 생성자 함수를 호출
console.log(ts); // undefined ==> 일반 함수로 호출된 Book은 반환문이 없으므로
console.log(page); // 785
+) 자바스크립트는 Object, String, Number, Boolean, Function, Array, Date, RegExp, Promise 등의 빌트인 생성자 함수를 제공한다.
2. 객체 생성 방식
객체 리터럴에 의한 생성
객체를 생성하는 방법은 객체 리터럴을 사용하는 것이 더 간편하다. 또한 직관적이다. 하지만 단 하나의 객체만 생성하므로, 동일한 프로퍼티를 갖는 객체를 여러 개 생성해야 하는 경우 매번 갖은 프로퍼티를 작성해야 하므로 비효율적이다.
const js = {
page: 50,
string() {
return 10 * this.page;
};
};
console.log(js.string()); // 500
const ts = {
page: 30,
string() {
return 10 * this.page;
};
};
console.log(ts.string()); // 300
객체는 프로퍼티를 통해 객체 고유의 상태(state)를 표현한다. 그리고 메서드를 통해 상태 데이터인 프로퍼티를 참조하고 조작하는 동작(nehavior)을 표현한다. 위처럼, 프로퍼티 구조가 동일함에도 불구하고 매번 같은 프로퍼티와 메서드를 기술해야 하는 비효율이 발생한다.
생성자 함수에 의한 생성
생성자 함수를 사용하면 이런 비효율적인 문제를 해결할 수 있다. 프로퍼티 구조가 동일한 객체 여러 개를 간편하게 생성할 수 있다. 위의 코드를 바꾸면 다음과 같다.
// 생성자 함수
const Book(page) {
// 생성자 함수 내부의 this는 생성자 함수가 생성할 인스턴스를 가리킨다.
this.page = page;
this.string = () {
return 10 * this.page;
};
}
// 인스턴스 생성
const js = new Book(50);
const ts = new Book(30);
console.log(js.string()); // 500
console.log(ts.string()); // 300
생성자 함수의 인스턴스 생성 과정
생성자 함수의 역할은 프로퍼티 구조가 동일한 인스턴스를 생성하기 위한 템플릿(클래스)으로서 동작하여 인스턴스를 생성하는 것(필수)과 생성된 인스턴스를 초기화(옵션)하는 것이다.
자바스크립트 엔진은 암묵적인 처리를 통해 인스턴스를 생성하고 초기화한 후 암묵적으로 인스턴스를 반환한다.
1. 인스턴스 생성과 this 바인딩
암묵적으로 빈 객체(인스턴스)가 생성된다. 그리고 인스턴스는 this에 바인딩(name binding) 된다.
2. 인스턴스 초기화
생성자 함수에 기술되어 있는 코드가 한 줄씩 실행되어 this에 바인딩되어 있는 인스턴스를 초기화한다.
3. 인스턴스 반환
1) 생성자 함수 내부의 모든 처리가 끝나면 완성된 인스턴스가 바인딩된 this가 암묵적으로 반환된다.
2) 만약 this가 아닌 다른 객체를 명시적으로 반환하면 return 문에 명시한 객체가 반환된다.
3) 그러나 return 문에 원시값을 반환하도록 작성하면 무시되고 암묵적으로 this가 반환된다.
이처럼 생성자 함수 내부에서 명시적으로 this가 아닌 다른 값을 반환하는 것은 생성자 함수의 기본 동작을 훼손한다. 따라서 생성자 함수 내부에서 return 문은 반드시 생략해야 한다.
// 생성자 함수
const Book(page) {
// 1. 암묵적으로 빈 객체가 생성되고 this에 바인딩된다.
// 2. this에 바인딩되어 있는 인스턴스를 초기화한다.
this.page = page;
this.string = () {
return 10 * this.page;
};
// 3. 암묵적으로 this를 반환한다.
}
// 인스턴스 생성
const js = new Book(50);
const ts = new Book(30);
console.log(js); // Book {page: 50, string: f}
console.log(ts); // Bool {page: 30, string: f}
3. 내부 메서드 [[Call]] / [[Construct]]
함수 선언문 또는 함수 표현식으로 정의한 함수는 생성자 함수로도 호출할 수 있다. 즉, new 연산자와 함께 호출하여 객체를 생성하는 것을 의미한다.
함수는 객체이므로 일반 객체와 동일하게 동작할 수 있다. 함수 객체는 일반 객체가 가지고 있는 내부 슬롯과 내부 메서드를 모두 가지고 있기 때문이다. 그러나 일반 객체와는 다른 점이 존재한다. 일반 객체는 호출할 수 없지만, 함수는 호출할 수 있다. 따라서, 함수로서 동작하기 위해 함수 객체만의 [[Environment]], [[FormalParameters]] 등의 내부 슬롯과 [[Call]], [[Construct]] 같은 내부 메서드를 추가로 가지고 있다.
function foo() {}
// 일반적인 함수 호출 : [[Call]] 메서드가 호출된다.
foo();
// 생서자 함수로서 호출 : [[Construct]] 메서드가 호출된다.
new foo();
모든 함수 객체는 callable이지만 모든 함수 객체가 constuctor인 것은 아니다.
callable | 함수, 즉 호출할 수 있는 객체 | 내부 메서드 [[Call]]을 갖는 함수 객체 |
constructor | 생성자 함수로서 호출할 수 있는 함수 | 내부 메서드 [[Construct]]를 갖는 함수 객체 |
non-constructor | 객체를 생성자 함수로서 호출할 수 없는 함수 즉, 일반 함수로서만 호출할 수 있는 객체 |
[[Construct]]를 갖지 않는 함수 객체 |
- constructor 함수 객체 : 함수 선언문, 함수 표현식, 클래스(클래스도 함수)
- non-constructor 함수 객체 : 메서드(축약 표현만), 화살표 함수
함수를 프로퍼티 값으로 사용하면 일반적으로 메서드라고 부른다. 하지만, ECMAScript 사양에서 메서드란 ES6의 메서드 축약 표현만을 의미한다.
const sample = {
name: "World",
sayHi() { // sayHi: function() 의 축약형
console.log('Hello ' + this.name);
}
};
sample.sayHi(); // Hello World
new 연산자
일반 함수와 생성자 함수에 특별한 형식적 차이는 없다. 다만, 생성자 함수는 파스칼 케이스로 명명하여 구분한다.
생성자 함수 - new 연산자 = 일반 함수
일반함수 + new 연산자 = 생성자 함수
new.target
둘의 형식적 차이가 거의 없다보니, 실수는 언제든 발생할 수 있다. 이러한 위험성을 방지하기 위해 ES6에서는 new.target을 지원한다. this와 유사하게 consturcot인 모든 함수 내부에서 암묵적인 지역 변수와 같이 사용되며, 메타 프로퍼티라고 부른다. 이를 사용하면, new 연산자와 함께 생성자 함수로서 호출되었는지 확인할 수 있다.
// 생성자 함수
function Book(page) {
// 이 함수가 new 연산자와 함께 호출되지 않았다면 new.target은 undefined
if (!new.target) {
// 재귀함수로 호출하여 생성된 인스턴스를 반환
return new Book(page);
}
this.page = page;
this.string = function () {
return 30 * this.page;
};
}
// new 연산자 없이 생성자 함수를 호출하여도 new.target을 통해 생성자 함수로서 호출된다.
const js = Book(50);
console.log(js.string()); // 1500 -- why? 30 * 50
+) 스코프 세이프 생성자 패턴(scope-safe constructor)
new.target을 사용할 수 없는 상황이라면 스코프 세이프 생성자 패턴을 사용할 수 있다.
위의 코드에서 if (!new.target)을 if (!(this instanceof Book)) 으로 바꾸면 된다.
4. 빌트인 생성자 함수
Object, String, Number, Boolean, Function, Array, Date, RegExp, Promise 등
Object와 Function 생성자 함수
new 연산자 없이 호출해도 new 연산자와 함께 호출했을 때와 동일하게 동작한다.
String, Number, Boolean 생성자 함수
new 연산자와 함께 호출했을 때는 String, Number, Boolean 객체 생성하여 반환한다.
new 연산자 없이 호출하면, 문자열, 숫자, 불리언 값을 반환한다. 이를 통해, 묵시적 형변환을 하기도 한다.
참고 자료
모던 자바스크립트 Deep Dive (p.234-248)
초보자를 위한 JavaScript 200제 (p.143-145)
'프론트엔드 > JS 공부' 카테고리의 다른 글
HTTP 메서드(method)에 대한 정리 (0) | 2024.04.06 |
---|---|
이벤트 위임(event delegation)에 대한 정리 (0) | 2024.04.06 |
이벤트 전파(Event propagation)에 대한 정리 (0) | 2024.04.05 |
Map() 객체에 대한 정리 (0) | 2024.04.04 |
02. The JavaScript Universe (0) | 2024.04.02 |
댓글