this는 언제, 어떻게 결정되는가

유현수·2025년 3월 11일
post-thumbnail

Javascript의 this

대부분의 객체지향 언어에서 this 는 클래스로 생성한 인스턴스 객체를 의미합니다. 하지만 Javascript에서 this 는 어디서든 사용할 수 있죠. 상황에 따라 this 가 바라보는 대상이 달라지기 때문에 정확한 작동 방식을 이해하지 못하면 굉장히 혼란스럽습니다.

오늘은 이 this 가 어떤 상황에서 무엇을 바라보는지 알아봅시다.

사전 지식

이 글에서는 this 아래 두 개념을 이해하고 있다는 것을 전제로 설명을 진행합니다.

  • 실행 컨텍스트
  • 엄격 모드

this는 언제 결정될까?

실행 컨텍스트가 생성될 때 this가 결정된다.

자바스크립트에서 this실행 컨텍스트가 생성될 때 함께 결정됩니다.

실행 컨텍스트가 생성되면 아래와 같은 정보들이 담기게 됩니다. 이 중 ThisBinding에 this에 대한 정보가 담기게 됩니다.

// 현재 컨텍스트 내의 식별자 정보 & 외부 환경 정보
// 지금은 몰라도 됩니다 :D
- VariableEnvironment
- LexicalEnvironment

// this 식별자가 바라봐야 할 대상 객체
- ThisBinding

실행 컨텍스트는 언제 생성되는가

그렇다면 실행 컨텍스트는 언제 생성될까요? 다음 3가지 상황에서 생성됩니다.

  1. 전역 객체가 생성 될 때
  2. 함수가 호출될 때
  3. eval() 함수가 호출될 때

단, eval() 함수는 왠만해서는 사용하지 않으니, 이 글에서는 1, 2번 상황만을 가정하겠습니다.

this는 언제 결정되는가

위의 2가지 내용을 종합하면 this는 전역 객체가 생성될 때 혹은 함수가 실행될 때 결정된다는 것을 알 수 있습니다.

  • 전역 객체가 생성될 때, 함수가 실행될 때 실행 컨텍스트가 생성된다.
  • 실행 컨텍스트에는 ThisBinding 정보가 담긴다.

this는 전역 객체가 생성될 때, 함수가 호출될 때 결정된다.

this가 변경될 때

하지만 여기서 끝이 아닙니다. this가 변할 수도 있기 때문이죠. JS 엔진이 알아서 변경하기도 하고, 개발자가 명시적으로 변경할 수도 있습니다.

JS 엔진이 변경하는 경우 - 엄격 모드가 아닐 때

엄격 모드가 활성화 되어 있지 않다면 this 값이 undefined 혹은 null 일 때, JS 엔진이 autoboxing을 수행하여 this 에 전역 객체를 바인딩합니다.

// 비엄격 모드
function nonStrictFunc() {
  return this;
}

// 함수 단위의 엄격 모드 설정
function strictFunc() {
  "use strict";
  return this;
}

console.log(nonStrictFunc());  // Window or Global
console.log(strictFunc());     // undefined

개발자가 명시적으로 변경하는 경우 - bind(), call(), apply()

개발자가 명시적으로 this를 바인딩하는 방법도 있습니다.

function foo(bar) {
  return [this, bar];
}

const myThis = "my this";
const myParam = "my param";

// func.call(thisArg[, arg1[, arg2[, ...]]])
console.log(foo.call(myThis, myParam));

// func.apply(thisArg, [argsArray])
console.log(foo.apply(myThis, [myParam]));

// func.bind(thisArg[, arg1[, arg2[, ...]]])
const boundFunc = foo.bind(myThis);
console.log(boundFunc(myParam));

// result
// [ [String: 'my this'], 'my param' ]
// [ [String: 'my this'], 'my param' ]
// [ [String: 'my this'], 'my param' ]

요약

위 내용을 종합하면 this 는 다음과 같은 순간에 결정된다는 것을 알 수 있습니다.

  • 전역 공간이 생성될 때
  • 함수를 호출할 때
  • this 값을 변경할 때

이제 this언제 결정되는지 알았으니 this어떻게 결정되는지 각 상황별로 알아보겠습니다.

this는 어떻게 결정될까?

대원칙

💡

this 는 호출 주체에 대한 정보가 담깁니다.

이 대원칙을 바탕으로 this 를 호출하는 상황은 5개로 정리할 수 있습니다. 그리고 이 5가지 규칙은 늘 성립합니다. (this 를 별도로 바인딩 해주지 않는다면)

5가지 규칙

  • 전역공간에서 thiswindow(브라우저 환경) 또는 module.exports(Node.js 환경)를 참조합니다.
  • 어떤 함수를 함수로서 호출한 경우 thisundefined 로 바인딩 됩니다.
  • 어떤 함수를 메서드로 호출한 경우 this 는 메서드 호출 주체(메서드명 앞의 객체)를 참조합니다.
  • 콜백 함수 내부에서의 this는 해당 콜백 함수의 제어권을 넘겨받은 함수가 정의한 바에 따르며, 정의하지 않은 경우에는 undefined를 참조합니다.
  • 생성자 함수에서의 this는 생성될 인스턴스를 참조합니다.

