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

해피데빙·2022년 8월 11일
0

객체 생성 방법
1. 객체 리터럴
2. 생성자 함수
...

1. Object 생성자 함수

new 연산자 + Object 생성자 함수
//빈 객체 생성하여 반환
//생성 후 프로퍼티 또는 메서드 추가하여 객체 완성

const person = new Object(); 

person.name = 'Lee'' 
person.sayHello = function(){
	console.log('hi')
}

생성자 함수
new 연산자와 함께 호출하여 객체(인스턴스)를 생성하는 함수

인스턴스
생성자 함수에 의해 생성된 객체

이외 빌트인 생성자 함수 : String, Number, Boolean 등
이러한 생성자 함수를 사용해서 만든 인스턴스는 object 타입을 가지게 도니다

사실 객체 리터럴로 만드는 것이 더 편함!

2. 생성자 함수

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

  • 장점 : 직관적, 간편
  • 단점 : 단 하나의 객체만 생성. 그러므로 동일 프로퍼티 갖는 객체 여러 개 생성 시 비효율적
const circle1 = {
	radius: 5, 
    getDiameter(){ 
    	return 2*this.radius
    }
}

const circle2 = {
	radius: 10, 
    getDiameter(){ 
    	return 2*this.radius
    }
}

객체

  • 프로퍼티: 객체 고유의 상태 표현
  • 메서드: 상태 데이터인 프로퍼티를 참조하고 조작하는 동작을 표현
    즉 위의 예시처럼 프로퍼티 값이 달라도 메서드 내용이 동일한 경우가 많다
    이때 매번 객체 리터럴 방식으로 만드는 것은 비효율적

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

  • 객체 생성 템플릿(클래스)처럼 생성자 함수 사용하여 프로퍼티 구조가 동일한 객체 여러 개 간편하게 생성 가능
  • 일반 함수와 동일한 방법으로 함수 정의
  • new 연산자와 함께 호출하면 생성자 함수로 동작 (new 없으면 일반 함수로 동작)
function Circle(radius){ 
this.radius = radius; 
this.getDiameter = function(){
	return 2*this.radius;
	}
}

const circle1 = new Circle(5)
const circle2 = new Circle(10)
const circle3 = Circle(15)//일반 함수로 호출 

console.log(circle3) //undefined : 반환문이 없는 함수니까 
console.log(radius) //circle3의 경우에서는 전역 객체를 가리킨다

}

cf. this
객체 자신의 프로퍼티나 메서드를 참조하기 위한 자기 참조 변수
this 바인딩은 함수 호출 방식에 따라 동적 결정

  • 일반함수로서 호출 : this는 전역 객체 가리킴
  • 메서드로서 호출 : this는 메서드를 호출한 객체(마침표 앞의 객체)
  • 생성자 함수로서 호출 : 생성자 함수가 생성할 인스턴스
function foo(){ console.log(this) } 
foo()//window

const obj = {foo} //프로퍼티 축약 표현 
obj.foo(); //obj

const inst = new foo(); //inst

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

생성자 함수의 역할

  • 프로퍼티 구조가 동일한 인스턴스를 생성하기 위해 템플릿(클래스)로서 동작하여
  • 인스턴스 생성하는 것 [필수]
  • 생성된 인스턴스를 초기화(인스턴스 프로퍼티 추가 및 초기값 할당) [옵션]
    : this에 프로퍼티를 추가해 필요에 따라 전달된 인수를 프로퍼티 초기값으로 할당하여 인스턴스 초기화

인스턴스를 생성하고 반환하는 코드가 없는데 어떻게 가능?

  • 자바스크립트 엔진가 암묵적으로 처리
  • new를 사용하면 암묵적으로 생성, 초기화, 반환

1. 인스턴스 생성과 this 바인딩

런타인 이전에 실행
1. 암묵적으로 빈 객체 생성 (생성자 함수가 생성한 인스턴스)
2. 인스턴스는 this에 바인딩된다

