코딩기록

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

프론트/모던 자바스크립트 Deep Dive 책 스터디

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

뽀짝코딩 2024. 7. 21. 13:47
728x90



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

17-1. Object 생성자 함수

  • new 연산자와 함께 Object 생성자 함수를 호출하면 빈 객체를 생성해서 반환.
    • 빈 객체를 생성한 이후 프러퍼티 또는 메서드를 추가하여 객체를 완성.
    •    [예제 17-01]
         // 빈 객체의 생성
         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
  • 생성자 함수란 new연산자와 함께 호출하여 객체(인스턴스)를 생성하는 함수. 
  • 자바스크립트는 Object 생성자 함수 이외에도 String, Number, Boolean, Function, Array, Date, RegExp, Promise 등의 빌트인 생성자 함수를 제공. 
  • 객체를 생성하는 방법은 객체 리터럴을 사용하는 것이 더 간편하며, Object  생성자 함수를 사용해 객체를  생성하는 방식은 특별한 이유가 없다면 그다지 유용해 보이지 않음.
  •    [예제 17-02]
       // 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 (대한민국 표준시)

var strObj = new String('Lee'); 에서 new를 붙여 만든 것은 String 객체  [[Prototype]] 프로토타 입프로퍼티가 존재하고 이걸로 메서드들을 상속받아 쓸 수 있음.

String('Lee') -> 그냥  문자열 리터럴을 만듦.

 

객체 생성 방식 3가지

1. 객체 리터럴

2. 생성자 함수 

3. Object() 생성자 함수 (제일 비효율적)

 

 

17-2. 생성자 함수

1). 객체 리터럴에 의한 객체 생성 방식의 문제점

  • 객체 리터럴에 의한 객체 생성 방식은 직관적이고 간편 하지만 단 하나의 객체만 생성.
  • 동일한 프로퍼티를 갖는 객체를 여러 개 생성해야 하는 경우 매번 같은 프로퍼티를 기술해야 하기 때문에 비효율적.
  • 비슷한 코드가 중복됨.
  •    [예제 17-03]
       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

 

 

2). 생성자 함수에 의한 객체 생성 방식의 장점

  • 객체(인스턴스)를 생성하기 위한 템플릿(클래스)처럼 생성자 함수를 사용하여 프로퍼티 구조가 동일한 객체(인스턴스) 여러개 를 간편하게 생성 가능.
    • 생성자 함수 만들어서 인스턴스로 값만 넘겨줌.
  •    [예제 17-04]
       // 생성자 함수
       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 연산자와 함께 호출하면 해당 함수는 생성자 함수로 동작.
  •    function test() { }

       test();  // 일반 함수로서 호출
       new test();  // 생성자 함수로서 호출
  • new 연산자와 함께 ⭕ 생성자 함수를 호출 
    • 해당 함수는 생성자 함수로 동작.
  • new 연산자와 없이❌ 생성자 함수를 호출
    • 생성자 함수가 아니라 일반 함수로 동작.
    •    [예제 17-06]
         // new 연산자와 함께 호출하지 않으면 생성자 함수로 동작하지 않음.
         // 즉, 일반 함수로서 호출.
         const circle3 = Circle(15);
         
         // 일반 함수로서 호출된 Circle은 반환문이 없으므로 암묵적으로 undefined를 반환.
         console.log(circle3); // undefined
         
         // 일반 함수로서 호출된 Circle내의 this는 전역 객체를 가리킴.
         console.log(radius); // 15
    •  [예제 17-06] 리턴한 게 없어서 undefined를 반환.
  • this는 객체 자신의 프로퍼티나 메서드를 참조하기 위한 자기 참조 변수(self-referencing variable).
  • this가 가리키는 값,  즉 This 바인딩은 함수 호출 방식에 따라 동적으로 결정.
  •  
    함수 호출 방식 this가 가리키는 값(this 바인딩)
    일반 함수로서 호출 전역 객체  (브라우저 환경 - window,  Node.js 환경 - global)
    메서드로서 호출 메서드를 호출한 객체(마침표 앞의 객체)
    생성자 함수로서 호출 생성자 함수가 생성할 인스턴스
  •    [예제 17-05]
       // 함수는 다양한 방식으로 호출될 수 있음.
       function foo() {
         console.log(this);
       }
       
       // 일반적인 함수로서 호출
       // 전역 객체는 브라우저 환경에서는 window, Node.js 환경에서는 global을 가리킴.
       foo(); // window
       
       // 메서드로서 호출
       const obj = { foo }; // ES6 프로퍼티 축약 표현
       obj.foo(); // obj
       
       // 생성자 함수로서 호출
       const inst = new foo(); // inst

 

