항상 일반 함수와 화살표 함수의 정확한 차이점을 지나쳐 왔습니다. 단순하게 화살표 함수(Arrow function)는 ES6에 새로 나온 문법인 것으로만 인식한 상태로 개발했지만 정확히 알고 적절하게 활용한다면 더욱 깔끔하고 디버깅이 편한 코드를 작성할 수 있을 것 같아서 따로 정리하게 되었습니다.


Q . function 과 () => {} (화살표 함수)의 차이점이 무엇일까요?

A : "바인딩되는 this가 다릅니다."

틀린 답변은 아니지만, 정확히 어떻게 this가 다를까요? 말 뜻대로 생각했을 때, 아래 코드처럼 생각할지도 모르겠습니다.

// 서로 다른 값이 출력될 것?
function normalFunc() {
  console.log(this); 
}

const arrowFunc = () => {
  console.log(this);
}

"function은 this 를 가지니까 자기 자신 즉 normalFunc 을 가리키고, 화살표 함수는 this 를 안 가지니까 Window 함수를 가지겠다." 라고 생각할 수 있지만, 둘 다 Window 객체를 출력하는 것을 알 수 있습니다. 이 개념을 이해하려면 실행 컨텍스트 라는 개념을 먼저 알아야겠습니다.

 

실행 컨텍스트(Execution Context)

실행 컨텍스트란 자바스크립트 코드가 실행되고 연산되는 범위를 나타내는 추상적인 개념으로 코드가 실행된다면 실행 컨텍스트 내부에서 작동하고 있는 것이라고 보면 됩니다. 실행 컨텍스트는 크게 3가지로 분류되는 데, 이 게시글에서는 2가지만 간단하게 알아보겠습니다. (자세한 내용은 이곳)

Global Execution Context

기본적으로 코드가 실행되는 영역이며, 여기서 글로벌 객체인 Window 객체를 생성하며, this 를 글로벌 객체(Window 객체)로 설정해줍니다. 그래서 위에서 this 를 찍었을 때, Window 객체가 출력되었습니다.

Functional Execution Context

각각의 함수가 가지는 실행 컨텍스트로, 함수가 호출될 때 해당 실행 컨텍스트 생성이 됩니다.

 

this가 바뀌는 시점

this는 기본적으로 Window 객체를 말합니다(Node.js는 Global 객체). 하지만 this 의 값이 바뀌는 경우가 있습니다. 때문에 this 값이 어떻게 바뀌고 있는지, 해당 this 가 무슨 값인지 알기 위해선 코드의 문맥을 살펴볼 필요가 있습니다.

객체의 메소드를 호출하는 경우

const myObj = {
  myValue: 6,
  myWindow: this,
  myFunc: function() {
    return this;
  },
  myArrow: () => {
    return this;
  },
};

console.log(myObj.myFunc()); // myObj 객체
console.log(myObj.myArrow()); // Window 객체

출력되는 this의 값이 바뀌었습니다. 이에 관해 MDN에는 아래와 같이 설명하고 있습니다.

함수를 어떤 객체의 메소드로 호출하면 this의 값은 그 객체를 사용합니다.
ES2015는 스스로의 this 바인딩을 제공하지 않는 화살표 함수를 추가했습니다.
-MDN web docs-

이것이 아까 위에서 나왔던 질문의 정확한 답변입니다. 일반 함수는 해당 객체를 바인딩하여 this의 값을 변경하는 것이고, 화살표 함수는 상위 객체의 this를 그대로 이어받아 사용하는 것입니다.

생성자를 통해 객체를 생성하는 경우

function Programmer(name, age) {
  this.name = name;
  this.age = age;
}

// 생성자를 사용하지 않은 경우 - 단순 호출
const covy = Programmer('Covy', 20); // covy 변수는 undefined
console.log(window.name, window.age); // Covy 20

// 생성자를 통한 객체 생성
const tony = new Programmer('Tony', 25);
console.log(tony.name, tony.age); // Tony 25 - this가 해당 객체에 바인딩됨

new 키워드를 통해 객체를 생성하면, this 가 해당 객체에 바인딩되어 해당 값을 읽게 되는 것입니다. ES6에 추가된 class를 사용하면 동일하게 작동합니다.

예외

Jquery, React 등에 일부 라이브러리에서 엘리먼트에 이벤트를 추가할 때, 콜백 함수에서 this 를 사용하면 값이 바뀌는 경우가 있습니다.

// Jquery
$('div').on('click', function() {
  console.log(this); // <div>
  function normalFunc() {
    console.log(this); // Window 객체
  }
  const arrowFunc = () => {
    console.log(this); // <div>
  }
});

위 코드처럼 라이브러리 상에서 this를 바인딩해주는 경우가 있기도 하니 this를 사용할 때는 해당 라이브러리의 document를 찾아보는 것도 중요할 것입니다. (아니면 stack overflow)

 

apply(), call(), bind()

사실 this는 이 3개를 이해하기 위한 발판입니다. 자바스크립트에서는 this 의 바인딩을 바꿀 수 있도록 해주는 메소드를 제공하는 데, 바로 apply , call , bind 입니다.

const Tom = {
  age: 25,
  gender: 'man',
};

function printProfile(name) {
  console.log(name, this.age, this.gender);
}

printProfile.apply(Tom, ['Tom']); // Tom 25 man
printProfile.call(Tom, 'Tom'); // Tom 25 man
printProfile.bind(Tom, 'Tom').call(); // Tom 25 man

apply : call 함수와 유사하지만, 매개변수는 배열로 받는 것에 있어 차이가 있다.
call : 객채를 바인딩 함과 동시에 호출을 한다.
bind : 바인딩이된 함수를 반환하며, 한번더 호출시 함수를 실행한다.