함수의 암시적 파라미터 - this #2

암시적 파라미터인 this에 대해서 알아봅니다.


함수의 암시적 파라미터는 4편으로 이루어져 있습니다.

  1. arguments
  2. this-1
  3. this-2
  4. this-3

들어가며

이번 포스팅에서는 이전장에 이어서 생성자를 통한 호출을 알아보겠습니다.

  1. 함수로서 호출
  2. 메서드로서 호출
  3. 생성자로서 호출
  4. call 및 apply를 통한 호출

생성자 함수

일반적으로 생성자 함수는 일반 함수와 기술적인 차이는 없고 아래 두 관례를 따릅니다.

1. 함수 이름의 첫 글자는 대문자로 시작합니다.

2. 반드시 ‘new’ 연산자를 붙여 실행합니다.

은영 선임님이 9월 포스팅에서 생성자 함수에 대해서 설명해 주셨는데요.

제가 더 살펴볼 점은 new 연산자를 이용하여 생성자 함수를 호출할 때의 this가 무엇을 반환할까? 입니다.

function MyFunction() {
	this.skulk = function() {
  	return this
  }
}

const my = new MyFunction()

const my2 = MyFunction()

// 첫번째 질문
console.log("my1", my.skulk() === my) 
// 두번째 질문
console.log("my2", my2.skulk() === my2) 

첫 번째 질문, 두 번째 질문에서 어떤 결과가 출력 될까요?

jsfiddle에서 확인해주세요

new 연산자의 동작

new 연산자를 사용하면 아래와 같은 알고리즘이 동작합니다.

  1. 빈 객체를 만들어 this에 할당합니다.
  2. 함수 본문을 실행합니다. this에 새로운 프로퍼티를 추가해 this를 수정합니다.
  3. this를 반환합니다.
function User(name) {
  // this = {};  (빈 객체가 암시적으로 만들어짐)

  // 새로운 프로퍼티를 this에 추가함
  this.name = name;
  this.isAdmin = false;

  // return this;  (this가 암시적으로 반환됨)
}

이제 첫 번째 질문에 대한 답을 확인할 수 있습니다.

this에 할당된 새롭게 생성된 객체는 new 연산자의 값으로 반환되고 skulk는 this 자기 자신을 반환하기 때문에 true가 됩니다.

두 번째 질문은 my2를 생성할 때 new 연산자를 사용하지 않았습니다. 함수로서 호출되었기 때문에 이전 장에 설명처럼 this는 전역 객체 window가 됩니다.

console.log("my2", my2.skulk() === my2) // Cannot read properties of undefined (reading 'skulk')

console.log("my2", window.skulk() === window) // true

생성자 함수에서 자체 값을 반환하면 어떻게 될까?

위 new 연산자의 동작에서 this에 빈 객체가 암시적으로 만들어지고 암시적으로 반환된다고 했습니다.

여기서 다른 값을 자체적으로 반환하면 어떻게 될까요?

값을 반환

function MyFunction() {
	this.skulk = function() {
  	return true
  }
  return 1
}

const my = new MyFunction()

console.log(MyFunction() === 1) // ?

console.log(my.skulk() === true) // ?

결과는 jsfiddle에서 확인해주세요!

결과를 실행해 보면 MyFunction 함수가 1을 반환한다는 사실은 코드 동작 방식에 큰 영향을 미치지 않습니다.

MyFunction을 함수로 호출하면 1을 리턴합니다.

new 연산자를 이용하여 생성자로 호출하여도 객체가 생성되고 skulk 메서드는 올바르게 반환됩니다.

객체를 반환

const obj = {
  skulk: 1
}

function MyFunction2() {
  this.skulk = 2
  return obj
}

const my2 = new MyFunction2()

console.log(my2 === obj)
console.log(my2.skulk === 1)

결과는 jsfiddle에서 확인해주세요!

객체를 반환할 때는 값을 반환할 때와는 좀 다릅니다.

this에 빈 객체가 암시적으로 만들어지고 반환되어야 하는데 obj 객체가 반환되었습니다.

이 경우에는 생성자가 객체를 반환하면 해당 객체는 전체 new 연산자의 생성자 함수로 반환되고 this에 할당된 빈 객체는 버려집니다.

규칙

위에서 보셨다시피 객체를 반환할 때는 this에 할당된 객체는 무시됩니다.

생성자로 호출되는 함수는 일반적으로 일반 함수와 다르게 코딩되고 사용되며, 생성자로 호출되지 않는 한 그다지 유용하지 않기 때문에

생성자를 일반 함수 및 메서드와 구별하기 위해 명명 규칙이 생겼습니다.

  1. 함수와 메서드는 일반적으로 하는 일을 설명하는 동사로 시작하고 소문자로 시작하여 이름이 지어집니다.
  2. 생성자는 일반적으로 User, Employee, Person과 같이 대문자로 시작하여 생성되는 객체를 설명하는 명사로 만들게 됩니다.

자바스크립트는 왜 그 모양일까?

자바스크립트는 왜 그 모양일까란 책 1장 이름에서는 이렇게 얘기합니다.

자바스크립트의 모든 이름은 반드시 소문자로 시작해야 합니다. 이는 자바스크립트의 new 연산자 문제 때문입니다. 함수 호출문이 new로 시작하면 해당 함수는 생성자로서 호출되고, 그렇지 않으면 함수로서 호출됩니다. 생성자와 함수의 기능은 상당히 다릅니다. 생성자를 잘못된 방식으로 호출하면 에러가 발생할 수 있습니다. 더 헷갈리는 점은 생성자와 함수는 겉으로는 완전히 똑같아 보인다는 점입니다. 그래서 new를 써야 하는데 쓰지 않은 경우, 혹은 반대로 잘못 사용한 new로 인해 발생하는 문제를 자동으로 감지할 방법이 없습니다. 그래서 한 가지 약속을 했습니다. 모든 생성자 함수의 이름은 대문자로 시작되어야 하며, 그렇지 않은 경우 모든 경우는 소문자로 시작되어야 합니다. 그렇게 해서 에러를 줄일 수 있는 시각적은 표시를 제공합니다.

여기서 저자 더글러스 크락포드(JSON을 만든 분입니다.)가 제시하는 방법 중 하나는 절대 new를 사용하지 말아라 입니다. new를 쓰지 않으면 대문자로 시작하는 이름을 쓸일 도 없다고 합니다.

애초에 헷갈리지 않게 사용하지 않는 편이 좋다.라는 정도로만 짚고 넘어가면 좋을 듯합니다. (이 세상에는 다양한 의견이 존재하고 많이들 싸우지요. 탭 vs 스페이스바처럼)

개인적으로는 Class에서 사용되는 new 문법은 생성자 함수의 new와 좀 다르지 않나 생각되지만 ES6를 사용할 수 없는 환경에서 Class 없이 재사용 객체를 만들려면 직접 만들어도 됩니다.(은영 선임님 예제를 좀 가져왔습니다.)

function user(who, when, what) {
  const _who = who;
  const _when = when;
  const _what = what;
  
  function printAll() {
  	return `${_when} ${_who} ${_what}`;
  }
  
  return Object.freeze({
  	who: _who,
    when: _when,
    what: _what,
  	printAll
  })
}

// 새로운 객체 생성
let user1 = user('woman', 2021, 'studying');
let user2 = user("man", 2020, "hello")

console.log(user1);
console.log(user2);

마치며

이번 포스팅에서는

  1. 생성자로서 호출

에 대해 알아봤습니다.

다음 포스팅에서는 call 및 apply를 통한 호출을 알아보겠습니다.

읽어주셔서 감사합니다.

참고문서