덧) 스터디에서 나온 이런저런 내용 240722월-
✨✨함수는 호출방식 3가지!!!!  강사님이 암기하라는 몇가지 안되는 것 중 하나 함수 호출방식.✨✨
this는 기본적으로 전역객체.
①일반함수, ②메서드, ③생성자 함수
일반함수로서 호출되었을 때-
       함수 내부 this는 무조건 전역, 그때 디스는 아무 의미 없음.

메서드로서 호출되었을 때-
       메서드 내 this는 점. 앞에 있는 객체.

생성자 함수로서 호출되었을 때-
       함수 내부의 this는 암묵적 빈 객체가 디스에 바인딩됨.
       암묵적으로 생성된 객체가 생성자 함수가 생성한 인스턴스.  그 생성자 함수가 생성할 인스턴스.


*디스는 메서드, 생성자 함수일 때만 의미 있음. 디스는 동적으로 결정된다.
*함수의 인수 전달할 수 있는 자는 누구? 호출하는자,

 

 

3). 생성자 함수의 인스턴스 생성 과정

  • 생성자 함수의 역할
    • (필수 ❗)  프로퍼티 구조가 동일한 인스턴스를 생성하기 위한 템플릿(클래스)으로서 동작하여 인스턴스를 생성. 
    • (옵션  )  생성된 인스턴스를 초기화(인스턴스 프로퍼티 추가 및 초기값 할당). 
    •    [예제 17-07]
       // 생성자 함수
       function Circle(radius) {
         // 인스턴스 초기화
         this.radius = radius;
         this.getDiameter = function () {
           return 2 * this.radius;
         };
       }
       
       // 인스턴스 생성
       const circle1 = new Circle(5);  // 반지름이 5인 Circle 객체를 생성
  •  
  • Circle인스턴스인 빈 객체가 만들어지고 이게 곧 this가 됨. 이 객체 { }가 반환됨.
  • {
  • radius: 5,
  • getDiameter() {... }
  • }
  •  
  • [예제 17-07] 생성자 함수 내부의 코드를 살펴보면 this에 프로퍼티를 추가하고 필요에 따라 전달된 인수를 프로퍼티의 초기값으로 할당하여 인스턴스를 초기화함.
    • 하지만 인스턴스를 생성하고 반환하는 코드는 보이지 않음. 
    • new 연산자와 함께 생성자 함수를 호출하면 자바스크립트 엔진은 아래와 같이 인스턴스를 생성하고 초기화한 후 반환하는 과정까지 암묵적으로 처리
      • 과정 1. 인스턴스 생성과 this 바인딩
        • 암묵적으로 빈 객체가 생성(생성자 함수가 생성한 인스턴스, 미완성 단계임)되고 this에 바인딩됨.
          • 이 이유로 생성자 함수 내부의 this가 생성자 함수가 생성할 인스턴스를 가리킴. (런타임 이전 실행).
            • 바인딩(name binding)이란 식별자와 값을 연결하는 과정을 의미.
              • 변수 선언 - 변수 이름(식별자)과  확보된 메모리 공간의 주소를 바인딩.
              • this 바인딩 - this(키워드로 분류되지만 식별자 역할을 함)와 this가 가리킬 객체를 바인딩.
              •    [예제 17-08]
                   function Circle(radius) {
                     // 1. 암묵적으로 인스턴스가 생성되고 this에 바인딩됨.
                     console.log(this); // Circle {}
                   
                     this.radius = radius;
                     this.getDiameter = function () {
                       return 2 * this.radius;
                     };
                   }
      • 과정 2. 인스턴스 초기화 
        • 생성자 함수에 기술되어 있는 코드가 한 줄씩 실행되어 this에 바인딩되어 있는 인스턴스를 초기화함.
          • 즉,  this에 바인딩되어 있는 인스턴스에 프로퍼티나 메서드를 추가하고 생성자 함수가 인수로 전달받은 초기값을 인스턴스 프로퍼티에 할당하여 초기화하거나 고정값을 할당.(개발자가 하는 부분)
            •    [예제 17-09]
                 function Circle(radius) {
                   // 1. 암묵적으로 인스턴스가 생성되고 this에 바인딩됨.
                 
                   // 2. this에 바인딩되어 있는 인스턴스를 초기화함.
                   this.radius = radius;
                   this.getDiameter = function () {
                     return 2 * this.radius;
                   };
                 }
      • 과정 3. 인스턴스 반환
        • 생성자 함수 내부의 모든 처리가 끝나면 완성된 인스턴스가 바인딩된 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); // Circle {radius: 1, getDiameter: ƒ}
      •  
      • 만약 this가 아닌 다른 객체를 명시적으로 반환하면 this가 반환되지 못하고 return문에 명시한 객체가 반환.
      •      [예제 17-11]
             // 3. 암묵적으로 this를 반환.
             // 명시적으로 객체를 반환하면 암묵적인 this 반환이 무시.
             return {};  // function() {};  function() {} 반환됨. 참조형 데이터가 있음 덮어 씌움.
           }
           
           // 인스턴스 생성. Circle 생성자 함수는 명시적으로 반환한 객체를 반환.
           const circle = new Circle(1);
           console.log(circle); // {}
      •  
      • 하지만 명시적으로 원시 값을 반환하면 원시 값 반환은 무시되고 암묵적으로 this가 반환.
      •      [예제 17-12]    
             // 3. 암묵적으로 this를 반환.
             // 명시적으로 원시값을 반환하면 원시값 반환은 무시되고 암묵적으로 this가 반환.
             return 100;
           }
           
           // 인스턴스 생성. Circle 생성자 함수는 명시적으로 반환한 객체를 반환.
           const circle = new Circle(1);
           console.log(circle); // Circle {radius: 1, getDiameter: ƒ}
      • 생성자 함수 내부에서 명시적으로 this가 아닌 다른 값을 반환하는 것은 생성자 함수의 기본 동작을 훼손하기에 반드시 생성자 함수 내부에서 return 문을 생략해야 함.

 

