study: javascript | 숨참고 deep dive (17) 생성자 함수에 의한 객체 생성

Lumpen·2023년 2월 22일
0

Study

목록 보기
19/92

Object 생성자 함수

new 연산자와 함께 생성자 함수를 호출하면 빈 객체를 생성하여 반환한다
생성된 객체에 프로퍼티 또는 메서드를 추가하여 객체를 완성할 수 있다

생성자 함수란 new 연산자와 호출 시 인스턴스(객체)를 생성하는 함수를 말한다
자바스크립트 Obejct 생성자 함수 외에도 빌트인 생성자 함수를 제공한다

반드시 Object 생성자 함수를 사용해야하는 것은 아니다
객체는 리터럴로 사용하는 편이 더 간편하다
그다지 유용한 방법은 아님

생성자 함수

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

객체 리터럴로 객체를 생성하는 방식은 직관적이고 간편하지만
단 하나의 객체만을 생성할 수 있다
동일한 객체를 여러개 생성해야할 경우 비효율적이다

객체는 프로퍼티를 통해 객체 고유의 상태를,
메서드를 통해 프로퍼티 참조와 동작을 표현한다
프로퍼티는 달라도 동작은 비슷할 수 있다
객체 리터럴의 경우에는 같은 동작을 하는 객체를 생성한다면
매번 새로 객체를 정의해야만 한다

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

생성자 함수로 인스턴스를 생성하는 방식은
객체를 클래스(탬플릿) 처럼 사용하여 구조가 동일한 여러 객체를
한 번에 정의할 수 있다

객체 자신 혹은 자신의 프로퍼티나 메서드를 참조하기 위한 참조변수 this는
자바스크립트에서 함수 호출 방식에 따라 동적으로 결정된다

- 일반 함수로서 호출 시: 전역 객체에 binding
- 메서드로서 호출: 메서드를 호출한 객체 binding
- 생성자 함수로서 호출: 생성자 함수가 생성할 인스턴스에 binding

생성자 함수는 이름대로 인스턴스를 생성하는 함수
하지만 자바와 같이 클래스 기반 언어와는 다르게 동작한다
일반 함수에 new 연산자와 함께 호출 시에도 해당 함수는 생성자 함수로 동작한다
new 연산자 없이 호출 시 일반 함수로 동작한다

따라서 일반 함수로 호출 시 this 는 전역 객체에 binding 된다

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

생성자 함수의 몸체에서 수행해야 하는 것은
클래스(탬플릿) 처럼 동작하여 인스턴스를 생성하는 것과
생성된 인스턴스를 초기화다 (프로퍼티 추가 및 초기값 할당)
인스턴스를 생성하는 것은 필수, 초기화 하는 것은 옵션

function Circle(radius) {
 	console.log(this) // Circle {}
	this.radius = radius;
  	this.getDiameter = function () {
    	return 2 * this.radius;
    };
}

const circle1 = new Circle(5); 

생성자 함수 내부에서 this 에 프로퍼티를 추가하고
전달될 인수를 프로퍼티 초기값으로 할당하여 인스턴스를 초기화 한다
하지만 인스턴스를 생성하고 반환하는 코드는 작성하지 않았다

new 연산자와 함께 생성자 함수를 호출하면
자바스크립트 엔진은 암묵적으로 인스턴스를 생성 후 반환한다

다음은 생성 과정

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

암묵적으로 빈 객체가 생성된다
빈 객체는 생성자 함수의 인스턴스로서 사용된다
인스턴스는 this 에 바인딩 된다
생성자 함수 내부 this 는 위 과정을 통해 인스턴스를 가리키게 된다
이 처리는 런타임 이전에 실행된다

바인딩이란 식별자와 값을 연결하는 과정으로
this 는 키워드로 분류되지만 식별자의 역할을 하는 것이다

2. 인스턴스 초기화

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

3. 인스턴스 반환

생성자 내부의 모든 처리가 끝나면 완성된 인스턴스가 바인딩된 this 가 암묵적으로 반환된다
만약 명시적으로 다른 객체를 반환하는 반환문을 작성하면
this 가 반환되지 못하여 생성자 함수로의 기능을 상실한다
하지만 원시 값을 반환한다면 반환이 무시되고 암묵적으로 this 가 반환된다
생성자 함수에서의 return 문은 생략해야 한다

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

함수 선언문 또는 표현식으로 정의된 함수는 일반적인 함수로서 호출할 수 있는 것은 물론
생성자 함수로서 호출할 수 있다
생성자 함수로서 호출한다는 것은 new 연산자와 함께 사용하여 객체를 생성하는 것이다
함수는 객체이므로 일반 객체와 동일하게 동작할 수 있다
함수 객체는 일반 객체가 가지고 있는 내부 슬롯과 내부 메서드를 모두 가지고 있기 때문이다

