standard function + arrow function + this

GI JUNG·2022년 10월 5일
2

javascript

목록 보기
2/12
post-thumbnail

this binding error를 공부하면서 this에 대한 이해가 정확히 안 된 것을 깨달아 this에 관해 완전히 파헤치고자 한다.
자바스크립트는 함수가 호출될 때 기본적으로 arguementsthis인자를 넘겨받는다. this에 관련하여 일반함수에서 this가 binding되는 것을 이해하는 것에 엄청 어려움을 느꼈는데 함수가 호출되는 방식(how)에 따라 this가 binding되는 객체가 달라진다. 즉, 이는 동적 바인딩(dynamic binding)에 해당된다고 볼 수 있으며 arrow function의 경우에는 정적 바인딩(static binding)이라 할 수 있다.
그리고 this는 기본으로 window 또는 global객체를 가리킨다.

🍀 일반함수에서의 this

함수가 호출되는 방식을 나는 아래와 같이 정리했다.

  1. 함수 호출
  2. 메서드 호출
  3. 생성자함수 호출
  4. strict mode에서 호출
  5. 콜백함수 호출
  6. 내부함수 호출
  7. 이벤트 호출

1. 함수 호출

standard function

function printThis(){
  console.log(this);  
}

printThis()   // window or global

환경

  • browser: this -> window
  • nodejs: this -> global

📒 그냥 함수 호출시에는 this는 전역객체인 window 또는 global을 가리키게된다.

arrow function

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

printThis()   // window or global

arrow function은 standard function과 달리 변수를 lexical scope에서 찾기 때문에 변수가 정의된 context에서 this를 찾아서 binding한다. 이해가 잘 안 갈 수 있는데, 한 마디로 말하면 arrow function에서 this는 정의된 곳에서부터 상위 scope를 따라서 this를 찾는다. 따라서, 이부분에서 this가 binding되는 것은 window or global이다. 이는 위의 일반 함수에서 this가 binding되는 개념과는 다르다는 것에 유의하자.

여기서 lexical scope에 대해서 추가적으로 설명하자면 lexical scope와 반대되는 개념인 dynamic scope도 존재한다. 이에 관한 내용은 lexical scope에 link를 걸어두었다.

📒 lexical scope: no matter a function or variable is invoked from, or even how is invoked, its lexical scope is only defined by where they delcared
lexical scope는 함수나 변수가 어디에서 또는 어떻게 호출되었는지와 상관없이 변수 또는 함수가 선언된 부분에서 정의된다.

reference here

2. 메서드 호출

standard function

const obj = {
  name: 'someone',
  printThis(){
    console.log(this)
  }
}

obj.printThis();   // obj

this는 객체를 통한 호출로 obj객체와 binding된다.

📒 메서드 호출은 자신을 감싸고 있는 객체에 binding된다.

arrow function

const obj = {
  name: "someone",
  printThis: () => console.log(this),
};

obj.printThis();   // window or global

this를 lexical scope에서 찾기 때문에 this는 window or global과 binding된다.
여기서 obj가 this를 감싸는 상위 scope라고 할 수 있지만, obj는 객체로서 scope에 해당되지 않는다.
이 부분에서 처음 공부할 때 obj는 scope에 해당하므로 this는 obj와 binding되어야 하는거 아닌가라고 생각하여 애먹었다🤣

😅 obj가 상위 스코프에 해당한다고 생각하여 this는 obj와 binding되는 거 아닌가?????
더 공부하고 정리하기

📒 arrow function에서 this는 예외없이 정의된 곳에서부터 상위 scope로 this를 찾기 때문에 이 경우에도 window 또는 global객체를 가리키게 된다.

3. 생성자함수 호출

standard function

function Person() {
  this.name = "someone";
  this.printThis = function () {
    console.log(this);
  };
}

const person = new Person();
person.printThis();   // Person

this는 객체를 통한 호출로 person객체와 binding된다.

📒 생성자 호출은 new를 통해 호출되는 순간 instance와 binding되게 된다.