덧) 스터디에서 나온 이런저런 내용 240722월-
*new가 왜 필요한가.
2015년 미국에서 이슈가 됨.
객체지향을 선호하던 시대적 배경.
자바스크립트에 new를 도입함.
만약 new를 빼먹으면 어떻게 하냐 하는 문제가 제기됨.

생성자 함수에서 new를 안 써줬을 때를 대비해 방어 코드를 써야 함.
문제는 일부러 new를 안 붙이고 사용하는 경우가 있음.
타입케스팅(타입변환)을 위해 문자형, 넘버형에 new를 안 붙이고 씀.

함수는 하나의 일만 하게 만드는 게 좋다.
생성자를 쓰면 객체만 만드는 게 좋다.

그래서 클래스가 만들어짐
클래스에서는 new 없이 인스턴스를 호출하면 에러가 남.
결론은 new를 생성자 함수 말고 클래스에서 써라. 


*메서드는 점. 앞에 객체가 있고 그 객체를 읽어 들이고 그때 this가 쓰임.
함수는 객체로 전달.
*객체리터럴 내부에서 메서드를 만들 때 this를 씀 프로퍼티만 있으면 문제없음.
*어떤 두 가지 문법이 동일한 행동을 할 때 신텍틱슈가, 문법설탕이라 함.  
*16장부터 객체지향 이야기. 

 

 

 

