모던 자바스크립트 Deep Dive : 17장 생성자 함수에 의한 객체 생성

EdLee·2022년 10월 30일

javascript

목록 보기
7/37

17장 생성자 함수에 의한 객체 생성

1. Object 생성자 함수


  • new 연산자를 사용해 Object 생성자 함수를 호출할 수 있다.
  • Object 외에도 String, Number, Boolean, Function, Array, Date, RegExp, Promise 등의 빌드인 생성자 함수를 제공한다.
  • 하지만 객체 리터럴을 사용하는 편이 간편해보이는데, Object 생성자 함수를 쓸 이유가 있나...?🙄
const person = new Object({name: 'Lee'});
console.log(person);

const strObj = new String('str');
console.log(typeof strObj); // object
console.log(strObj); // __proto__는 String

const whatType = new Object('str'); // String로 생성안해도 js가 알아서 prototype을 잘 지정해주긴 한다
console.log(typeof whatType); // 타입은 object
console.log(whatType); // __proto__는 String

2. 생성자 함수


2.1 객체 리터럴에 의한 객체 생성 방식의 문제점

  • 객체 리터럴 방식은 직관적이고 간편하다
  • 하지만 객체 리터럴에 의한 객체 생성 방식은 단 하나의 객체만 생성한다
  • 따라서 유사한 프로퍼티를 갖는 객체를 여러개 생성하기엔 적합하지 않다

2.2 생성자 함수에 의한 객체 생성

  • 생성자 함수에 의한 객체 생성 방식은 객체(인스턴스)를 생성하기 위한 템플릿(클래스)처럼 동작한다
function Circle(radius) {
  this.radius = radius;
  this.getDiameter = function() {
    return 2 * this.radius;
  };
}

const circle1 = new Circle(5);
const circle2 = new Circle(10);
console.log(circle1.getDiameter()); // 10
console.log(circle2.getDiameter()); // 20


// new를 안쓰면 Circle은 일반 함수로써 동작한다
const circle3 = Circle(15);
console.log(circle3); // undefined
console.log(radius); // 15, Circle이 전역 객체로 생성됐다. this가 window이기 때문

// 멤버로 추가해 메서드로써 호출할 경우
const circle4 = { Circle }; // Circle의 this는 circle4를 가리킨다
circle4.Circle(20);
console.log(circle4);
  • 함수 호출 방식에 따른 this
함수 호출 방식this가 가리키는 값(this 바인딩)
일반 함수로서 호출전역 객체
메서드로서 호출메서드를 호출한 객체(마침표 앞의 객체)
생성자 함수로서 호출생성자 함수가 (미래에) 생성할 인스턴스

2.3 생성자 함수의 인스턴스 생성 과정

function Circle(radius) {
  // 1. 암묵적으로 빈 객체가 생성되어 this에 바인딩

  // 2. this에 바인딩되어 있는 인스턴스를 초기화
  this.radius = radius;
  this.getDiameter = function() {
    return 2 * this.radius;
  };

  // 3. ★return은 생략★ 완성된 인스턴스(가 바인딩된 this)가 암묵적으로 반환된다.

  // return {}; // 명시적으로 객체를 반환한다면, {}가 리턴된다
  // return 10; // 명시적으로 원시값을 리턴하면, 이 return은 무시된다
}

const circle = new Circle(5);
console.log(circle); // Circle {radius: 5, getDiameter: f}

2.4 내부 메서드 [[Call]]과 [[Construct]]

  • JS에서 함수는 객체의 일종이다
  • 다른 객체와는 달리 함수 호출이 가능한 이유는 Environment, FormalParametes 등의 내부 슬롯과 Call, Construct 등의 메서드를 추가로 갖고 있기 때문이다
  • 단, 모든 함수는 call은 갖고 있지만, construct는 아닐 수 있다
function foo() {}

foo(); // [[Call]] 호출
new foo(); // [[Construct]] 호출

2.5 constuctor와 non-constructor의 구분

  • constructor : 함수 선언문, 함수 표현식, 클래스
function foo1() {}
new foo1();

const foo2 = function () {};
new foo2();

const foo3 = {
  x: function () {} // 메서드가 아니고 일반함수다
};
new foo3.x();
  • non-constructor : 메서드, 화살표 함수
const foo1 = () => {};
new foo1(); // TypeError: foo1 is not a constructor

const foo2 = {
  x() {} // 메서드 정의
};
new foo2.x(); // TypeError: foo2.x is not a constructor

2.6 new 연산자

  • 함수 선언문, 함수 표현식 등으로 선언된 모든 함수들은 new만 붙여주면 생성자 함수처럼 동작할 수 있다😨
  • 이를 방지하기 위해 일반 함수와 생성자용 함수를 구분하기 위해서 파스칼 표기법을 사용하는 것이 업계의 룰🤝
function add(x,y) {
  return x + y;
}
const obj = new add();
console.log(obj); // add{}, add라는 이름의 빈 객체를 생성

모든 생성자 함수의 이름은 첫글자를 대문자로! 🙏

2.6 new.target

  • 생성자 함수라면, new로 호출하지 않아도, constructor가 호출되도록 할 수 있다.
function Circle(radius) {
  if(!new.target) { // new로 호출되지 않았다면, new.target = undefined 이다
    return new Circle(radius);
  }
  this.radius = radius;
  this.getDiameter = function() {return 2 * this.radius;}
}

const circle = Circle(5);
console.log(circle.getDiameter()); // 10
// 만약 일반함수 였다면, TypeError: Cannot read properties of undefined (reading 'getDiameter')가 발생했을 것
  • 스코프 세이프 생성자 패턴(scope-safe constructor) : ES6 이하 환경에서는 다음과 같이 처리할 수도 있다
function Circle(radius) {
  console.log(this instanceof Circle); // false
  if(!(this instanceof Circle)) { // new로 호출되지 않았다면, this는 Window이다
    return new Circle(radius);
  }
  this.radius = radius;
  this.getDiameter = function() {return 2 * this.radius;}
}

const circle = Circle(5);
console.log(circle.getDiameter()); // 10

0개의 댓글