JavaScript
[모던 자바스크립트] 생성자함수에 의한 객체 생성
zin502
2022. 8. 17. 20:35
Object 생성자 함수
- new 연산자와 함께 object 생성자 함수를 호출하면 빈 객체를 생성해 반환함.
- 빈 객체는 생성 이후 프로퍼티 또는 메서드를 추가해 완성할 수 있다.
// 빈 객체의 생성
const person = new Object();
// 프로퍼티 추가
person.name = "Sim Mi Jin";
person.sayHello = function () {
console.log(`Hi my name is ${this.name}`);
};
console.log(person);
// {name: "Sim Mi Jin", sayHello: ƒ ()}
person.sayHello();
// Hi my name is Sim Mi Jin
그런데 .. 생성자 함수가 뭐죠 .. ?
- new 연산자와 함께 호출해 객체(인스턴스)를 생성하는 함수
- 인스턴스 : 생성자함수에 의해 생성된 객체
// 17-2
// String 생성자 함수에 의한 String객체 생성
const strObj = new String("Sim");
console.log(typeof strObj);
console.log(strObj);
// Number 생성자 함수에 의한 Number객체 생성
const numObj = new Number(414);
console.log(typeof numObj);
console.log(numObj);
// Boolean 생성자 함수에 의한 Boolean 객체 생성
const boolObj = new Boolean(true);
console.log(typeof boolObj);
console.log(boolObj);
// Function 생성자 함수에 의한 Function(함수)객체 생성
const funcObj = new Function("x", "return x*x");
console.log(typeof funcObj);
console.log(funcObj);
// Array 생성자 함수에 의한 Array(배열)객체 생성
const arr = new Array(1, 2, 3);
console.log(typeof arr);
console.log(arr);
// RegExp생성자 함수에 의한 RegExp(정규 표현식)객체 생성
const regExp = new RegExp(/a+b+c/i);
console.log(typeof regExp);
console.log(regExp);
// Date생성자 함수에 의한 Date객체 생성
const date = new Date();
console.log(typeof date);
console.log(date);
생성자 함수
객체 리터럴에 의한 객체 생성 방식의 문제점
- 한 번에 하나의 객체만 생성
- 동일한 프로퍼티를 가진 객체를 여러개 생성성해야하는 경우 매번 같은 프로퍼티를 기술해야함. ➡️ 비효율적
// 17-3
// 객체 리터럴에 의한 객체 생성 방식의 문제점
const circle1 = {
radius: 5,
getDiameter() {
return 2 * this.radius;
}
};
console.log(circle1.getDiameter());
const circle2 = {
radius: 10,
getDiameter() {
return 2 * this.radius;
}
};
console.log(circle2.getDiameter());
- 프로퍼티는 객체마다 프로퍼티 값이 다를 수 있으나 메서드는 동일한 경우가 일반적
생성자 함수에 의한 객체 생성 방식의 장점
- 구조가 동일한 객체를 편리하게 생성 가능
// 17-4
// 생성자 함수에 의한 객체 생성 방식의 장점
// 생성자 함수
function Circle(radius) {
//
this.radius = radius;
this.getDiamter = function () {
return 2 * this.radius;
};
}
// 인스턴스의 생성
const c1 = new Circle(5);
const c2 = new Circle(10);
console.log(c1.getDiamter());
console.log(c2.getDiamter());
this
- 객체 자신의 프로퍼티나 메서드를 참조하기 위한 자기 참조 변수
// 17-5
// 생성자 함수에 의한 객체 생성 방식의 장점
// 함수는 다양한 방식으로 호출될 수 있음.
function foo(this) {
console.log(this);
}
// 일반적인 함수로서 호출
// 전역 객체는 브라우저 환경에서는 window,node.js환경에서는 global을 가리킴
foo(); // window
const obj = { foo };
// ES6 프로퍼티 축약 표현
// 메서드로서 호출
obj.foo(); // obj
// 생성자 함수로서 호출
const inst = new foo(); // inst
만약 new 연산자와 함께 생성자 함수를 호출하지 않는다면요 .. ? 🐳
- 일반 함수로 동작합니다 !
// 17-6
// new 연산자와 함께 호출하지 않으면 생성자 함수로 동작하지 않는다.
// 즉, 일반 함수로서 호출된다.
const circle3 = Circle(15);
// 일반 함수로서 호출된 Circle은 반환문이 없으므로 암묵적으로 undefined를 반환한다.
console.log(circle3);
// 일반함수로서 호출된 Circle 내의 this는 전역 객체를 가리킨다.
console.log(raduis);
생성자 함수의 인스턴스 생성과정
- 생성자 함수가 인스턴스를 생성하는 것은 필수
- 인스턴스 초기화는 옵션
// 17-7
// 생성자 함수
function Circle(radius) {
// 인스턴스 초기화
this.radius = radius;
this.getDiameter = function () {
return 2 * this.radius;
};
}
인스턴스 생성과 this 바인딩
- 암묵적 빈 객체 생성(인스턴스)
- this에 바인딩됨
- 바인딩 : 식별자와 값을 연결하는 과정
// 17-8
// 인스턴스 생성과 바인딩
function Circle(radius) {
// 1. 암묵적으로 인스턴스가 생성되고 this를 바인딩한다.
console.log(this);
this.radius = radius;
this.getDiameter = function () {
return 2 * this.radius;
};
}
인스턴스 초기화
// 17-9
// 인스턴스 생성과 바인딩
function Circle(radius) {
// 1. 암묵적으로 인스턴스가 생성되고 this를 바인딩한다.
console.log(this);
// 2. this에 바인딩되어있는 인스턴스를 초기화한다.
this.radius = radius;
this.getDiameter = function () {
return 2 * this.radius;
};
}
인스턴스 반환
- 내부 모든 처리가 끝나면 완성된 인스턴스가 바인딩된 this가 암묵적으로 반환됨
// 17-10
// 인스턴스 반환
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);
만약 this가 아닌 다른 객체를 명시적으로 반환된다면..?
- this가 아니라 return문에 명시한 객체가 반환됨
// 17-11
// 인스턴스 반환
function Circle(radius) {
// 1. 암묵적으로 인스턴스가 생성되고 this를 바인딩한다.
// 2. this에 바인딩되어있는 인스턴스를 초기화한다.
this.radius = radius;
this.getDiameter = function () {
return 2 * this.radius;
};
// 3. 완성된 인스턴스가 바인딩된 this가 암묵적으로 반환된다.
// 명시적으로 객체를 반환하면 암묵적인 this가 무시됨.
return {};
}
// 인스턴스 생성. Circle 생성자 함수는 암묵적으로 this를 반환한다.
const circle = new Circle(1);
console.log(circle);
하지만 .. 명시적으로 원시 값을 반환한다면 .. ?
- 원시 값 반환 무시, 암묵적으로 this가 반환
// 17-12
// 인스턴스 반환
function Circle(radius) {
// 1. 암묵적으로 인스턴스가 생성되고 this를 바인딩한다.
// 2. this에 바인딩되어있는 인스턴스를 초기화한다.
this.radius = radius;
this.getDiameter = function () {
return 2 * this.radius;
};
// 3. 완성된 인스턴스가 바인딩된 this가 암묵적으로 반환된다.
// 명시적으로 원시값을 반환하면 원시값 반환은 무시, 암묵적으로 this가 반환됨.
return 100;
}
// 인스턴스 생성. Circle 생성자 함수는 암묵적으로 this를 반환한다.
const circle = new Circle(1);
console.log(circle);
- 따라서 생성자 함수 내부에서 return문을 반드시 생략해야 함.
내부 메서드 [[Call]],[[Constructor]]
- 함수 선언문 또는 함수 표현식으로 정의한 함수는 일반적인 함수로서 호출 가능, 생성자로도 OK!
- 함수 = 객체 이므로 일반 객체와 동일하게 동작
- 함수 객체는 일반 객체가 가지고 있는 내부슬롯과 메서드를 모두 가지고 있기 때문
// 17-13
// 내부 매서드
// 함수는 객체다.
function foo() {}
// 함수는 객체 이므로 프로퍼티를 소유 할 수 있다.
foo.prop = 10;
// 함수는 객체이므로 메서드를 소유할 수 있다.
foo.method = function () {
console.log(this.prop);
};
foo.method();
// 10
- 함수는 객체이나, 일반 객체와는 다르다
- 🔥 일반 객체는 호출이 불가능 하지만, 함수는 가능하다
- 일반 함수로서 호출 : [[Call]]
- 생성자 함수로서 호출 : [[Constructor]]
// 17-14
// 내부 매서드
// 함수는 객체다.
function foo() {}
// 일반적인 함수로서 호출 : [[Call]]이 호출된다.
foo();
// 생성자 함수로 호출 : [[Construct]]이 호출.
new foo();
construct 와 non-construct의 구분
- construct : 함수 선언문, 함수 표현식, 클래스(클래스도 함수임)
- non-construct : 메서드(ES6메서드 축약 표현), 화살표 함수
// 17-15
// construct 와 non-construct 구분
// 일반적인 함수 정의 : 함수 선언문, 함수 표현식
function foo() {}
const bar = function () {};
// 프로퍼티 x의 값으로 할당된 것은 일반함수로 정의된 함수. 이는 메서드로 인정되지 않음.
const byz = {
x: function () {}
};
// 일반 함수로 정의된 함수만이 construct다.
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
- 🔥주의 : 생성자 함수로서 호출할 것을 기대하고 정의하지 않은 일반함수에 new연산자를 붙여 호출시 생성자 함수처럼 동작할 수 있음
// 17-16
function foo() {}
// 일반적인 함수로서 호출
// [[Call]]이 호출. 모든 함수 객체는 [[Call]]이 구현되어 있음
new foo(); // foo {}
// 생성자 함수로서 호출
// [[construct]]가 호출됨. [[construct]]를 갖지 않는다면 에러 발생
new연산자
// 17-17
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("Sim", "admin");
// 함수가 생성한 객체를 반환한다.
console.log(inst);
// {name: "Sim", role: "admin"}
반대로 한다면 .. ?
// 17-18
// 생성자 함수
function Circle(radius) {
this.radius = radius;
this.getDiameter = function () {
return 2 * this.radius;
};
}
// new연산자 없이 생성자 함수를 호출하면 일반한수로서 호출됨
const c = Circle(5);
console.log(c); // undefined
// 일반 함수 내부의 this는 전역 객체 window를 가리킴
console.log(radius);
console.log(getDiameter());
c.getDiameter();
// TypeError: Cannot read property 'getDiameter' of undeined
new.target
- new 연산자와 함께 생성자 함수로서 호출되면 함수 내부의 new.target은 함수 자신을 가리킴.
- 일반함수로서 호출된 함수 내부의 new.target은 undfined다.
// 17-19
// 생성자 함수
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연산자 없이 생성자 함수를 호출하면 일반한수로서 호출됨
const c = Circle(5);
console.log(c.getDiameter());
스코프 세이프 생성자 패턴
- new target을 사용할 수 없을 경우
// 17-20
// Scope-Safe Constructor Patten
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 c = Circle(5);
console.log(c.getDiameter());
- 참고 🦄 : 대부분의 빌트인 생성자 함수는 new연산자와 함께 호출됐는지 확인 후 적절한 값을 반환
// 17-21
let obj = new Object();
console.log(obj);
obj = Object();
console.log(obj); // {}
let f = new Function("x", "return x ** x");
console.log(f);
f = new Function("x", "return x ** x");
console.log(f);
- 하지만 .. String,Number,Boolean 생성자 함수는 new연산자와 함께 호출시 String,Number,Boolean 객체를 생성해 반환하지만, new연산자 없이 호출한다면 문자열, 숫자, 불리언 값을 반환함.
- 이를 통해 데이터 타입을 변환하기도 함.
// 17-22
const str = String(123);
console.log(str, typeof str);
const num = Number("123");
console.log(num, typeof num);
const bool = boolean("true");
console.log(bool, typeof bool);