This 키워드와 바인딩
자바스크립트에서 this
키워드는 함수가 호출될 때마다 결정되는 특별한 값입니다. 많은 개발자들이 this
의 동작 방식 때문에 혼란을 겪곤 하는데, 그 이유는 this
가 함수가 선언된 방식이 아니라 함수가 호출되는 방식에 따라 동적으로 결정되기 때문입니다. this
는 현재 실행 중인 코드의 '맥락(context)'을 나타내며, 어떤 객체가 현재 함수를 호출했는지(또는 어떤 객체와 연결되어 있는지)를 참조합니다.
이번 장에서는 이 this
키워드가 어떻게 다양한 상황에서 다르게 바인딩(연결)되는지, 그리고 이 특성을 어떻게 이해하고 활용해야 하는지 깊이 있게 탐구해 보겠습니다. this
의 동작 원리를 명확히 이해하는 것은 객체 지향 프로그래밍, 이벤트 처리, 그리고 모듈 패턴 등 자바스크립트의 고급 개념들을 자유자재로 다루는 데 필수적인 지식입니다.
this
의 기본 개념: 실행 컨텍스트의 일부
앞서 1장에서 우리는 실행 컨텍스트가 Lexical Environment
, Variable Environment
, 그리고 this
바인딩이라는 세 가지 구성 요소를 가지고 있음을 배웠습니다. 즉, this
키워드는 특정 함수가 실행될 때, 해당 실행 컨텍스트의 한 부분으로서 함께 결정되는 값입니다.
this
의 가장 중요한 특징은 "함수를 호출하는 방식"에 따라 this
가 참조하는 객체가 달라진다는 것입니다. 이는 다른 언어와 달리 자바스크립트의 this
가 매우 유연하다는 것을 의미하며, 동시에 혼란의 원인이 되기도 합니다.
기본적으로 this
는 현재 코드를 실행하는 주체(객체)를 가리킵니다. 그럼 이제 다양한 호출 상황별로 this
가 어떻게 바인딩되는지 살펴보겠습니다.
this
바인딩 규칙: 상황별 결정 방식
자바스크립트에서 this
는 크게 네 가지 일반적인 규칙에 따라 바인딩됩니다.
전역 컨텍스트에서의 this
함수 내부가 아닌 전역 코드에서 this
를 사용하면, this
는 전역 객체(Global Object)를 참조합니다.
- 웹 브라우저 환경:
window
객체 - Node.js 환경:
global
객체
// 웹 브라우저에서 실행 시
console.log(this === window); // 결과: true
let globalVar = "나는 전역 변수";
console.log(this.globalVar); // 결과: 나는 전역 변수 (window.globalVar와 같음)
function checkGlobalThis() {
console.log(this === window); // 함수 내부이지만 일반 함수 호출이므로 true (아래 2.2.2. 설명)
}
checkGlobalThis();
엄격 모드(strict mode)에서는 전역 컨텍스트의 this
는 undefined
가 됩니다. (엄격 모드에 대해서는 뒤에서 자세히 다룹니다.)
일반 함수 호출에서의 this
가장 혼란스러운 부분 중 하나입니다. 일반적인 함수(객체의 메서드가 아닌 독립적인 함수)로 호출될 때, this
는 전역 객체(window
또는 global
)를 참조합니다.
function greet() {
console.log(`Hello, ${this.name || '세계'}`); // this.name이 없으면 '세계' 사용
}
let name = "전역"; // window.name이 됨 (브라우저 환경)
greet(); // 결과: Hello, 전역 (브라우저에서는 window.name이 '전역'이 됨)
// Node.js에서는 window가 없으므로 '세계'가 나옴
const personName = "홍길동"; // var가 아니므로 window.personName이 아님
function showThis() {
console.log(this);
}
showThis(); // 웹 브라우저: window 객체, Node.js: global 객체 (또는 undefined in strict mode)
엄격 모드 ('use strict'
)의 영향
자바스크립트의 엄격 모드('use strict'
)에서는 일반 함수 호출 시 this
가 undefined
로 바인딩됩니다. 이는 개발자가 의도치 않게 전역 객체를 수정하는 것을 방지하기 위함입니다.
'use strict'; // 스크립트 최상단 또는 함수 최상단에 선언
function strictGreet() {
console.log(this); // 결과: undefined
}
strictGreet();
function outer() {
function inner() {
console.log(this); // outer가 일반 함수이므로, inner도 일반 함수 호출로 간주되어 undefined
}
inner();
}
outer();
따라서, this
문제를 줄이기 위해 항상 엄격 모드 사용을 권장하는 이유 중 하나입니다.
메서드 호출에서의 this
가장 흔하게 사용되는 패턴입니다. 함수가 객체의 속성(메서드)으로서 호출될 때, this
는 해당 메서드를 호출한 객체 자체를 참조합니다.
const user = {
name: "김철수",
age: 30,
greet: function() { // greet은 user 객체의 메서드
console.log(`안녕하세요, 제 이름은 ${this.name}입니다.`); // 여기서 this는 user 객체
},
introduce: function() {
console.log(`저는 ${this.age}살입니다.`); // 여기서 this는 user 객체
}
};
user.greet(); // 결과: 안녕하세요, 제 이름은 김철수입니다.
user.introduce(); // 결과: 저는 30살입니다.
// 중첩 객체에서의 this
const company = {
name: "ABC",
ceo: {
name: "박영희",
greetCEO: function() {
console.log(`CEO 이름: ${this.name}`); // 여기서 this는 ceo 객체
}
}
};
company.ceo.greetCEO(); // 결과: CEO 이름: 박영희
this
는 '어떤 객체가 점(.
) 앞에 왔는가'에 따라 결정됩니다.
주의: 메서드를 별도의 변수에 할당하여 일반 함수처럼 호출하는 경우
메서드를 변수에 할당하여 호출하면, 더 이상 객체의 메서드로서 호출되는 것이 아니라 일반 함수처럼 호출됩니다. 이 경우 this
바인딩 규칙이 변경됩니다.
const myGreet = user.greet; // user.greet 메서드를 myGreet 변수에 할당
myGreet(); // 결과: Hello, 세계 (또는 undefined in strict mode)
// myGreet은 일반 함수로 호출되었으므로 this는 전역 객체를 가리킵니다.
이러한 특성 때문에 this
의 혼란이 발생하며, 이를 해결하기 위한 여러 방법이 있습니다.
new
를 사용한 생성자에서의 this
new
키워드와 함께 함수를 호출하면, 해당 함수는 생성자 함수(Constructor Function) 로서 작동합니다. 이 경우 this
는 새로 생성되는 인스턴스(Instance) 객체를 참조합니다.
function Person(name, age) {
// 1. (내부적으로) 새로운 빈 객체가 생성되고 this에 바인딩됩니다. (예: this = {})
this.name = name; // 새로 생성된 객체의 name 속성에 값 할당
this.age = age; // 새로 생성된 객체의 age 속성에 값 할당
// 2. (내부적으로) this가 반환됩니다.
}
const person1 = new Person("김민준", 25);
console.log(person1.name); // 결과: 김민준
console.log(person1.age); // 결과: 25
const person2 = new Person("이유리", 30);
console.log(person2.name); // 결과: 이유리
new
연산자는 다음의 과정을 수행합니다:
- 빈 객체를 생성하고
this
에 할당합니다. - 생성자 함수의 본문을 실행하여
this
에 속성들을 추가합니다. this
(새로 생성된 객체)를 반환합니다.
특별한 메서드를 사용한 명시적 바인딩
자바스크립트의 모든 함수는 call()
, apply()
, bind()
라는 특별한 메서드를 가지고 있습니다. 이 메서드들을 사용하면 개발자가 명시적으로 this
가 참조할 객체를 지정할 수 있습니다.
-
function.call(thisArg, arg1, arg2, ...)
: 함수를 즉시 호출하면서, 첫 번째 인자로 전달된thisArg
객체를this
로 바인딩하고, 이후 인자들은 개별적으로 함수에 전달합니다.function introduce(job, city) { console.log(`안녕하세요, 저는 ${this.name}입니다. ${job}를 하고 ${city}에 살고 있습니다.`); } const person = { name: "최지수" }; introduce.call(person, "개발자", "서울"); // 결과: 안녕하세요, 저는 최지수입니다. 개발자를 하고 서울에 살고 있습니다. // introduce 함수 내부의 this가 person 객체를 가리키게 됨
-
function.apply(thisArg, [argsArray])
:call()
과 동일하게 함수를 즉시 호출하며thisArg
를this
로 바인딩합니다. 차이점은 두 번째 인자로 배열 형태의 인자들을 전달해야 한다는 것입니다.introduce.apply(person, ["디자이너", "부산"]); // 결과: 안녕하세요, 저는 최지수입니다. 디자이너를 하고 부산에 살고 있습니다. // 배열로 인자들을 전달
call
과apply
는 즉시 함수를 실행한다는 점에서 유사하며, 주로 인자 전달 방식의 차이로 구분됩니다. -
function.bind(thisArg, arg1, arg2, ...)
:call()
이나apply()
와 달리 함수를 즉시 호출하지 않고,this
가 영구적으로 바인딩된 새로운 함수를 반환합니다. 이 반환된 함수는 나중에 호출될 때 지정된this
값을 가집니다.const anotherPerson = { name: "정우진" }; const boundIntroduce = introduce.bind(anotherPerson, "학생"); // this를 anotherPerson으로, 첫 인자를 "학생"으로 고정 boundIntroduce("제주"); // 결과: 안녕하세요, 저는 정우진입니다. 학생를 하고 제주에 살고 있습니다. // boundIntroduce는 이미 this가 anotherPerson으로 고정되어, job은 "학생", city는 "제주"로 전달됨
bind()
는 이벤트 리스너에서 콜백 함수의this
컨텍스트를 유지해야 할 때 매우 유용하게 사용됩니다.
화살표 함수에서의 this
바인딩
ES6에서 도입된 화살표 함수(Arrow Function) 는 this
바인딩 방식에서 기존 함수와 중요한 차이점을 가집니다.
화살표 함수는 자체적인 this
바인딩을 가지지 않습니다. 대신, 화살표 함수가 선언될 당시의 상위(Lexical) 스코프의 this
를 그대로 상속받습니다. 즉, 화살표 함수는 this
를 동적으로 결정하지 않고, 자신이 정의된 렉시컬 환경의 this
값을 '고정'시킵니다.
const user = {
name: "이아림",
age: 28,
// 일반 메서드 (메서드 호출 규칙 적용)
greet: function() {
console.log(`안녕하세요, ${this.name}입니다.`); // this는 user 객체
},
// 화살표 함수 메서드 (상위 스코프의 this 상속)
// 이 경우 user 객체는 스코프가 아니므로, 전역 스코프의 this(window)를 상속받음
arrowGreet: () => {
console.log(`안녕하세요, ${this.name || '알 수 없는 사람'}입니다.`); // this는 window 객체 (브라우저)
},
// 중첩된 함수에서의 화살표 함수 활용 (매우 중요!)
delayedGreet: function() { // 일반 함수 (여기서의 this는 user 객체)
console.log(`(1) 일반 함수 내의 this: ${this.name}`); // this는 user
// 1초 뒤에 실행될 함수 (일반 함수)
setTimeout(function() {
console.log(`(2) setTimeout 내부 일반 함수 this: ${this.name}`); // this는 window (일반 함수 호출 규칙)
}, 1000);
// 1초 뒤에 실행될 함수 (화살표 함수)
setTimeout(() => {
console.log(`(3) setTimeout 내부 화살표 함수 this: ${this.name}`); // this는 outer 스코프(delayedGreet)의 this (user)
}, 2000);
}
};
user.greet(); // 결과: 안녕하세요, 이아림입니다.
user.arrowGreet(); // 결과: 안녕하세요, 알 수 없는 사람입니다. (브라우저에서 window.name이 없으면)
user.delayedGreet();
// (1) 일반 함수 내의 this: 이아림
// (2) setTimeout 내부 일반 함수 this: (undefined 또는 window.name 값)
// (3) setTimeout 내부 화살표 함수 this: 이아림
위 예시에서 delayedGreet
메서드 내부의 일반 setTimeout
함수는 this
를 전역 객체로 바인딩하지만, 화살표 함수는 delayedGreet
가 정의된 렉시컬 스코프의 this
(즉, user
객체)를 그대로 유지하는 것을 볼 수 있습니다. 이러한 특성 때문에 이벤트 핸들러나 콜백 함수에서 외부 스코프의 this
를 유지하고 싶을 때 화살표 함수를 사용하는 것이 매우 효과적입니다.
마무리하며
이번 장에서는 자바스크립트의 this
키워드와 그 바인딩 원리에 대해 깊이 있게 탐구했습니다. this
가 함수가 호출되는 방식에 따라 동적으로 결정되며, 전역, 메서드, 생성자, 일반 함수 호출 시 각각 다른 규칙을 따름을 이해했습니다. 또한, call()
, apply()
, bind()
메서드를 사용하여 this
를 명시적으로 제어하는 방법과, 화살표 함수가 this
를 렉시컬하게 상속받는다는 중요한 특성도 학습했습니다.
this
는 자바스크립트의 유연성을 보여주는 동시에, 초보 개발자들에게는 가장 큰 혼란을 주는 개념이기도 합니다. 하지만 이제 여러분은 this
의 결정 규칙을 명확히 이해하고, 각 상황에 맞는 바인딩 방식을 예측하거나 제어할 수 있는 능력을 갖추게 되었습니다.
이 지식은 객체 지향 프로그래밍 패턴을 이해하고, 비동기 코드에서 컨텍스트를 올바르게 유지하며, 다양한 라이브러리와 프레임워크의 내부 동작을 파악하는 데 필수적인 기반이 될 것입니다. 다양한 상황에서 this
값을 직접 console.log()
로 출력해보며 연습하는 것이 this
개념을 완전히 자기 것으로 만드는 데 가장 좋은 방법입니다.