포스트

[코어 자바스크립트] 3. this

JavaScript의 this를 호출 방식과 바인딩 규칙 중심으로 정리해보기

[코어 자바스크립트] 3. this

✍🏻 핵심 정리

  • this는 함수 실행 시점에 결정되는 값이며, 실행 컨텍스트에 따라 특정 객체를 참조하거나 undefined가 될 수 있다.
  • this는 함수 호출 시점에 결정된다.
  • this 바인딩 규칙은 new -> explicit -> implicit -> default의 우선순위를 가진다.
    • new는 constructor에서 사용되는 this로 이것은 새로 만들어진 인스턴스를 가리킨다.
    • explicit는 call, apply, bind 등을 통해 thisArg로 this를 명시적으로 지정하는 경우를 말한다.
    • implicit binding은 객체의 프로퍼티로서 함수를 호출하는 경우이며, 이때는 호출의 주체가 되는 객체가 this가 된다.
    • default binding은 일반 함수를 호출하는 경우이며 이때는 전역 객체 (strict mode이면 undefined)가 된다.
  • 화살표 함수는 자체적인 this 바인딩을 가지지 않으며, 정의된 위치의 상위 스코프의 this를 그대로 사용한다. (Lexical this)

this란?

this는 함수 실행 시점에 결정되는 값이다.
특정 객체를 가리킬 수도 있지만, strict mode에서는 undefined가 될 수도 있다.

핵심은 함수가 어디서 선언되었는지가 아니라, 어떻게 호출되었는지에 따라 결정된다는 것이다.


전역 공간에서의 this

전역 공간에서 this는 전역 객체를 가리킨다.

환경this
브라우저window
Node.jsglobal
표준 (ES2020~)globalThis

Node.js REPL (CLI)에서는 this === global이 맞지만 파일을 실행하면 다른 결과가 나온다.
이는 Node.js에서 파일이 전역에서 바로 실행되는 것이 아니라
CommonJS 모듈 래퍼 함수로 감싸지기 때문이다.

Node.js는 내부적으로 파일을 다음과 같은 형태로 실행한다.

1
2
3
(function (exports, require, module, __filename, __dirname) {
  // file code
});

이 때문에 파일의 top-level thisglobal이 아니라 module.exports를 가리킨다.

또한 ESM(ES Module) 환경에서는 top-level thisundefined이다.
ESM은 기본적으로 strict mode에서 실행되며, 모듈이 전역 객체에 의존하지 않도록 하기 위해 top-level thisundefined로 정의되어 있다.

var vs let/const 전역 변수의 차이

var로 선언한 전역 변수

1
2
3
4
5
6
7
8
var a = 1;
window.b = 2;

console.log(a, window.a, this.a); // 1 1 1
console.log(b, window.b, this.b); // 2 2 2

delete a; // false -> 여전히 1
delete b; // true -> b 사용하면 ReferenceError

var로 선언한 전역 변수는 전역 객체의 프로퍼티로 생성된다.
다만 직접적으로 전역 객체의 프로퍼티로 생성한 것과는 configurable 옵션이 차이가 난다.
var로 선언한 전역 변수는 configurable: false 옵션으로 생성되는데, 이는 delete로 삭제할 수 없음을 의미한다.
위의 코드에서 b처럼 직접 전역 객체에 할당한 프로퍼티는 configurable: true이기 때문에 삭제가 가능하다.

let/const로 선언한 전역 변수

1
2
3
4
5
var a = 1;
let b = 2;

console.log(window.a); // 1
console.log(window.b); // undefined

let/const는 전역 객체 프로퍼티가 아니라, Global Execution Context의 DeclarativeEnvironmentRecord에 직접 바인딩된다.

1
2
3
GlobalEnvironmentRecord
 ├─ ObjectEnvironmentRecord  : 전역 객체와 연결 / var, function 선언
 └─ DeclarativeEnvironmentRecord : lexical binding / let, const, class

Lexical Binding : 변수가 객체 프로퍼티가 아니라 스코프 환경(Environment Record)에 직접 바인딩되는 것


this 바인딩 규칙 (우선순위)

this는 함수가 어디서 선언되었는지가 아니라 함수가 어떻게 호출되었는지에 의해서만 결정된다.

우선순위바인딩 종류발생 조건
1new bindingnew 생성자 호출
2explicit bindingcall / apply / bind
3implicit binding메소드 호출 (obj.method())
4default binding일반 함수 호출

화살표 함수는 위 규칙을 따르지 않고 lexical this를 사용한다.


this 바인딩 규칙 상세

1. Default Binding - 일반 함수 호출

일반 함수로 호출되면 this는 전역 객체 또는 undefined이다.

모드this
non-strict전역 객체 (window / global)
strict modeundefined
1
2
3
4
5
function f() {
  console.log(this);
}

f(); // global object or undefined

2. Implicit Binding - 메소드 호출

객체의 프로퍼티로 호출되면 this는 호출한 객체이다.

1
2
3
4
5
6
7
8
const obj = {
  x: 10,
  getX() {
    return this.x;
  }
};

obj.getX(); // 10, this === obj

3. Explicit Binding - call / apply / bind

Explicit Binding은 정해진 함수를 실행하여 주어진 thisArg로 this를 지정한다.

call

1
2
Function.prototype.call(thisArg, ...args);
fn.call(obj, 1, 2);

