[모던 자바스크립트 Deep Dive]17장 생성자 함수에 의한 객체 생성
17.1 Object 생성자 함수
- 생성자 함수(constructor):
new
연산자와 함께 호출해 객체를 생성하는 함수Object
,String
,Number
,Boolean
,Function
,Array
,Date
,RegExp
,Promise
등
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
// String 생성자 함수에 의한 String 객체 생성 const strObj = new String('Lee'); console.log(typeof strObj); // object console.log(strObj); // String {"Lee"} // Number 생성자 함수에 의한 Number 객체 생성 const numObj = new Number(123); console.log(typeof numObj); // object console.log(numObj); // Number {123} // Boolean 생성자 함수에 의한 Boolean 객체 생성 const boolObj= new Boolean(true); console.log(typeof boolObj); // object console.log(boolObj); // Boolean {true} // Function 생성자 함수에 의한 Function 객체(함수) 생성 const func = new Function('x', 'return x * x'); console.log(typeof func); // function console.dir(func); // ƒ anonymous(x) // Array 생성자 함수에 의한 Array 객체(배열) 생성 const arr = new Array(1, 2, 3); console.log(typeof arr); // object console.log(arr); // [1, 2, 3] // RegExp 생성자 함수에 의한 RegExp 객체(정규 표현식) 생성 const regExp = new RegExp(/ab+c/i); console.log(typeof regExp); // object console.log(regExp); // /ab+c/i // Date 생성자 함수에 의한 Date 객체 생성 const date = new Date(); console.log(typeof date); // object console.log(date); // Mon May 04 2020 08:36:33 GMT+0900 (대한민국 표준시)
- 인스턴스(instance): 생성자 함수가 생성한 객체
new
연산자와 함께Object
생성자 함수를 호출하면 빈 객체를 생성하여 반환1 2 3 4 5 6 7 8 9 10 11
// 빈 객체의 생성 const person = new Object(); // 프로퍼티 추가 person.name = 'Lee'; person.sayHello = function () { console.log('Hi! My name is ' + this.name); }; console.log(person); // {name: "Lee", sayHello: ƒ} person.sayHello(); // Hi! My name is Lee
- 객체 리터럴을 사용하는 방법보다 번거롭다.
17.2 생성자 함수
17.2.1 객체 리터럴에 의한 객체 생성 방식의 문제점
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const circle1 = {
radius: 5,
getDiameter() {
return 2 * this.radius;
}
};
console.log(circle1.getDiameter()); // 10
const circle2 = {
radius: 10,
getDiameter() {
return 2 * this.radius;
}
};
console.log(circle2.getDiameter()); // 20
- 직관적이고 간편하지만, 단 하나의 객체만 생성한다.
- 프로퍼티 구조가 동일한 여러 개의 객체를 생성할 때 매번 같은 코드를 작성해야 한다.
17.2.2 생성자 함수에 의한 객체 생성 방식의 장점
- 생성자 함수: 일반 함수로 동일한 방법으로 정의하고
new
연산자와 함께 호출
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 생성자 함수
function Circle(radius) {
// 생성자 함수 내부의 this는 생성자 함수가 생성할 인스턴스를 가리킨다.
this.radius = radius;
this.getDiameter = function () {
return 2 * this.radius;
};
}
// 인스턴스의 생성
const circle1 = new Circle(5); // 반지름이 5인 Circle 객체를 생성
const circle2 = new Circle(10); // 반지름이 10인 Circle 객체를 생성
console.log(circle1.getDiameter()); // 10
console.log(circle2.getDiameter()); // 20
// new 연산자와 함께 호출하지 않으면 생성자 함수로 동작하지 않는다.
// 즉, 일반 함수로서 호출된다.
const circle3 = Circle(15);
// 일반 함수로서 호출된 Circle은 반환문이 없으므로 암묵적으로 undefined를 반환한다.
console.log(circle3); // undefined
// 일반 함수로서 호출된 Circle내의 this는 전역 객체를 가리킨다.
console.log(radius); // 15
- 인스턴스를 생성하기 위한 클래스처럼 이용할 수 있어 구조가 동일한 여러 객체를 간편하게 생성할 수 있다.
this
- 객체 자신의 프로퍼티나 메서드를 참조하기 위한 자기 참조 변수(self-referencing variable)
this
바인딩은 함수 호출 방식에 따라 동적으로 결정1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// 함수는 다양한 방식으로 호출될 수 있다. function foo() { console.log(this); } // 일반적인 함수로서 호출 // 전역 객체는 브라우저 환경에서는 window, Node.js 환경에서는 global을 가리킨다. foo(); // window // 메서드로서 호출 const obj = { foo }; // ES6 프로퍼티 축약 표현 obj.foo(); // obj // 생성자 함수로서 호출 const inst = new foo(); // inst
- 일반 함수로서 호출 ⇒ 전역 객체
- 메서드로서 호출 ⇒ 메서드를 호출한 객체(마침표 앞의 객체)
- 생성자 함수로서 호출 ⇒ 생성자 함수가 생성할 인스턴스
17.2.3 생성자 함수의 인스턴스 생성 과정
1. 인스턴스 생성과 this
바인딩
1
2
3
4
5
6
7
8
9
function Circle(radius) {
// 1. 암묵적으로 빈 객체가 생성되고 this에 바인딩된다.
console.log(this); // Circle {}
this.radius = radius;
this.getDiameter = function () {
return 2 * this.radius;
};
}
- 암묵적으로 빈 객체(인스턴스) 생성
- 인스턴스는
this
에 바인딩 됨 - 런타임 이전에 실행
2. 인스턴스 초기화
1
2
3
4
5
6
7
8
9
function Circle(radius) {
// 1. 암묵적으로 인스턴스가 생성되고 this에 바인딩된다.
// 2. this에 바인딩되어 있는 인스턴스를 초기화한다.
this.radius = radius;
this.getDiameter = function () {
return 2 * this.radius;
};
}
this
에 바인딩된 인스턴스에 프로퍼티, 메서드를 추가- 생성자 함수가 인수로 전달받은 초깃값을 할당하여 초기화 또는 고정값을 할당
3. 인스턴스 반환
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Circle(radius) {
// 1. 암묵적으로 인스턴스가 생성되고 this에 바인딩된다.
// 2. this에 바인딩되어 있는 인스턴스를 초기화한다.
this.radius = radius;
this.getDiameter = function () {
return 2 * this.radius;
};
// 3. 완성된 인스턴스가 바인딩된 this가 암묵적으로 반환된다
}
// 인스턴스 생성. Circle 생성자 함수는 암묵적으로 this를 반환한다.
const circle = new Circle(1);
console.log(circle); // Circle {radius: 1, getDiameter: ƒ}
- 모든 처리가 끝나면 완성된 인스턴스가 바인딩된
this
가 암묵적으로 반환 명시적으로 객체를 반환하면 암묵적
this
반환 무시1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
function Circle(radius) { // 1. 암묵적으로 인스턴스가 생성되고 this에 바인딩된다. // 2. this에 바인딩되어 있는 인스턴스를 초기화한다. this.radius = radius; this.getDiameter = function () { return 2 * this.radius; }; // 3. 암묵적으로 this를 반환한다. // 명시적으로 객체를 반환하면 암묵적인 this 반환이 무시된다. return {}; } // 인스턴스 생성. Circle 생성자 함수는 명시적으로 반환한 객체를 반환한다. const circle = new Circle(1); console.log(circle); // {}
명시적으로 원시 값을 반환하면 이를 무시하고 암묵적
this
반환1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
function Circle(radius) { // 1. 암묵적으로 인스턴스가 생성되고 this에 바인딩된다. // 2. this에 바인딩되어 있는 인스턴스를 초기화한다. this.radius = radius; this.getDiameter = function () { return 2 * this.radius; }; // 3. 암묵적으로 this를 반환한다. // 명시적으로 원시값을 반환하면 원시값 반환은 무시되고 암묵적으로 this가 반환된다. return 100; } // 인스턴스 생성. Circle 생성자 함수는 명시적으로 반환한 객체를 반환한다. const circle = new Circle(1); console.log(circle); // Circle {radius: 1, getDiameter: ƒ}
- 생성자 함수의 기본 동작을 위해
return
문은 반드시 생략해야 한다.
17.2.4 내부 메서드 [[Call]]
과 [[Construct]]
함수는 객체이므로 일반 객체와 동일하게 일반 객체의 내부 슬롯과 내부 메서드를 가지고 있다.
1 2 3 4 5 6 7 8 9 10 11 12
// 함수는 객체다. function foo() {} // 함수는 객체이므로 프로퍼티를 소유할 수 있다. foo.prop = 10; // 함수는 객체이므로 메서드를 소유할 수 있다. foo.method = function () { console.log(this.prop); }; foo.method(); // 10
- 함수는 함수로 동작하기 위해 일반 객체와는 달리 함수 객체만을 위한
[[Environment]]
,[[FormalParameters]]
등의 내부 슬롯과[[Call]]
과[[Construct]]
등의 내부 메서드를 추가로 가지고 있다. 함수가 일반 함수로서 호출되면
[[Call]]
이 호출되고,new
연산자와 함께 생성자 함수로서 호출되면[[Construct]]
가 호출된다.1 2 3 4 5 6 7
function foo() {} // 일반적인 함수로서 호출: [[Call]]이 호출된다. foo(); // 생성자 함수로서 호출: [[Construct]]가 호출된다. new foo();
- callable: 내부 메서드
[[Call]]
을 갖는 함수 객체- 호출할 수 있는 객체, 즉 함수
- 함수 객체는 무조건 callable이어야 한다.
- constructor:
[[Construct]]
를 갖는 함수 객체- 생성자 함수로서 호출할 수 있는 함수
- non-constructor:
[[Construct]]
를 갖지 않는 함수 객체- 생성자 함수로서 객체를 호출할 수 없는 함수
17.2.5 constructor와 non-constructor의 구분
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 일반 함수 정의: 함수 선언문, 함수 표현식
function foo() {}
const bar = function () {};
// 프로퍼티 x의 값으로 할당된 것은 일반 함수로 정의된 함수이기에 메서드로 인정하지 않는다.
const baz = {
x: function () {}
};
// 일반 함수로 정의된 함수만이 constructor이다.
new foo(); // -> foo {}
new bar(); // -> bar {}
new baz.x(); // -> x {}
// 화살표 함수 정의
const arrow = () => {};
new arrow(); // TypeError: arrow is not a constructor
// 메서드 정의: ES6의 메서드 축약 표현만을 메서드로 인정한다.
const obj = {
x() {}
};
new obj.x(); // TypeError: obj.x is not a constructor
- constructor: 함수 선언문, 함수 표현식, 클래스
- non-constructor: 메서드(ES6 메서드 축약 표현), 화살표 함수
- ECMAScript 사양에서 메서드는 ES6의 메서드 축약 표현만을 의미한다.
생성자 함수로서 호출하면 에러가 발생한다.
1 2 3 4 5 6 7 8 9 10
function foo() {} // 일반 함수로서 호출 // [[Call]]이 호출된다. 모든 함수 객체는 [[Call]]이 구현되어 있다. foo(); // 생성자 함수로서 호출 // [[Construct]]가 호출된다. // 이때 [[Construct]]를 갖지 않는다면 에러가 발생한다. new foo();
17.2.6 new
연산자
- 생성자 함수로서 정의하지 않은 일반 함수를
new
연산자와 함께 호출하는 경우[[Construct]]
호출
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
// 생성자 함수로서 정의하지 않은 일반 함수 function add(x, y) { return x + y; } // 생성자 함수로서 정의하지 않은 일반 함수를 new 연산자와 함께 호출 let inst = new add(); // 함수가 객체를 반환하지 않았으므로 반환문이 무시된다. // 따라서 빈 객체가 생성되어 반환된다. console.log(inst); // {} // 객체를 반환하는 일반 함수 function createUser(name, role) { return { name, role }; } // 생성자 함수로서 정의하지 않은 일반 함수를 new 연산자와 함께 호출 inst = new createUser('Lee', 'admin'); // 함수가 생성한 객체를 반환한다. console.log(inst); // {name: "Lee", role: "admin"}
- 생성자 함수를
new
연산자 없이 호출하는 경우[[Call]]
호출
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
function Circle(radius) { this.radius = radius; this.getDiameter = function () { return 2 * this.radius; }; } // new 연산자 없이 생성자 함수 호출하면 일반 함수로서 호출된다. // 일반 함수 내부의 this는 전역 객체 window를 가리킨다. // 따라서 radius 프로퍼티와 getDiameter 메서드는 전역 객체의 프로퍼티와 메서드다. const circle = Circle(5); console.log(circle); // undefined // 일반 함수 내부의 this는 전역 객체 window를 가리킨다. console.log(radius); // 5 console.log(getDiameter()); // 10 circle.getDiameter(); // TypeError: Cannot read property 'getDiameter' of undefined
- 생성자 함수는 일반적으로 파스칼 케이스로 명명해 일반 함수와 구별한다.
17.2.7 new.target
- ES6에서 지원
- 생성자 함수가
new
연산자 없이 호출되는 것을 방지하기 위해 사용 this
와 유사하게 constructor인 모든 함수 내부에서 암묵적인 지역 변수와 같이 사용- 메타 프로퍼티라고 부른다.
new.target
가 가리키는 대상new
연산자와 함께 호출 ⇒ 함수 자신- 일반 함수로서 호출 ⇒
undefined
생성자 함수로 호출되기 위해서 재귀 호출을 이용 가능
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// 생성자 함수 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; }; } // new 연산자 없이 호출하여도 new.target을 통해 생성자 함수로서 호출된다. const circle = Circle(5); console.log(circle.getDiameter());
new.target
을 지원하지 않는 경우(IE) 스코프 세이프 생성자 패턴을 사용1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
// Scope-Safe Constructor Pattern function Circle(radius) { // 생성자 함수가 new 연산자와 함께 호출되면 함수의 선두에서 빈 객체를 생성하고 // this에 바인딩한다. 이때 this와 Circle은 프로토타입에 의해 연결된다. // 이 함수가 new 연산자와 함께 호출되지 않았다면 // 이 시점의 this는 전역 객체 window를 가리킨다. // 즉, this와 Circle은 프로토타입에 의해 연결되지 않는다. if (!(this instanceof Circle)) { // new 연산자와 함께 호출하여 생성된 인스턴스를 반환한다. return new Circle(radius); } this.radius = radius; this.getDiameter = function () { return 2 * this.radius; }; } // new 연산자 없이 생성자 함수를 호출하여도 생성자 함수로서 호출된다. const circle = Circle(5); console.log(circle.getDiameter()); // 10
다른 빌트인 생성자 함수의 경우
Object
,Function
생성자 함수는new
연산자 없이 호출해도new
연산자와 함께 호출한 것처럼 동작1 2 3 4 5 6 7 8 9 10 11
let obj = new Object(); console.log(obj); // {} obj = Object(); console.log(obj); // {} let f = new Function('x', 'return x ** x'); console.log(f); // ƒ anonymous(x) { return x ** x } f = Function('x', 'return x ** x'); console.log(f); // ƒ anonymous(x) { return x ** x }
String, Number, Boolean 생성자 함수는
new
연산자 없이 호출하면 객체가 아닌 문자열, 숫자, 불리언 값을 반환1 2 3 4 5 6 7 8
const str = new String(123); console.log(str, typeof str); // String {'123'} 'object' const num = new Number('123'); console.log(num, typeof num); // Number {123} 'object' const bool = new Boolean('true'); console.log(bool, typeof bool); // Boolean {true} 'object'
이를 이용해 데이터 타입 변환 가능
1 2 3 4 5 6 7 8
const str = String(123); console.log(str, typeof str); // 123 string const num = Number('123'); console.log(num, typeof num); // 123 number const bool = Boolean('true'); console.log(bool, typeof bool); // true boolean
This post is licensed under CC BY 4.0 by the author.