cf. 바인딩
식별자와 값을 연결하는 과정
ex. 변수 선언 : 변수 이름(식별자)와 확보된 메모리 공간의 주소를 바인딩하는 것
this 바인딩 : this(키워드지만 식별자 역할을 한다)와 가리킬 객체를 바인딩하는 것

2. 인스턴스 초기화

  • 생성자 함수에 기술되어 있는 코드가 한줄씩 실행되어 this에 바인딩되어 있는 인스턴스 초기화
  • this에 바인딩되어 잇는 인스턴스에 프로퍼티나 메서드를 추가
  • 생성자 함수가 인수로 전달받은 초기값을 인스턴스 프로퍼티에 할당하여 초기화하거나 고정값을 할당한다

3. 인스턴스 반환

  • 생성자 함수 내부의 모든 처리가 끝나면 완성된 인스턴스가 바인딩된 this가 암묵적으로 반환된다
  • 만약 this가 아닌 다른 객체를 명시적으로 반환하면 this가 반환되지 못하고 return 문에 명시한 객체가 반환된다
  • 명시적으로 원시 값을 반환하면 원시 값 반환 무시되고 암묵적으로 this가 반환
  • 명시적으로 this가 아닌 다른 값을 반환하는 것은 생성자 함수의 기본 동작을 훼손한다. 따라서 내부에서 return 문은 반드시 생략! (암묵적 반환을 하니까)
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:f}

//1)객체 return 
function Circle(radius){
this.radius = radius; 
this.getDiameter = function(){
	return 2 * this.radius
}
return {}
}
const circle = new Circle(1)
console.log(circle)//{}

//2)원시 값 return
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}

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

함수 선언문 또는 표현식으로 정의한 함수

  • 일반적인 함수로서 호출 가능
  • 생성자 함수로서 호출 가능
    : 생성자 함수로서 호출하는 것은 new 연산자와 함께 호출하여 객체 생성

함수

  • 객체, 일반 객체와 동일하게 동작 가능
  • 일반 객체가 가지고 있는 내부 슬롯과 내부 메서드 모두 가지고 있기 때문에
  • 호출할 수 있기 대문에 함수 객체만을 위한 내부 슬롯, 내부 메서드 가지고 있다

일반 함수로서 호출되면

  • 객체의 내부 메서드 [[Call]] 호출
  • [[Call]]을 갖는 함수 객체를 callable
  • 호출할 수 있는 객체, 즉 함수
  • 즉 함수는반드시 call이 있다

new 연산자와 함께 생성자 함수로서 호출되면

  • 내부 메서드 [[Construct]] 호출
  • 함수는 construct가 있을 수도 있고 없을 수도 있다
  • [[Construct]]를 갖는 함수 객체 : constructor (생성자 함수로서 호출할 수 있는)
  • [[Construct]]를 갖지 않는 함수 객체 : non-constructor
function foo(){ 
foo(); //[[Call]] : 일반 함수 
new foo();//[[Construct]] : 생성자 함수
}

5. constructor와 non-constructor의 구분

  • 자바스크립트 엔진이 둘을 구분하는 방법
    : 함수 정의를 평가해서 함수 객체를 생성할 때 함수 정의 방식에 따라 구분한다
  • constructor : 함수 선언문, 함수 표현식, 클래스 (클래스도 함수)
  • non-constructor : 메서드(ES6 메서드 축약표현), 화살표 함수

주의
ECMAScript 사양에서 메서드로 인정하는 범위가 일반적인 의미의 메서드보다 좁다는 것

//일반적인 함수 정의 : 함수 선언문, 함수 표현식
function foo(){} 
const bar = function(){}

//프로퍼티 x의 값으로 할당된 것은 일반 함수로 정의된 함수. 메서드로 인정 X 
const baz = { 
x:function(){}
}

//일반 함수로 정의된 함수만이 constructor
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 

