function Person(){} vs var person = Person() vs var person = new Person()?

young_pallete·2021년 6월 26일
1

JavaScript

목록 보기
3/5

1. 시작하며 👏

최근에 면접도 슬슬 준비해야겠다! 싶어서, 여러 글들을 보다가 알듯 말듯한 그런 질문을 하나 갖고 왔습니다 :)
이왕이면, 최대한 제대로 차이를 설명할 수 있도록 하는 게 기본 마음가짐 아니겠어요 😊

제가 들고온 문제는, 제목과 같이

function Person(){}

var Person = Person();

var Person = new Person();

의 차이점이 무엇이냐? 라는 질문이었어요.
처음에는 이걸 왜 물었지? 라는 생각으로 접근했는데, 점차 생각하면서 꽤나 괜찮은 질문이라고 생각하여 정리해본 글입니다.

항상 붙이는 말이지만,

초보의 글입니다. 언제든지 틀린 부분이 있다면 비판의 글 남겨주시면, 감사히 배우겠습니다!


2. 요약

  • function Person(){} 은 함수 선언문으로서 실행을 하지 않습니다.
  • var person = Person() 은 실행을 하여 값을 person 변수에 할당합니다.
  • var person = new Person()은 생성자 함수로서 특정 인스턴스를 갖는 객체를 만들어내고, personPerson.prototype을 상속하게 합니다.

3. 문제의 요점

결국 핵심은 생성자 함수를 이해하는 지에 대해 묻는 듯합니다.
(이는 Person의 네이밍 컨벤션에서 유추했어요. 파스칼 표기법으로 했으니, 냄새가 나지요?)

거시적으로 크게 살펴보면

  1. function Person(){}은 함수 선언문
  2. var person = Person()은 (일반)함수를 호출하고 값을 변수에 할당하는 statement,
  3. var person = new Person()new 연산자를 통해 생성된 인스턴스를 person에 할당

이라고 할 수 있겠습니다.

만약 비교를 한다면 크게 2가지로, 1번 vs 2번 와 2번 vs 3번으로 할 수 있겠어요!


3-1. function Person(){} vs var person = Person()

쉽게 1번, 2번으로 칭하겠습니다.

1번은 함수 선언문이라, 따라서 런타임 이전에 선언이 실행되며, 호이스팅이 됩니다.

이때, 함수 선언문의 경우 호출이 되지 않는다면 실행이 되지 않아요.

2번은 함수를 호출하고, 그 값을 변수에 할당하는 문입니다. 이는 1번과 달리 함수를 실행하면서, 그 반환된 결과 값을 변수에 할당합니다.

다만 여기서 Person()은 선언이 되지 않았기 때문에, 해당 코드에 Person 함수가 선언이 되어있음이 전제되어야 해요!!


3-2. var person = Person() vs var person = new Person()

이것 역시 2번, 3번으로 칭하겠습니다!

키포인트는 new로 인해 어떤 것이 차이가 발생하느냐입니다.

일단 new가 앞에 붙었을 경우, 일반함수에서의 동작 방식에서 미묘한 차이가 발생하게 되는데요. 다음과 같습니다.

function User(username) {
	this.username = username;

}

const tester = User('Jaeyoung');
console.log(tester);

만약 여기서 2번이었다면,

  1. this는 현재 일반함수이기에 참조할 객체가 없습니다.
    따라서 값이 undefined가 되는데, undefined인 경우 비엄격 모드에서는 window객체를 가리킵니다.
  2. 따라서 window의 프로퍼티에 username이 등록됩니다.
  3. 결과적으로 return값은 존재하지 않는 함수선언문이었기에, tester에는 값이 할당되지 않게 됩니다.
  4. undefined가 출력됩니다.

하지만 3번이었다면 다음과 같은 차이가 발생하게 됩니다!


function User(username) {
	// this = {}
	this.username = username;
	// return this; 
}

const tester = new User('Jaeyoung');
console.log(tester);
  1. 먼저 함수 실행 시 어떤 빈 객체를 만듭니다. 그리고 this가 해당 빈 객체를 참조하게 합니다. (신기하지 않나요😊)
  2. this가 가리키는 객체에 username인스턴스를 생성합니다.
  3. 암묵적으로 해당 객체를 반환합니다.
  4. 결과적으로 User에서 생성된 객체가 tester의 값으로 할당됩니다.
  5. User { username: "Jaeyoung" }을 출력합니다.