4). 내부 메서드 [[Call]]과 [[Construct]]

  • 함수 선언문 또는 함수 표현식으로 정의한 함수는 일반적인 함수로서도 생성자 함수로서도 호출 가능.
  • 생성자 함수로서 호출한다는 것은 new 연산자와 함께 호출하여 객체를 생성한다는 의미.
  • 일반 객체는 호출할 수 없지만 함수는 호출할 수 있음.
    •    [예제 17 - 13]
         // 함수는 객체.
         function foo() { }
         
         // 함수는 객체이므로 프로퍼티를 소유할 수 있음.
         foo.prop = 10;
         
         // 함수는 객체이므로 메서드를 소유할 수 있음.
         foo.method = function () {
           console.log(this.prop);
         };
         
         foo.method(); // 10
    • 함수 객체는 일반 객체가 가지고 있는 내부 슬롯과 내부 메서드는 물론,  함수 객체로 동작하기 위한 [[Environment]], [[FormalParameters]] 등의 내부 슬롯과 [[Call]], [[Construct]] 같은 내부 메서드를 추가로 가지고 있음.
    • 함수가 일반 함수로서 호출되며 함수객체의 내부 메서드 [[Call]]이 호출되고, new 연산자와 함께 생성자 함수로서 호출되면 내부 메서드 [[Construct]]가 호출됨.
    •    [예제 17 - 14]
         function foo() { }
         
         // 일반적인 함수로서 호출: [[Call]]이 호출.
         foo();
         
         // 생성자 함수로서 호출: [[Construct]]가 호출.
         new foo();
    • callable, constructor, non-constructor
      • callable 
        • 내부 메서드 [[Call]]을 갖는 함수 객체.  
          • 호출할 수 있는 객체. 즉, 함수.
      • consructor
        •  내부 메서드 [[Construct]]를 갖는 함수 객체.
          • 생성자 함수로서 호출할 수 있는 함수.
      • non-constructor 
        • [[Construct]] 를 갖지 않는 함수 객체.
          • 객체를 생성자 함수로서 호출할 수 없는 함수.
    • 호출할 수 없는 객체는 함수 객체가 아니므로 함수 객체는 반드시 callable.
      • 모든 함수는 callable이면서 constructor이거나 
      • callable이면서  non-constructor.
        • 즉, 모든 함수 객체는 호출할 수 있지만 모든 함수 객체를 생성자 함수로서 호출할 수 있는 것은 아님.

 

 

5). constructor와 non-constructor의 구분

  • 자바스크립트 엔진은 함수 정의를 평가하여 함수 객체를 생성할 때 함수 정의 방식에 따라 함수를 constructor와 non-constructor로 구분.
    • constructor: 함수 선언문, 함수 표현식, 클래스(클래스도 함수)
    • non-constructor: 메서드(ES6 메서드 축약 표현), 화살표 함수
    • ECMAScript 사양에서 메서드로 인정하는 범위가 일반적인 의미의 메서드보다 좁다는 것에 주의.
    •    [예제 17-15]
         // 일반 함수 정의: 함수 선언문, 함수 표현식
         function foo() { }
         const bar = function () { };
         // 프로퍼티 x의 값으로 할당된 것은 일반 함수로 정의된 함수다. 이는 메서드로 인정하지 않음.
         const baz = {
           x: function () { }
         };
         
         // 일반 함수로 정의된 함수만이 constructor.
         new foo();   // -> foo {}
         new bar();   // -> bar {}
         new baz.x(); // -> x {}
         
       
        // non-constructor
         // 화살표 함수 정의
         const arrow = () => { };
         new arrow(); // TypeError: arrow is not a constructor
         
         // 메서드 정의: ES6의 메서드 축약 표현만을 메서드로 인정.
         const obj = {
           x() { }
         };
         new obj.x(); // TypeError: obj.x is not a constructor
    •  
    •    [예제 17-16]
         function foo() { }
         
         // 일반 함수로서 호출
         // [[Call]]이 호출된다. 모든 함수 객체는 [[Call]]이 구현되어 있다.
         foo();
         
         // 생성자 함수로서 호출
         // [[Construct]]가 호출된다. 이때 [[Construct]]를 갖지 않는다면 에러가 발생한다.
         new foo();
    • ❗ 주의 ❗ 생성자 함수로서 호출할 것을 기대하고 정의하지 않은 일반 함수(callable이면서 constructor)에 new 연산자를 붙여 호출하면 생성자 함수처럼 동작할 수 있음.

 