함수를 프로퍼티 값으로 사용하면 일반적으로 메서드로 통칭
하지만 메서드란 ES6의 메서드 축약 표현만을 의미
함수가 어디에 할당되어 있는지에 따라 메서드인지 판단하는 것이 아니라 함수 정의 방식에 따라 구

  • constructor : 일반 함수(함수 선언문, 표현식으로 정의된 함수)
  • non-constructor : 화살표 함수와 메서드 축약 표현으로 정의된 함수

이유
일반 함수로 호출 시

  • 내부 메서드 [[Call]]이 호출되고
  • new 연산자와 함께 생성자 함수로서 호출되면 내부 메서드 [[Construct]]가 호출된다
    이외 함수
  • 내부 메서드 [[Construct]]를 갖지 않는다
  • 이런 함수 객체를 생성자 함수로서 호출하면 에러 발생
function foo(){} 
//일반 함수로서 호출 
//[[Call]]이 호출된다. 모든 함수 객체는 [[Call]]이 구현되어 있다 

foo(); 

//생성자 함수로서 호출 
//[[Construct]]가 호출된다. 이때 이걸 갖지 않으면 에러 발생 
new foo(); 

6. new 연산자

  • 일반 함수와 생성자 함수에 형식적 차이 X (생성자 함수는 첫문잘르 대문자로 기술해 구분)
  • new 연산자와 함께 함수를 호출하면 생성자 함수로 동작 : [[Call]]이 아니라 [[Construct]]호출
  • new 연산자 없이 생성자 함수 호출 시 일반 함수로 호출 : [[Construct]]가 호출되는 것이 아니라 [[Call]]이 호출된다
  • 단, new 연산자와 함께 호출하는 함수는 constructor(선언문, 표현식)이어야 한다
//생성자 함수로서 정의하지 않은 일반 함수
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"}
function Circle(radius){ 
this.radius = radius //전역 객체의 프로퍼티
this.getDiameter = function(){ //전역 객체의 메서드
return 2*this.radius
}
} 

const circle = Circle(5)//일반 함수로 호출
console.log(circle) //undefined 
console.log(radius)//5, 일반 함수 내부의 this는 전역 객체 window를 가리킨다 
console.log(getDiameter())//100 

circle.getDiameter(); 
//TypeError: Cannot read propery 'getDiameter' of undefined

7. new.target

  • 생성자 함수가 new 연산자 없이 호출되는 것을 방지하기 위해 파스칼 케이스 컨벤션을 사용한다 하더라도 실수는 언제나 발생할 수 있다

  • 이를 방지하기 위해 ES6에서 new.target 지원

  • constructor인 모든 함수 내부에서 암묵적인 지역변수와 같이 사용된다

  • 메타 프로퍼티라고 부른다

  • IE는 지원하지 않는다

  • 함수 내부에서 new.target을 사용하면 new 연산자와 함께 생성자 함수로서 호출되었는지 확인 가능

  • new 연산자와 함께 생성자 함수로서 호출되면 함수 내부의 new.target은 함수 자신을 가리킨다

  • new 연산자 없이 일반 함수로서 호출된 함수 내부의 new.target은 undefined

  • 따라서 함수 내부에서 new.target을 사용해 생성자 함수로 호출했는지 확인

  • 재귀호출을 통해 생성자 함수로서 호출 가능

  • 대부분의 빌트인 생성자 함수는 new 연잔사와 함께 호출되었는지 확인 후 적절한 값 반환

  • String, Number, Boolean 생성자 함수는 new 연산자와 함께 호출했을 때 객체 생성 반환, 없이 호출하면 문자열, 숫자, 불리언 값을 반환. 이를 통해 데이터 타입 변환 가능

functino Circle(radius){
if(!new.target){ 
return new Circle(raduis) 
}
this.radius = radius 
this.getDiameter = function(){
return 2 * this.radius
}
}

const circle = Circle(5)//new를 사용하지 않아도 new.target 통해 생성자 함수로서 호출 
console.log(circle.getDiameter())
profile
노션 : https://garrulous-gander-3f2.notion.site/c488d337791c4c4cb6d93cb9fcc26f17

0개의 댓글