this는 호출되는 순간에 결정된다
자바스크립트 this 키워드가 헷갈리는 이유는 거의 하나로 귀결됩니다. 바로 this가 함수가 정의된 위치에 따라 결정된다고 착각하는 것이죠. 사실은 그렇지 않습니다. this는 함수가 어떻게 호출되는지에 따라 달라집니다.
똑같은 함수를 두 가지 방식으로 호출해 보면 차이가 확 드러납니다:
같은 함수인데도 this가 달라졌죠. 첫 번째 호출에서는 점(.) 앞에 user가 있으니 this는 user가 됩니다. 두 번째 호출에서는 점 앞에 아무것도 없으니 this는 undefined가 되고요. 함수 자체는 전혀 바뀌지 않았습니다. 바뀐 건 호출하는 방식뿐이에요.
이 원칙을 꼭 기억하세요. this가 뭔지 알고 싶다면 함수 정의가 아니라 호출 지점을 봐야 합니다.
this 바인딩의 네 가지 규칙
this가 결정되는 방식은 크게 네 가지입니다. 자바스크립트 this 키워드와 관련해서 헷갈리는 상황은 거의 다 이 네 가지 중 어디에 해당하는지만 따져보면 풀립니다.
1. 메서드 호출: obj.fn()
함수를 객체의 프로퍼티로 호출하면, this는 그 객체가 됩니다.
점(.) 앞에 붙은 대상이 곧 this다. 이게 전부다.
2. 그냥 호출: fn()
앞에 아무 객체도 없이 호출하면, strict mode(모듈과 클래스에서는 자동으로 켜진다)에서는 this가 undefined가 되고, sloppy mode에서는 전역 객체가 된다:
이 지점이 바로 "this is undefined" 에러가 튀어나오는 곳입니다. 객체에서 메서드를 떼어내는 순간, 메서드 호출이 일반 함수 호출로 바뀌어 버리거든요:
counter.를 앞에 붙이지 않고 호출했으니 바인딩이 풀려버린 거죠. 함수 자체는 자기가 어느 객체에서 꺼내졌는지 기억하지 못합니다.
3. 명시적 바인딩: .call(), .apply(), .bind()
this를 원하는 값으로 강제로 지정할 수도 있습니다:
.call과 .apply는 함수를 즉시 호출한다는 점은 같고, 인자를 넘기는 방식만 다릅니다. 반면 .bind는 this가 고정된 새로운 함수를 반환하기 때문에 콜백 함수에 넘길 때 특히 유용합니다.
4. new 호출 방식: new Fn()
함수를 new와 함께 호출하면 새로운 객체가 만들어지고, 그 객체가 this에 바인딩됩니다:
클래스도 내부적으로는 이 메커니즘을 그대로 사용합니다. 뒷장에서 자세히 다룰 예정이에요.
화살표 함수는 자기만의 this가 없다
화살표 함수는 위에서 설명한 규칙을 의도적으로 따르지 않습니다. this를 아예 바인딩하지 않고, 화살표 함수가 정의된 시점의 바깥 스코프의 this를 그대로 물려받아 고정해 버리죠.
모듈 최상위에서 선언된 화살표 함수는 그 시점의 this, 즉 undefined를 그대로 가둬버립니다. user.arrow() 형태로 호출해도 화살표 함수는 this를 다시 바인딩하지 않아요.
얼핏 버그처럼 보이지만, 사실 이게 핵심입니다. 화살표 함수는 메서드 내부에서 바깥쪽 this를 그대로 이어받고 싶을 때 진가를 발휘하거든요:
setInterval 안의 화살표 함수는 start의 this를 그대로 물려받습니다. 그리고 start는 timer.start() 형태로 호출됐기 때문에 this.seconds가 제대로 동작하죠. 만약 여기에 일반 function을 썼다면 setInterval이 넘겨주는 별도의 this를 갖게 되면서 코드가 깨졌을 겁니다.
정리하자면, 메서드 안쪽의 콜백에는 화살표 함수를 쓰고, 메서드 자체는 일반 함수로 정의하는 게 좋습니다.
콜백 함수 this, 가장 흔한 함정
자바스크립트 this가 꼬이는 가장 흔한 패턴이 바로 이겁니다. 메서드를 콜백으로 넘기는 순간 바인딩이 풀려버리죠:
setTimeout은 이 함수를 c.increment() 형태가 아니라 그냥 일반 함수처럼 호출합니다. 이걸 해결하는 방법은 세 가지가 있습니다:
셋 다 잘 동작하지만, 보통은 화살표 함수로 감싸는 방식이 가장 깔끔합니다.
최상위 스코프에서의 this
최상위(top-level)에서 this가 무엇을 가리키는지는 코드가 실행되는 환경에 따라 달라집니다.
- 브라우저 일반 스크립트(모듈 아님):
this는window입니다. - ES 모듈(요즘 번들러로 빌드되는 대부분의 코드 포함):
this는undefined입니다. - Node.js CommonJS 모듈:
this는module.exports입니다.
환경에 상관없이 전역 객체를 안전하게 참조하고 싶다면 globalThis를 사용하세요.
실무에서는 최상위 레벨의 this에 기대지 않는 게 좋습니다. 진짜로 전역 객체가 필요할 땐 globalThis를 쓰고, 그게 아니라면 값을 명시적으로 넘겨주세요.
this 바인딩 판별 순서도
this가 뭘 가리키는지 헷갈린다면 아래 순서대로 따져보면 됩니다.
- 화살표 함수인가요? 그렇다면
this는 바깥 스코프의this를 그대로 물려받습니다. 호출된 위치는 아예 신경 쓸 필요가 없어요. - **
new**로 호출됐나요? 그러면this는 새로 만들어진 객체입니다. .call,.apply, 또는 바인딩된 함수로 호출됐나요? 그러면this는 넘겨준 그 값이 됩니다.obj.method()형태로 호출됐나요? 그러면this는obj죠.- 그냥 **
fn()**처럼 단독 호출됐나요? strict mode에서는this가undefined입니다.
이 순서대로 내려가면 모든 경우가 정리됩니다.
다음 주제: 고차 함수
이제 this가 더 이상 수수께끼가 아니니, 자바스크립트를 자바스크립트답게 만들어주는 패턴으로 넘어갈 준비가 됐습니다. 바로 함수를 값처럼 주고받는 것이죠. 다음에는 고차 함수(higher-order functions) — 함수를 인자로 받거나 함수를 반환하는 함수 — 를 살펴보고, 이것이 배열 메서드와 이벤트 핸들러, 그리고 실제 프로젝트의 대부분의 자바스크립트 코드를 어떻게 떠받치고 있는지 알아보겠습니다.
자주 묻는 질문
자바스크립트에서 this는 뭘 가리키나요?
this는 뭘 가리키나요?this는 함수가 정의된 곳이 아니라 호출된 곳의 객체를 가리킵니다. 예를 들어 user.greet()에서 this는 user가 되고, 그냥 greet()처럼 호출하면 strict mode에서는 undefined, sloppy mode에서는 전역 객체가 됩니다. 핵심은 '점(.) 왼쪽에 뭐가 있느냐', 즉 호출 시점(call site)입니다.
함수 안에서 this가 왜 자꾸 undefined로 나오죠?
this가 왜 자꾸 undefined로 나오죠?메서드를 객체에서 떼어내 단독으로 호출했거나, 콜백으로 넘겼을 가능성이 높습니다. const fn = user.greet; fn();처럼 쓰면 점(.) 왼쪽에 아무것도 없으니 바인딩이 끊어집니다. 해결 방법은 세 가지입니다: user.greet()로 그대로 호출하거나, .bind(user)로 묶어주거나, 화살표 함수로 감싸서 외부 this를 유지하는 겁니다.
화살표 함수에서 this는 뭐가 다른가요?
this는 뭐가 다른가요?화살표 함수는 자기 자신의 this를 갖지 않습니다. 정의되는 시점에 바깥 스코프의 this를 그대로 물려받죠. 그래서 메서드 안에서 콜백을 만들 때 바깥 this를 그대로 쓰고 싶다면 화살표 함수가 정답입니다. 대신 .call(), .apply(), .bind()로 this를 바꿀 수 없다는 점은 알아두세요.
최상위(top-level)에서 this는 뭔가요?
this는 뭔가요?브라우저의 일반 스크립트에서는 최상위 this가 window 객체입니다. ES 모듈에서는 undefined이고, Node.js의 CommonJS 모듈에서는 module.exports를 가리킵니다. 환경에 상관없이 전역 객체에 접근하고 싶다면 globalThis를 쓰면 됩니다.