궁금한게 많은 열아홉

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);
profile

궁금한게 많은 열아홉

@jjin502

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!