Frontend/Today I Learned

[JS] this

joycie416 2024. 12. 2. 21:42

※ 본 내용은 코어 자바스크립트 (정재남)의 내용을 정리한 글입니다.

What Is This?

 

JS를 공부하다보면 많이들 혼란스러워하는 개념 중 하나로 `this`가 있다. this는 상황에 따라 가리키는 대상이 달라져 매우 혼란스럽기 때문이다. 이에 대해서 정리해보려고 한다.

 

01 상황에 따라 달라지는 this

실행 컨텍스트가 생성될 때 VariableEnvironment와 LexicalEnvironment가 생성되며 하나의 일을 더 한다. 그것이 바로 thisBinding이다. this가 가리키는 대상을 결정해준다.

 

this는 함수를 호출할 때 결정된다. 함수를 어떤 방식으로 호출하냐에 따라 값이 달라진다.

 

01-1 전역 공간에서의 this

전역 공간에서 this는 전역 객체를 가리킨다. 즉 브라우저에서 실행하면 window, Node.js에서 실행하면 global이 된다.

 

아래 코드를 브라우저 콘솔 창에 입력해보자.

console.log(this);
console.log(window);
console.log(this === windlow);

01-2 메서드 내부에서의 this

함수와 메서드의 차이점은 무엇일까? 바로 호출 주체의 유무이다.

 

배열을 예시로 들어보자. 우리가 자주 사용하는 map, filter, reduce 등이 바로 배열의 메서드이다. 아래 코드를 브라우저 콘솔창에서 실행해보자.

var log = function () {
  console.log(this);
}
log();

var obj = {
  name: 'obj',
  func: log
}
obj.func()

 

이처럼 호출한 주체가 명확하면 메서드이다. `obj`가 `func`라는 함수를 호출했으므로 메서드인 것이다. `obj['func']()`라고 해도 마찬가지이다. 4번째 줄에서 log()를 호출한 주체가 명확하지 않다. 그러면 함수가 호출된 것으로, this는 전역 객체이다.

 

01-3 함수로서 호출할 때 그 함수 내부에서의 this

그렇다면 메서드 내부에서 함수를 만들어 호출한다면 그때의 this는 무엇일까? 아래 코드의 (1), (2), (3) 은 각각 어떤 값이 출력될까?

var obj = {
  methodA: function () {
    console.log(this); // (1)

    var innerFunc = function () {
      console.log(this)
    }
    innerFunc(); // (2)

    var innerObj = {
      methodB: innerFunc
    }
    innerObj.methodB(); // (3)
  }
}
obj.methodA()

 

정답은 다음과 같다. (1) `obj` (2) 전역 객체 (3) `innerObj`

위에서 말했듯이 함수의 this는 전역 객체이다. 따라서 (2)이 전역 객체가 되고, (1)과 (3)은 각각 `obj`, `innerObj`가 호출한 것으로 호출 주체가 명확하므로 (1)과 (3)은 각각 `obj`, `innerObj`가 된다.

 

화살표 함수

하지만 `innerFunc`가 `obj` 내부에 정의됐는데, this가 `obj`가 아니라 전역 객체라는 것이 조금 어색하다. 이런 문제를 보완하고자 ES6에서 this를 바인딩하지 않는 화살표 함수를 도입했다. 위 코드를 아래와 같이 수정해보자.

var obj = {
  methodA: function () {
    console.log(this); // (1)

    var innerFunc = () => {
      console.log(this)
    }
    innerFunc(); // (2)

    var innerObj = {
      methodB: innerFunc
    }
    innerObj.methodB(); // (3)
  }
}
obj.methodA()

 

그러면 (1), (2), (3) 모두 `obj`를 가리킨다. (1)은 앞서 말한대로 `methodA`의 호출 주체가 `obj`이기 때문이다. (2)은 방금 언급했듯이, 화살표 함수는 this를 별로도 바인딩하지 않아 상위 this(`obj`)를 출력한다. 이러한 화살표 함수를 methodB에 할당했으므로 methodB의 호출 주체는 `innerObj`이지만, 할당 시점에서의 this를 출력하므로 (3)도 `obj`가 된다.

 

01-4 콜백 함수 내부에서의 this

결론부터 말하자면, 콜백 함수도 함수이다. 즉 콜백 함수 내부에서의 this도 전역 객체이다. 하지만 별도로 this를 바인딩한 경우 그 대상을 참조한다.

var array = [1,2,3,4,5];
array.map(function (ele) {
  console.log(ele, this)
})

 

위 코드를 실행하면 this가 전역객체임을 알 수 있다.

 

HTML 파일을 만들어 아래 코드를 실행해보자.

document.body.innerHTML += '<button id="a">버튼</button>';
document.body.querySelector('#a').addEventListener('click', function () {
  console.log(this)
})

 

이 경우에는 직접 추가해준 버튼이 출력된다. addEventListner 메서드는 지정한 HTML 요소를 콜백 함수의 this에 자신의 this를 상속하도록 정의되어 있기 때문이다.

 

01-5 생성자 함수 내부에서의 this

생성자 함수는 어떤 공통된 성질을 지니는 객체들을 생성하는 데 사용하는 함수이다. `new` 명령어와 함께 함수를 호출하면 생성자로서 동작하게 되며, 생성자 함수 내부에서 this는 자기 자신이다. (클래스와 prototype에 대한 내용은 나중에 다루겠다.)

var Dog = function (name, owner) {
  this.name = name;
  this.owner = owner;
}

var Mike = new Dog ('Mike', 'Sam');

console.log(Mike) // Dog { name: 'Mike', owner: 'Sam' }

 

`Dog`이라는 변수에 익명함수를 할당한 후 this에 name과 owner라는 속성을 추가했다. `new`와 함께 `Dog`함수를 호출해 `Mike`라는 변수에 할당했다. 출력해보니 name과 owner 속성을 출력해준다. 이 것을 통해 `new Dog` 내부의 this는 자기 자신임을 알 수 있다.