객체는 객체 리터럴 이외의 생성자 함수를 사용하여 객체를 생성할 수 있다.
const person = new Object();
person.name = 'Ssong';
📌 생성자 함수 : new 연산자와 함께 호출하여 객체를 생성하는 함수를 말한다.
📌 인스턴스 : 생성자 함수에 의해 생성된 객체
🔎 JS는 Object 생성자 함수 이외에도 String, Number, Boolean, Function등 다양한 빌트인 생성자 함수를 제공한다.
const strObj = new String('Ssong');
const Number = new Number(123);
const function = new Function('x', 'return x + x');
❗ 반드시 Object 생성자 함수를 사용해 빈 객체를 생성해야 하는 것은 아니다. -> 빈 객체 만들려면 리터럴 쓰는거랑 다를 바가 없다.
❗❗ 객체를 생성하는 방법은 객체 리터럴을 사용하는 것이 더 간편하다.
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
❗ 프로퍼티 구조가 동일함에도 불구하고 매번 같은 프로퍼티와 메서드를 기술해야 한다. 만약 수십 개의 객체를 생성해야 한다면 문제는 커진다.
// 생성자 함수
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
const circle = Circle(5);
console.log(circle); //undefined -> Circle이 일반 함수로 호출되었을 때는, Circle내에 return문이 없으므로 undefined를 반환한다.
function Circle(radius) {
// 인스턴스 초기화
this.radius = radius;
this.getDiameter = function () {
return 2 * this.radius;
};
}
// 인스턴스 생성
const circle1 = new Circle(5);
📌 new 연산자와 함께 함수를 호출하면 자바스크립트 엔진은 암묵적으로 인스턴스를 생성하고 반환한다.
❓ 바인딩
- 식별자와 값을 연결하는 과정.
- 예를 들어, 변수 선언은 변수 이름과 확보된 메모리 공간의 주소를 바인딩 하는 것이다.
- this 바인딩은 this와 this가 가리킬 객체를 바인딩하는 것이다.
- 할당은 개발자가 할 수 있지만 바인딩은 오직 JS엔진만이 할 수 있다.
function Circle(radius) {
// 1. 암묵적으로 인스턴스가 생성되고 this에 바인딩된다.
// 2. this에 바인딩되어 있는 인스턴스를 초기화한다.
this.radius = radius;
this.getDiameter = function () {
return 2 * this.radius;
};
// 3. 완성된 인스턴스가 바인딩된 this가 암묵적으로 반환된다. return this
}
const circle = new Circle(1);
console.log(circle); //Circle {radius: 1, gerDiameter: f}
function Circle(radius) {
this.radius = radius;
this.getDiameter = function () {
return 2 * this.radius;
};
return {}; // 명시적 반환
}
const circle = new Circle(1);
console.log(circle) // {}
function Circle(radius) {
this.radius = radius;
this.getDiameter = function () {
return 2 * this.radius;
};
return 100; // 명시적으로 원시 값을 반환
}
const circle = new Circle(1);
console.log(circle) // Circle {radius: 1, getDiameter: f}
❗❗ 이처럼 생성자 함수 내부에서 명시적으로 this가 아닌 다른 값을 반환하는 것은 생성자 함수의 기본 동작을 훼손하기 때문에, 생성자 함수 내부에서는 return 문을 반드시 생략해야 한다.
[[Call]]
과 [[Construct]]
- 함수는 일반 객체가 가지고 있는 내부 슬롯과 내부 메소드들은 물론, 함수 객체만을 위한
[[Enviroment]]
,[[FormalParameters]]
등의 내부 슬롯과[[Call]]
,[[Construct]]
같은 내부 메소드를 추가로 가지고 있다.- 함수가 일반 함수로서 호출되면 함수 객체의 내부 메서드
[[Call]]
이 호출되고, new 연산자와 함께 생성자 함수로서 호출되면 내부 메서드[[Construct]]
가 호출된다.function foo() {} foo(); // 일반적인 함수로서 호출 , [[Call]] 호출 new foo(); // 생성자 함수로서 호출, [[Construct]]호출
- callable : 내부 메서드
[[Call]]
을 갖는 함수 객체, 함수 객체는 반드시 callable이어야 한다.- constructor : 내부 메서드
[[Constructor]]
를 갖는 함수 객체. 생성자 함수로서 호출할 수 있는 함수다.- non-constructor : 내부 메서드
[[Constructor]]
를 갖지 않는 객체. 생성자 함수로서 호출할 수 없는 함수다.- ❗❗ 모든 함수 객체는 callable이면서 constructor이거나, callable이면서 non-constructor이다.
function foo() {}
const bar = function () {};
//프로퍼티 x의 값으로 할당된 것은 일반 함수로 정의된 함수다. 이는 메소드로 인정하지 않는다.
const baz = {
x: function() {}
};
new foo(); -> foo {}
new bar(); -> bar {}
new bar.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
[[Call]]
이 호출되고, new연산자와 함께 생성자 함수로서 호출하면 [[Constructor]]
가 호출된다. function foo() {}
foo(); //일반 함수로서 호출, [[Call]] 이 호출된다.
new foo(); //생성자 함수로서 호출, [[Constructor]]가 호출된다.
[[Construct]]
가 호출된다. (constructor 여야 한다.)function add(x, y) {
return x + y;
}
//생성자 함수로서 정의하지 않은 일반 함수를 new 연산자와 함께 호출
let inst = new add();
//함수가 객체를 반환하지 않으므로 반환문이 무시된다. 빈 객체 반환
console.log(inst); // {}
//객체를 반환하는 일반 함수
function user(name, role) {
return { name, role };
}
//일반 함수를 new연산자와 함께 호출
inst = new user('Song', 'admin');
//함수가 생성한 객체를 반환한다.
console.log(inst); //{ name: 'Song', role: 'admin' }
function Circle(radius) {
this.radius = radius;
this.getDiameter = function () {
return 2 * this.radius;
};
}
// 일반 함수로서 호출된다.
const circle = Circle(15);
// 반환문이 없으므로 undefined를 반환한다.
console.log(circle); // undefined
// 일반 함수로 호출된 Circle 내 this는 전역 객체 window를 가리킨다.
console.log(radius); // 15
console.log(getDiameter()); // 30
circle.getDiameter(); // TypeError: Cannnot read property 'getDiameter' of undefined
📌 일반적으로 생성자 함수는 첫 문자를 대문자로 기술하여 일반 함수와 구별할 수 있도록 한다.
첫 문자를 대문자로 기술하는 파스칼 케이스로 명명했다 할지라도 실수는 언제나 발생할 수 있다. 이런 실수를 방지하기 위해 ES6에서는 new.target을 지원하고 있다.
const person(name) {
if (!(this instanceof Person)) {
return new Person(name);
} //아래의 경우처럼 new를 붙이지 않고 호출하는 것을 대비하여 방어 코드를 만들자.
}
const me = Person('Ssong'); //new가 붙지 않은 상태로 호출되었다. 일반 함수로 호출되었다.
function Circle(radius) {
// new 연산자와 함께 호출되지 않았다면 new.target은 undefined다.
if(!new.target) {
// 생성자 함수를 재귀 호출하여 생성된 인스턴스를 반환
return new Circle(radius);
}
this.radius = radius;
this.getDiameter = function () {
return 2 * this.radius;
};
}
// 일반 함수로서 호출되더라도 new.target을 통해 생성자 함수로서 호출된다.
const circle = Circle(5);
console.log(circle.getDiameter()); // 10
❗ new.target은 IE에서 지원하지 않는다. 이때 스코프 세이프 생성자 패턴을 사용할 수 있다.
function Circle(radius) {
// 이 함수가 new 연산자와 함께 호출되지 않았다면 이 시점의 this는 전역 객체 window를 가리킨다.
// this와 Circle은 연결되지 않는다.
if(!(this instanceof Circle)) {
// new 연산자와 함께 호출하여 생성된 인스턴스를 반환
return new Circle(radius);
}
this.radius = radius;
this.getDiameter = function () {
return 2 * this.radius;
};
}
const circle = Circle(5);
console.log(circle.getDiameter()); // 10
let obj = new Object();
console.log(obj); // {}
obj = Object();
console.log(obj); // {}
let f = new Function('x', 'return x ** x');
console.log(f); // f anonymous(x) { return x ** x }
f = Function('x', 'return x ** x');
console.log(f); // f anonymous(x) { return x ** x }
❗ 하지만 String,Number,Boolean 생성자 함수는 new 연산자 없이 호출하면 문자열, 숫자, boolean 값을 반환한다.
const str = String(123);
console.log(str, typeof str); //123 string
<모던 자바스크립트 deepdive와, 추가 탐구한 내용을 정리한 포스트입니다.>