따라서 1. 함수 실행 방식에서 차이를 지니게 되는 거죠.

💡 그렇다면 만약 객체를 반환하는 함수였다면?

function User(username) {
	return { username }
}

const tester = User('Jaeyoung');
console.log(tester);

//{username: "Jaeyoung"}

이 역시 결국에는 username을 프로퍼티로 갖는 객체를 생성하게 됩니다!

그럼 뭐야?!

라고 생각할 수 있어요. 그런데 잘 보면, 미묘한 출력에서의 차이가 있습니다.

생성자 함수로 출력할 시 User { username: "Jaeyoung" } 이며
일반 함수로 출력할 시 { username: "Jaeyoung" }인 거죠.

도대체 이 두 차이는, 무엇일까요? (인스턴스와 객체... 속닥속닥)

객체 리터럴에 의해 생성된 객체 vs 생성자 함수에 의해 생성된 객체

단도직입적으로, 핵심은 프로토타입 결정 과정입니다.

  • 객체 리터럴에 의해 생성된 객체

    모든 객체는 생성할 때, OrdinaryObjectCreate라는 요상한 추상 연산을 호출합니다. (저는 이걸 몰랐어요!!)
    그리고 이 함수는 자신이 생성할 객체의 프로토타입을 인수로 전달 받아요.

    그리고 객체 리터럴이 평가될 때에는, 해당 연산은 Object.prototype을 프로토타입으로 갖는 빈 객체를 생성하고, 프로퍼티를 추가하도록 정의되어 있다고 합니다.

    결과적으로 객체 리터럴에 의해 생성된 객체의 프로토타입은 Object.prototype이며, Object.prototype을 상속받는 것이지요!

  • 생성자 함수에 의해 생성된 객체

    이 역시 OrdinaryObjectCreate라는 요상한 추상 연산을 호출하는데...

    여기서 특이점이 발생합니다.

    기존에는 OrdinaryObjectCreate 연산에 전달되는 프로토타입이 Object.prototype에 바인딩된 빈 객체였다면,
    여기서는 생성자 함수의 prototype 프로퍼티에 바인딩된 객체를 프로토타입으로 전달한다고 합니다!

    결과적으로 생성자 함수의 prototype 프로퍼티에 바인딩 된 객체를 프로토타입으로 갖기 때문에 Person.prototype을 프로토타입으로 갖게 되는 것입니다!

    그리고 우리는 이렇게 생성된 객체를, 생성자 함수의 인스턴스라고 부르게 되는 거죠 😘


4. 여담 💬

주의할 점 1: 생성자 함수로 사용할 시 return 주의!

웬만하면 생성자 함수로 쓰겠다고 마음을 먹는다면, 리턴을 쓰지 않는 것이 좋아요. 어차피 암묵적으로 this로 바인딩된 객체를 리턴해버리거든요.


function User(username) {
	this.username = username;
	return { username }
}

const tester =  new User('jengyoung');
console.log(tester) // { username: 'jengyoung' }

여기서 리턴을 객체로 하였습니다.

이때, 리턴할 객체가 Object 타입이라면 제대로 생성자 함수가 생성되지 않는다니 참고합시다!

(참고로, 원시값이라면 리턴값을 무시하고 this를 반환한다고 해요.)


5. 기타 TIP

반드시 생성자 함수로 실행해야 할 함수가 있다면 이럴 때에는 new.target을 활용할 수 있습니다.

new.target은 생성자 함수로 호출되면 함수 자신을, 호출되지 않을 시 undefined를 반환합니다.

function User(username) {
	if (!new.target) {
		return new User(username);
	}
	this.username = username;
}

const tester =  new User('jengyoung');
console.log(tester) // User { username: 'jengyoung' }

6. 참고자료

모던 자바스크립트 Deep Dive chap.19 프로토타입

https://ko.javascript.info/constructor-new

profile
People are scared of falling to the bottom but born from there. What they've lost is nth. 😉

0개의 댓글