6). new 연산자

  • Constructor인 함수를 New 연산자와 함께 호출하면 해당함수는 생성자 함수로 동작.
    • 함수 객체의 내부 메서드 [[Call]]이 호출되는 것이 아니라 [[Construct]]가 호출됨.
    • 단, new 연산자와 함께 호출하는 함수는 non-constructor가 아닌 constructor여야 함.
    •    [예제 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('Lee', 'admin');
         // 함수가 생성한 객체를 반환.
         console.log(inst); // {name: "Lee", role: "admin"}

 

  • 반대로 new 연산자 없이 생성자 함수를 호출하면 일반 함수로 호출. 
    • 함수 객체의 내부 메서드  [[Construct]]가 호출되는 것이 아니라 [[Call]]이 호출됨.
    •    [예제  17-18]
         // 생성자 함수
         function Circle(radius) {
           this.radius = radius;
           this.getDiameter = function () {
             return 2 * this.radius;
           };
         }
         
         // new 연산자 없이 생성자 함수 호출하면 일반 함수로서 호출.
         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
    • Circle 함수는 일반 함수로 호출되어 radius 프로퍼티와 getDiameter 메서드는 전역 객체의 프로퍼티와 메서드가 됨.
    • 생성자 함수는 첫 문자를 대문자로 하는 파스칼 케이스(PascalCase)로 명명하여 일반 함수와 구분. 

 

 

7). new.target (new.target 전체가 하나의 키워드)

  • 생성자 함수가 new 연산자 없이 호출되는 것을 방지하기 위해 ES6에서는 new.target을 지원.
  • new.target은 this와 유사하게 constructor인 모든 함수 내부에서 암묵적인 지역 변수와 같이 사용되며 메타 프로퍼티라 부름. 참고로 IE는 new.target을 지원하지 않음.
    • new 연산자와 함께 생성자 함수로서 호출되면 함수 내부의 new.target은 함수 자신을 가리킴. new 연산자 없이 일반 함수로서 호출된 함수 내부의 new.target은 undefined.
    • 함수 내부에서 new.target을 사용하여 new 연산자와 생성자 함수로서 호출했는지 확인하여 그렇지 않은 경우 new 연산자와 함께 재귀 호출을 통해 생성자 함수로서 호출 가능.
    • undefined 거나 new와 함께하면 생성자 함수임. 
    •    [예제  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 연산자 없이 생성자 함수를 호출하여도 new.target을 통해 생성자 함수로서 호출.
         const circle = Circle(5);
         console.log(circle.getDiameter());  // 10
덧) 스터디에서 나온 이런저런 내용 240722월-
*new.target 등장 이유
생성자 함수는 반드시 new 키워드와 함께 호출해야 하는 규칙이 있음.
new를 붙여야 되는데 안 붙임. 안 붙여야 되는데 붙임.
지금 호출된 함수가 new와 함께 호출됐는지 아닌지 알 수 있음.




*Q: 무슨 함수인가?
function Foo() {}

A: 모른다.
이유: 함수란 무슨 함수로 호출하는지에 따라 존재.
함수라는 것은 정의를 가지고 어떻게 호출될지 알 수가 없음.
위 코드는 생성자 함수냐라고 물어보면 모른다고 얘기할 수 있고 컨스트럭터냐 넌컨스트럭터냐는 얘기할 수 있음.

[ 예시코드 01 ]
function Foo() {}

Foo();  // 일반함수로서 호출.
new Foo();  // 생성자 함수로서 호출.
const o = { a: Foo };
o.a()     // 메서드로서 호출.  o.a는 { }안의 Foo를 가리키고, 이때 펑션에 Foo 가 호출됨.
여기서 로. 서.라는 말이 중요함.


함수를 두 가지로 명확하게 구분, 컨스트럭터와 넌컨스트럭터로.
ㆍ컨스트럭터(
constructor ) => 함수 (함수선언문, 함수표현식으로 만든), 클래스
callable 이면서 constructor다. 즉, new를 붙일 수 있다.   
함수는 new를 빼먹었을 때를 대비한 방어코드를 써야 하지만 
클래스라는 것은 일반함수로서의 호출이 자동방어됨.  즉, new를 빼먹으면 오류발생시킴.