arrow function

function Person() {
  this.name = "someone";
  this.printThis = () => {
    console.log(this);
    console.log(this.name);
  };
}

const person = new Person();
person.printThis();

여기서 this가 선언된 곳은 Person 생성자 함수 안이다. Person 생성자 함수는 this의 lexical scope이기 때문에 this는 Person 객체를 가리키게 된다.

📒 Person 객체가 this의 lexical scope에 해당하므로 this는 Person과 binding

4. strict mode에서 호출

standard function

"use strict";
function printThis() {
  console.log(this);
}

printThis();   // undefined

strict mode에서 this는 execution context 도중에 this에 관련된 선언이 없으면 undefined를 return한다.

🤔 execution context: 자바스크립트 엔진은 자바스크립트 코드를 실행하고 변형시키기 위해 특별한 환경을 만드는데 이러한 환경을 execution context라 한다. 즉, 자바스크립트를 실행하기 위해 필요한 모든 정보들을 담고 있는 객체라고 생각하면 된다.
reference 1. execution context from githubio 2.execution context from freecodecamp

arrow function

"use strict";
const printThis = () => console.log(this)

printThis();   // window or global

strict mode에서 arrow function의 this는 window와 global을 가리킨다.

5. 콜백함수 호출

standard function

function Clock() {
  this.value = 0;

  setInterval(function () {
    this.value++;
    console.log(this.value);
    console.log(this);   // window or global
  }, 1000);
}

const clock = new Clock();

setInterval에 들어가는 인자인 익명함수는 실행될 때 global context(전역 환경)에서 실행된다.
따라서, this는 window 또는 global에 binding된다.

📒 콜백 함수 호출로 인한 this는 global 또는 window객체와 binding된다.

arrow function

function Clock() {
  this.value = 0;

  setInterval(() => {
    this.value++;
    console.log(this);
    console.log(this.value);
  }, 1000);
}

const clock = new Clock();

콜백함수인 익명 화살표 함수는 global context에서 실행되지만 arrow function일 때 this는 어떻게 호출되었던 상관없이 lexical binding을 함으로 this는 Clock과 binding된다.

6. 내부함수 호출

standard function

function foo() {
  function printThis() {
    console.log(this);
  }

  printThis();
}

foo();   // window

this는 객체의 프로퍼티나 메서드를 참조하기 위한 자기 참조 변수이므로 객체를 생성하지 않는 바깥함수(foo)는 일반함수로서 this는 의미가 없다.

📒 바깥 함수가 일반함수며 내부함수 호출은 어디에서 선언되던 this는 window와 binding된다.

arrow function

function foo() {
  const printThis = () => console.log(this);
  printThis();
}

foo();

위에 arrow function에서 this는 lexical scope에서 찾는다고 언급했다. foo함수가 lexical scope에 해당하지만 이 또한 객체를 생성하지 않는 바깥함수인 foo는 this와는 연관이 없다. 따라서 이 경우, this는 window 또는 global과 binding이 된다.

객체를 생성하지 않는 일반함수 안의 내부 함수에 있는 this는 전역 객체와 바인딩된다.

7. 이벤트 호출

const button = document.querySelector("#temp-button");

window.addEventListener("click", function (event) {
  console.log("window", this);
  console.log("window currentTaget", this);
});

button.addEventListener("click", function (event) {
  console.log(this);
  console.log("currentTaget", this);
});

이벤트에서 this가 동작하는 방법은 상당히 신기하다. 나는 eventhandler가 callback function으로서 위에서 공부한대로 추측해보면 this의 값은 window가 나와야한다. 하지만, event 함수에서의 this는 event.currentTarget과 일치한다.

📒 이벤트 함수 호출은 this가 event.currentTarget와 같다.


정리

this에 관해 공부하면서
메서드 호출이 아닌 내부함수던 콜백함수던 일반함수로 호출되면 this는 무조건 전역 객체를 가리키게 된다.


참고

dev site

javascript deep dive

coding apple video


profile
step by step

0개의 댓글