
JavaScript를 학습하면서 가장 헷갈리는 개념 중 하나가 바로 this입니다. 다른 언어와 달리 JavaScript의 this는 함수가 호출되는 방식에 따라 값이 달라지기 때문에 많은 개발자들이 어려움을 겪습니다. 이 글에서는 다양한 상황에서 this가 어떻게 바인딩되는지 6가지 핵심 상황을 통해 자세히 알아보겠습니다.
JavaScript에서 this는 함수가 호출되는 방식에 따라 결정됩니다. 이는 함수가 정의된 위치가 아닌, 실행되는 순간의 호출 방식이 중요하다는 의미입니다. 마치 전화를 걸 때 상황에 따라 다른 사람과 연결되는 것과 비슷합니다.
전역에서 함수가 호출되면, this는 전역 객체를 참조합니다.
function globalFunc() {
console.log(this);
}
globalFunc(); // 브라우저: window, Node.js: global
| 환경 | this 값 | 설명 |
|---|---|---|
| 브라우저 | window | DOM과 관련된 모든 전역 객체 |
| Node.js | global | Node.js 런타임 전역 객체 |
| Strict Mode | undefined | 엄격 모드에서는 전역 객체 대신 undefined |
"use strict";
function strictFunc() {
console.log(this); // undefined
}
strictFunc();
객체의 메서드로 호출된 함수에서는 this가 해당 객체를 참조합니다. 이는 가장 직관적인 동작 방식입니다.
const user = {
name: "김철수",
age: 25,
greet: function() {
console.log(`안녕하세요, 저는 ${this.name}입니다.`);
console.log(`나이는 ${this.age}살입니다.`);
}
};
user.greet();
// 출력: 안녕하세요, 저는 김철수입니다.
// 출력: 나이는 25살입니다.
const company = {
name: "테크 회사",
department: {
name: "개발팀",
introduce: function() {
console.log(`${this.name}입니다.`); // "개발팀입니다."
}
}
};
company.department.introduce();
생성자 함수나 클래스에서 this는 새로 생성되는 객체(인스턴스)를 참조합니다.
function Person(name, age) {
this.name = name;
this.age = age;
this.introduce = function() {
console.log(`저는 ${this.name}이고, ${this.age}살입니다.`);
};
}
const person1 = new Person("이영희", 28);
const person2 = new Person("박민수", 32);
person1.introduce(); // 저는 이영희이고, 28살입니다.
person2.introduce(); // 저는 박민수이고, 32살입니다.
class Developer {
constructor(name, language) {
this.name = name;
this.language = language;
}
coding() {
console.log(`${this.name}이 ${this.language}로 코딩합니다.`);
}
}
const developer = new Developer("김개발", "JavaScript");
developer.coding(); // 김개발이 JavaScript로 코딩합니다.
call(), apply(), bind() 메서드를 사용하면 this를 명시적으로 설정할 수 있습니다.
function greet() {
console.log(`안녕하세요, ${this.name}님!`);
}
const user1 = { name: "홍길동" };
const user2 = { name: "김철수" };
greet.call(user1); // 안녕하세요, 홍길동님!
greet.call(user2); // 안녕하세요, 김철수님!
function introduce(job, city) {
console.log(`${this.name}은 ${city}에서 ${job}으로 일합니다.`);
}
const person = { name: "이개발" };
introduce.apply(person, ["개발자", "서울"]);
// 이개발은 서울에서 개발자로 일합니다.
function sayHello() {
console.log(`Hello, ${this.name}!`);
}
const user = { name: "Alice" };
const boundSayHello = sayHello.bind(user);
boundSayHello(); // Hello, Alice!
| 메서드 | 즉시 실행 | 인자 전달 방식 | 반환값 |
|---|---|---|---|
call() | ✅ | 개별 인자 | 함수 실행 결과 |
apply() | ✅ | 배열 형태 | 함수 실행 결과 |
bind() | ❌ | 개별 인자 | 새로운 함수 |
화살표 함수는 자체적인 this를 가지지 않고, 상위 스코프의 this를 상속받습니다. 이는 화살표 함수의 가장 중요한 특징입니다.
const obj = {
name: "테스트",
regularFunction: function() {
console.log("일반 함수:", this.name); // "테스트"
},
arrowFunction: () => {
console.log("화살표 함수:", this.name); // undefined (전역 this)
}
};
obj.regularFunction(); // 일반 함수: 테스트
obj.arrowFunction(); // 화살표 함수: undefined
class EventHandler {
constructor() {
this.message = "클릭되었습니다!";
}
setupEventListener() {
// 화살표 함수를 사용하여 this 바인딩 유지
document.getElementById("btn").addEventListener("click", () => {
console.log(this.message); // "클릭되었습니다!"
});
}
}
const handler = new EventHandler();
handler.setupEventListener();
DOM 요소의 이벤트 핸들러에서 this는 기본적으로 이벤트를 발생시킨 요소를 참조합니다.
const button = document.getElementById("myButton");
button.addEventListener("click", function() {
console.log(this); // 클릭된 button 요소
console.log(this.textContent); // 버튼의 텍스트
this.style.backgroundColor = "blue"; // 버튼 색상 변경
});
const button = document.getElementById("myButton");
button.addEventListener("click", () => {
console.log(this); // 상위 스코프의 this (보통 window)
// 이벤트 객체를 통해 요소에 접근해야 함
});
// 이벤트 객체 활용
button.addEventListener("click", (event) => {
console.log(event.target); // 클릭된 button 요소
});
class ButtonController {
constructor() {
this.clickCount = 0;
}
handleClick() {
this.clickCount++;
console.log(`클릭 횟수: ${this.clickCount}`);
}
init() {
const button = document.getElementById("counter");
// bind 사용
button.addEventListener("click", this.handleClick.bind(this));
// 또는 화살표 함수 사용
button.addEventListener("click", () => this.handleClick());
}
}
const controller = new ButtonController();
controller.init();
JavaScript에서 this 바인딩에는 우선순위가 있습니다.
call, apply, bind)function test() {
console.log(this.name);
}
const obj = { name: "객체" };
const boundTest = test.bind({ name: "바인딩" });
// 명시적 바인딩이 암시적 바인딩보다 우선
obj.test = boundTest;
obj.test(); // "바인딩" (명시적 바인딩 우선)
// 문제 상황
class Timer {
constructor() {
this.seconds = 0;
}
start() {
// 이렇게 하면 this가 전역 객체를 참조
setInterval(function() {
this.seconds++; // TypeError: Cannot read property 'seconds' of undefined
console.log(this.seconds);
}, 1000);
}
}
// 해결책 1: bind 사용
class Timer {
constructor() {
this.seconds = 0;
}
start() {
setInterval(function() {
this.seconds++;
console.log(this.seconds);
}.bind(this), 1000);
}
}
// 해결책 2: 화살표 함수 사용
class Timer {
constructor() {
this.seconds = 0;
}
start() {
setInterval(() => {
this.seconds++;
console.log(this.seconds);
}, 1000);
}
}
// 문제 상황
const calculator = {
numbers: [1, 2, 3, 4, 5],
multiplier: 2,
calculate() {
return this.numbers.map(function(num) {
return num * this.multiplier; // this.multiplier is undefined
});
}
};
// 해결책 1: thisArg 매개변수 사용
const calculator = {
numbers: [1, 2, 3, 4, 5],
multiplier: 2,
calculate() {
return this.numbers.map(function(num) {
return num * this.multiplier;
}, this); // 두 번째 인자로 this 전달
}
};
// 해결책 2: 화살표 함수 사용
const calculator = {
numbers: [1, 2, 3, 4, 5],
multiplier: 2,
calculate() {
return this.numbers.map(num => num * this.multiplier);
}
};
JavaScript의 this 바인딩은 함수 호출 방식에 따라 동적으로 결정되는 특별한 개념입니다. 이를 제대로 이해하기 위해서는 다음 핵심 원칙들을 기억해야 합니다.
this는 함수가 정의된 곳이 아닌, 호출되는 방식에 따라 결정됩니다.this를 상속받아 바인딩이 고정됩니다.call, apply, bind를 활용하여 this를 명시적으로 제어할 수 있습니다.this 바인딩을 완전히 이해하면 JavaScript의 객체 지향 프로그래밍과 함수형 프로그래밍을 더욱 효과적으로 활용할 수 있습니다. 실무에서는 화살표 함수와 명시적 바인딩을 적절히 조합하여 this 관련 문제를 예방하는 것이 중요합니다.