위 5가지 규칙을 하나씩 살펴봅시다.

1. 전역 공간에서의 this

브라우저 환경

브라우저 환경에서 전역 공간의 this는 전역 객체인 window 를 참조합니다.

// 브라우저 환경
console.log(this);             // Window
console.log(window);           // Window
console.log(this === window);  // true

Node.js 환경

Node.js 환경에서 전역 공간의 this는 module.exports 를 참조합니다. this, module.exports, exports 는 모두 동일한 객체입니다.

// Node.js 환경
console.log(this, module.exports, exports);  // {} {} {}

console.log(this === module.exports);        // true
console.log(this === exports);               // true
console.log(module.exports === exports);     // true

2. 함수를 함수로서 호출할 때 그 함수 내부에서의 this

함수를 호출할 때, 함수 내부에 작성한 this는 undefined 가 바인딩됩니다. 때문에 엄격 모드로 실행하면 this 값이 undefined 그대로 나오지만 엄격 모드로 실행하지 않으면 autoboxing이 수행되어 전역 객체(Window, Global)이 바인딩 됩니다.

// 엄격 모드가 적용되지 않은 함수
function foo() {
  console.log(this);
}

// 엄격 모드가 적용된 함수
function bar() {
  "use strict";
  console.log(this);
}

foo();
bar();

// 브라우저 환경에서의 결과
// Window
// undefined

// Node.js 환경에서의 결과
// Global
// undefined

3. 함수를 메서드로서 호출할 때 그 메서드 내부에서의 this

함수를 메서드로서 호출할 때, 함수 내부에 작성한 this는 메서드 호출 주체(메서드명 앞의 객체)를 참조합니다.

// 함수로서 호출
const func = function(x) {
	console.log(this, x);
};
func(1);  // Window {...} 1

// 메서드로서 호출
const obj = {
	method: func
}
obj.method(2);  // { method: f } 1

4. 콜백 함수 호출 시 그 함수 내부에서의 this

이 경우 콜백 함수의 제어권을 넘겨받은 함수에 정의된대로 this가 바인딩 됩니다.

// addEventListener 함수는 콜백 함수의 this를 event.currentTarget으로 바인딩 하도록 정의되어 있습니다.
const $form = document.querySelector('#form');

$form.addEventListener('click', function (event) {
	console.log(event.currentTarget);
	console.log(this);
	console.log(this === currentTarget);
})

// 브라우저 환경 실행 결과
// <form>...</form>
// <form>...</form>
// true

해당 함수에 this를 무엇으로 바인딩할지 정의되어있지 않다면, undefined가 바인딩됩니다. 때문에 엄격 모드로 실행하지 않는다면 JS 엔진이 this를 전역 객체로 다시 바인딩합니다.

// forEach 함수는 콜백 함수의 this 바인딩을 별도로 정의해두고 있지 않습니다.
const foo = [1, 2, 3];

// 엄격 모드
foo.forEach(function (el) {
  "use strict";
  console.log(el, this);
});

// 브라우저 환경 실행 결과
// 1 undefined
// 2 undefined
// 3 undefined

// 비엄격 모드
foo.forEach(function (el) {
  console.log(el, this);
});

// 브라우저 환경 실행 결과
// 1 Window
// 2 Window
// 3 Window

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

new 명령어를 사용해 생성자 함수를 호출하면 this는 새롭게 생성될 인스턴스 자신이 할당됩니다.

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

const choco = new Cat("쪼꼬", 5);
const nabi = new Cat("나비", 3);

console.log(choco); // Cat {name: '쪼꼬', age: 5}
console.log(nabi); // Cat {name: '나비', age: 3}

총 정리

언제 결정되는가

  • this는 전역 공간이 생성될 때, 함수를 호출할 때 결정됩니다.
  • this는 다음 두 가지 경우에 변경될 수 있습니다.
    • 엄격 모드가 아닐 때, thisundefined, null 이 할당되어 있다면 JS 엔진에 의해 전역 객체로 바인딩 됩니다.
    • 개발자가 bind(), call(), apply() 메서드를 사용해 this를 명시적으로 바인딩 할 수 있습니다.

어떻게 결정되는가

  • 전역 공간을 생성하거나 함수를 호출하는 5가지 경우마다 다르게 결정됩니다.
    • 전역 공간
    • 함수를 함수로서 호출
    • 함수를 메서드로 호출
    • 콜백 함수에서
    • 생성자 함수에서

References

코어 자바스크립트 - YES24

메서드와 this

엄격 모드

JavaScript의 this

profile
"Life isn't about finding yourself. Life is about creating yourself."

0개의 댓글