// 함수는 객체
function foo() {}

foo.prop = 10;

foo.method = function () {
	console.log(this.prop);
}

foo.method() // 10

함수는 객체이지만 일반 객체와는 다르다
함수는 호출할 수 있는 객체다
일반 객체의 내부 슬롯과 내부 메서드와 함께 함수로서 동작하기 위한
함수 객체만의 [[Enviroment]]와 [[FormalParameters]] 등의 내부 슬롯과
[[Call]], [[Construct]] 와 같은 내부 메서드를 가지고 있다

함수가 일반 함수로서 호출되면 내부 메서드 [[Call]] 이 호출되고
new 연산자와 함께 호출되면 [[Constructor]] 가 호출된다

내부 메서드 [[Call]] 을 갖는 함수 객체를 callable 이라고 하며
내부 메서드 [[Constructor]] 를 갖는 함수 객체를 constructor 라고 한다
[[Constructor]] 를 가지지 않는 함수 객체를 non-constructor 라고 한다
callable 은 호출할 수 있는 객체로 함수를 뜻하며
constructor 는 생성자 함수로서 호출할 수 있는 함수
non-constructor 는 생성자 함수로서 호출할 수 없는 함수를 의미한다

함수 객체는 callable 이어야 한다
모든 함수 객체는 내부메서드로 [[Call]] 을 가지므로 호출할 수 있다
하지만 모든 객체가 constructor 는 아니다
즉 모든 함수를 생성자 함수로서 호출할 수 있는 것은 아니다

constructor 와 non-constructor

자바스크립트 엔진은 함수 정의를 평가하여 constructor 와 non-constructor 를 구분한다

  • constructor: 함수 선언문, 함수 표현식, 객체 (객체도 함수다)
  • non-constructor: 메서드 축약표현(ES6), 화살표 함수

주의해야할 점은 ECMAScript 에서 정의하는 메서드의 범위가
일반적인 의미의 메서드보다 좁다는 것

const baz = {
	x: function () {} // 일반함수로 작성된 메서드는 메서드로 인정하지 않는다
}

new baz.x(); // x {}

const arrow = () => {}

new arrow(); // TypeError: arrow is not a constructor

const obj = {
	x () {} // 축약 표현은 메서드로 인정한다
};

new obj.x() // TypeError: obj.x is not a constructor
주의해야할 것은 일반 함수로 작성된 함수는
new 연산자를 붙여 호출 시 생성자 함수처럼 동작할 수 있다는 것

new 연산자

일반 함수와 생성자 함수에 형식적 차이는 없다
new 연산자와 함께 호출 시 생성자 함수로 동작한다
[[Constructor]] 가 호출되는 것으로 constructor 여야 한다

function add(x ,y) {
	return x + y;
}

let inst = new add();
console.log(inst); // {}  - 생성자 함수로 호출되어 
					// 원시값 반환은 무시되고 빈객체를 생성하여 반환

반대로 new 연산자 없이 생성자 함수를 호출해도 [[Call]] 이 호출되어
일반 함수처럼 동작한다

function Circle(radius) {
	this.radius = radius;
  	this.getDiameter = function () {
    	return 2 * this.radius;
    }
}

const circle = Circle(5); // undefined

// 일반 함수의 this 는 전역 객체에 바인딩 된다
console.log(radius) // 5 - 전역 객체에 바인딩 되어 바로 사용하게 된다

circle.getDiameter(); // TypeError: Connot read property

new.target

생성자 함수가 new 연산자 없이 호출되는 것을 방지하기 위해
파스칼 케이스 컨벤션을 사용하여도 실수는 발생할 수 있다
이러한 위험을 피하기 위해 ES6 부터 new.target 을 지원한다

new.target 은 this 와 유사하게 constructor 인 모든 함수 내부에서
암묵적 지역 변수와 같이 사용되며 메타 프로퍼티라고 부른다
함수 내부에서 new.target 을 사용 시
new 연산자와 함께 생성자 함수로 호출되었는지 확인할 수 있다

생성자 함수로 호출되면 new.target 은 함수 자신을 가리킨다
일반 함수로 호출되면 new.target 은 undefined 다

new.target 을 지원하지 않는 IE 의 경우 스코프 세이프 생성자 패턴을 사용할 수 있다

대부분의 빌트인 생성자 함수는 new 연산자와 함께 호출되었는지 확인 후
적절한 값을 반환한다
Object와 Function 생성자 함수는
new 연산자를 생략해도 생성자 함수처럼 동작하도록 되어있음

String, Number, Boolean 생성자 함수는
new 연산자 없이 호출 시 값을 반환하며 데이터 타입을 자신의 타입으로 변환한다

profile
떠돌이 생활을 하는. 실업자는 아니지만, 부랑 생활을 하는

0개의 댓글