함수를 즉시 실행하면서 this를 지정한다. 나머지 인자는 파라미터로 사용한다.

apply

1
2
Function.prototype.apply(thisArg, argsArray);
fn.apply(obj, [1, 2]);

함수를 즉시 실행하면서 this를 지정한다. 두 번째 인자는 배열로 받아 파라미터로 사용한다.

bind

1
2
Function.prototype.bind(thisArg, ...args);
const boundFn = fn.bind(obj);

this를 바인딩한 새로운 함수를 반환한다. name 프로퍼티에 bound가 붙는다는 특징이 있다.
한 번 bind된 함수는 이후 call이나 apply로도 this를 변경할 수 없다.

4. New Binding - 생성자 함수

new로 호출되면 this는 새로 생성되는 인스턴스이다.

1
2
3
4
5
function Person(name) {
  this.name = name;
}

const p = new Person("Alice");

동작 과정은 아래와 같다.

  1. 빈 객체 생성
  2. 해당 객체를 this에 바인딩
  3. constructor 실행
  4. 객체(this) 반환

생성자에서 객체를 명시적으로 반환하면, 그 객체가 최종 결과로 반환된다.
primitive를 반환하면 무시되고, 생성된 객체(this)가 반환된다.


화살표 함수와 Lexical This

화살표 함수는 자체적인 this 바인딩을 가지지 않고 정의된 위치의 상위 스코프의 this를 그대로 사용한다.
이를 Lexical This라고 한다.

1
2
3
4
5
6
7
8
9
const obj = {
  x: 10,
  f() {
    const g = () => console.log(this.x);
    g(); // implicit binding으로 this는 obj → 화살표 함수가 obj의 this 유지
  }
};

obj.f(); // 10

콜백 함수에서의 this

this는 콜백함수라고 해도 해당 함수가 어떻게 호출되느냐에 따라 결정된다.

1
2
3
4
5
setTimeout(function () {
  console.log(this);
}); // 브라우저: window (default binding)

array.map(fn, thisArg); // this → thisArg

setTimeout 내부에서 콜백은 일반 함수 형태로 호출되므로 default binding이 적용된다.


이벤트 핸들러에서의 this

이벤트 핸들러에서 일반 함수로 등록하면 this는 이벤트를 발생시킨 DOM 요소를 가리킨다.

1
2
3
4
5
6
7
button.onclick = function () {
  console.log(this); // button
};

button.onclick = () => {
  console.log(this); // 상위 스코프 (window 등)
};

🔍 코드로 확인하기

1. 메소드 → 함수 참조 (this가 깨지는 경우)

1
2
3
4
5
6
7
8
9
const obj = {
  x: 10,
  f() {
    return this.x;
  }
};

const g = obj.f;
console.log(g()); // undefined ← default binding 발생

2. 화살표 함수 lexical this

1
2
3
4
5
6
7
8
9
const obj = {
  x: 10,
  f() {
    const g = () => console.log(this.x);
    g();
  }
};

obj.f(); // 10 ← g는 화살표 함수로 f의 this(obj)를 사용

3. 객체 내부 직접 정의된 화살표 함수

1
2
3
4
5
6
7
8
const obj = {
  x: 10,
  f: () => {
    console.log(this.x);
  }
};

obj.f(); // undefined ← 화살표 함수의 상위 스코프는 global

4. 화살표 함수 반환

1
2
3
4
5
6
7
8
9
const obj = {
  x: 10,
  f() {
    return () => console.log(this.x);
  }
};

const g = obj.f();
g(); // 10 ← implicit binding으로 this는 obj, 화살표 함수가 유지

💼 실무 연결 포인트

실무에서 this 문제가 발생할 수 있다.

  • 이벤트 핸들러: 메소드를 전달하면 this가 element로 바뀜
  • 콜백 함수: 일반 함수로 호출되어 this가 깨짐
  • React class component: 이벤트 핸들러에서 bind 필요
  • setTimeout / Promise callback: 일반 함수로 호출되어 this가 default binding을 따름

이러한 문제의 핵심 원인은 함수 호출 방식이 바뀌면서 this가 달라지기 때문이다.

따라서

  • 콜백으로 메소드를 넘길 때는 this가 깨질 수 있음을 항상 인지하고
  • 필요하면 bind 또는 화살표 함수를 사용한다.

🗣️ 면접 대비 Q&A

Q1. JavaScript에서 this는 언제 결정되나요?

JS에서 this는 함수 선언 시점이 아니라 함수 호출 시점에 결정됩니다.

Q2. this 바인딩 규칙을 설명해주세요.

this 바인딩 규칙은 우선순위 순서로, new binding → explicit binding → implicit binding → default binding 순입니다.

  • new binding: new로 constructor를 생성할 때, this는 새로 생성한 인스턴스
  • explicit binding: call/apply/bind로 명시적으로 this를 파라미터로 전달하는 경우
  • implicit binding: 메소드로 호출할 때, 호출 주체 객체가 this
  • default binding: 일반 함수 호출 시, strict mode이면 undefined, 아니면 전역 객체

Q3. 화살표 함수의 this는 어떻게 동작하나요?

화살표 함수는 자체적인 this를 가지지 않고, 상위 스코프의 this를 그대로 참조합니다.


💡 최종 인사이트

JavaScript의 this는 단순히 객체를 가리키는 키워드가 아니라, 함수가 어떻게 호출되었는가에 의해 결정되는 값이다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.