*함수선언문, 함수표현식을 만드는 예전 문법의 함수들은 콜러블이면서 컨스트럭 터라는 약점을 갖고 있다.
이 약점이 뒤에 프로토타입까지 이어짐.
생성자함수로 호출될 가능성이 있기 때문에 프로토타입이라 불리는 친구를 같이 만듦.
일반 함수로서만 호출되는 케이스에는 불필요한 객체를 만드는 게 됨. 그래서 메모리를 많이 써서 퍼포먼스에 좋지 않음.

넌컨스트럭터( non-constructor ) => 화살표함수, 함수 축약형 (ES6에서 나온 문법들)
new가 없으면 오류가 난다.  

함수 축약형 앞에 new를 붙이면 오류가 난다? -> 이거는 메서드라고 부름.


정리:
동일한 프로퍼티 구조를 만드는 인스턴스를 여러 개 만들려면 클래스 써라(생성자 함수 안됨!!).
new를 절대 붙이면 안 될 때는 화살표 함수를 써라. (무슨 이유인지는 모르겠으나 혹시나 그럴 일이 생기면)
이것이 문법적으로 가능하도록 모던 자바스크립트에서 분리가 되어있음.


[ 예시코드 02 ]
const person = {
  name: 'John Doe',
  sayHi: function () {
    console.log(`Hi, my name is ${this.name}`);
  },
};

person.sayHi();    // Hi, my name is John Doe

new person.sayHi();   // Hi, my name is undefined
person.sayHi는 person객체의 우항{ ~~ } 안의
sayHi키의 값인 함수 ' function () '를 가리킴.  
이 함수는 일반적 개념으론 매서드지만 문법적으로는 매서드가 아님. 
왜냐면 new person.sayhi(); 함수 앞에 new가 있어도 호출이 되기 때문.
문법적 매서드라는 것은 non-constructor를 얘기함(new를 붙일 수 없음).

만약 위 코드를 ' function () '을 생략하고 축약해서 
아래와 같이 변경하면
[ 예시코드 03 ]  메서드축약형
const person = {
  name: 'John Doe',
  sayHi() {
    console.log(`Hi, my name is ${this.name}`);
  },
};

person.sayHi();  // Hi, my name is John Doe

new person.sayHi();  //  TypeError: person.sayHi is not a constructor

sayHi()는 메서드라고 부른다.  그리고 new person.sayHi(); 부분이 에러가 나는 것을 볼 수 있다.
즉, 문법적으로 메서드이고 new를 붙일 수 없는 non-constructor다.

 

 

스코프 세이프 생성자 패턴(scope-safe constructor)

New.target을 사용할 수 없는 상황이면 스코프 세이프 생성자 패턴 사용 가능.

   [예제  17-20]
   // 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

new 연산자와 함께 생성자 함수에 의해 생성된 객체(인스턴스)는 프로토타입에 의해 생성자 함수와 연결됨.

이를 이용해 new 연산자와 함께 호출되었는지 확인할 수 있음.

프로토타입과 instanceof 연산자에 대해서는 19장 프로토타입에서 자세히...

 

 

  • 대부분의 빌트인 생성자 함수(Object,  String, Number, Boolean, Function, Array, Date, RegExp, Promise 등)는 new 연산자와 함께 호출되었는지를 확인한 후 적절한 값을 반환.
    • Object와 Function 생성자 함수는 new 연산자 없이 호출해도 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); // ƒ anonymous(x) { return x ** x }
         
         f = Function('x', 'return x ** x');
         console.log(f); // ƒ anonymous(x) { return x ** x }
    • String, Number, Boolean 생성자 함수는 new 연산자와 함께 호출했을 때 각각 객체를 생성해서 반환하지만, new 연산자 없이 호출하면 문자열, 숫자, 불리언 값을 반환. 이를 통해 데이터 타입을 반환하기도 함.
    •    [예제 17-22]
         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

 

 

 

 

참고

도서 - 모던 자바스크립트 Deep Dive -이웅모

이웅모 강사님 홈피  -  https://poiemaweb.com/js-prototype

이웅모 강사님 유튜브  -  https://www.youtube.com/watch?v=0AjTZG6bGq8

 

 

✅ 덧, 부분은 스터디 내용을 기억에 의존해서 쓴 글이라 틀린 부분이 있다면 댓글 부탁드립니다.- 뽀짝코딩 주인장-

 

 

반응형
Comments