들어가며
JavaScript의 함수는 선언 방식에 따라 동작 방식이 다르고, this 바인딩도 호출 방식에 따라 동적으로 결정됩니다.
이러한 차이를 정확히 이해하면 콜백, 메서드, 이벤트 핸들러 등에서 발생하는 this 관련 버그를 방지할 수 있습니다.
이 글에서는 함수 선언 vs 표현식 차이부터 this의 동적 바인딩 원리, 그리고 call, apply, bind를 활용한 명시적 바인딩까지 완벽 정리합니다.
1. 함수 선언 vs 함수 표현식
✅ 함수 선언 (Function Declaration)
function greet() {
console.log("Hello");
}
- 호이스팅 O: 선언 전에도 사용 가능
- 스코프 최상단으로 끌어올려짐
✅ 함수 표현식 (Function Expression)
const greet = function () {
console.log("Hi");
};
- 호이스팅 X: 선언 이후에만 사용 가능
- const, let, var 등 변수에 담기는 함수
✅ 화살표 함수 (Arrow Function)
const greet = () => console.log("Arrow!");
- this 바인딩이 없음 → 상위 스코프의 this를 계승 (렉시컬 this) (일반 함수는 호출한 객체에 this가 바인딩되지만, 화살표함수는 선언 당시 상위 스코프의 this를 렉시컬하게 바인딩함)
- 메서드(객체에 속한 함수)로는 사용하지 않는 것이 일반적(일반 함수 표현식_function키워드 혹은 function생략한 일반함수방식_으로 만든 함수가 메서드를 정의하는 데 적합함. 일반메서드에서는 this바인딩 없어서 부적합. 일반함수로 메서드를 정의하는 것이 안전 )
const obj = {
name: "Hoon",
sayHi: function () {
console.log(`Hi, ${this.name}`);
},
};
obj.sayHi(); // Hi, Hoon → sayHi는 일반 함수이므로 this는 obj를 가리킴.
const obj2 = {
name: "Hoon",
sayHi: () => {
console.log(`Hi, ${this.name}`);
},
};
obj2.sayHi(); // Hi, undefined (❌) → this는 obj가 아닌, **상위 스코프의 this (보통 window 또는 undefined)**를 가리켜요.
- 화살표함수는 콜백 클로저 등 함수 내부에서 this 공유할 때 적합.
function Timer() {
this.seconds = 0;
setInterval(() => {
this.seconds++;
console.log(this.seconds);
}, 1000);
}
new Timer(); // this는 Timer 인스턴스를 가리킴. 정의시점의 상위 스코프의 this를 받으니까
// 일반함수의 경우, 호출한 애를 가르키는데 시간이 지난 후 실행하는 경우 실행주체가 없게 됨
function Timer() {
this.seconds = 0;
setInterval(function () {
this.seconds++; // ❌ 예상한 this가 아님! 에러나거나 NaN 출력 (호출컨텍스트 따라감)
console.log(this.seconds);
}, 1000);
}
new Timer();
// 일반함수로 할 경우, 한 번 우회해서 구현은 되지만 불편하고 가독성 떨어짐
function Timer() {
this.seconds = 0;
const self = this; // 💡 Trick
setInterval(function () {
self.seconds++; // 우회해서 해결
console.log(self.seconds);
}, 1000);
}
// 방법2 bind사용
function Timer() {
this.seconds = 0;
setInterval(function () {
this.seconds++;
console.log(this.seconds);
}.bind(this), 1000);
}
2. this의 결정 방식
호출 방식 | this 값 |
일반 함수 호출 | 전역(window) 또는 undefined (strict mode) |
메서드 호출 (obj.fn()) | obj |
생성자 호출 (new) | 새로 생성된 인스턴스 |
화살표 함수 | 외부 스코프의 this (고정) |
call/apply/bind 사용 | 명시적으로 지정한 값 |
3. 예시로 비교해보기
일반 함수
function showThis() {
console.log(this);
}
showThis(); // 브라우저: window / strict: undefined
객체 메서드
const user = {
name: "Tom",
sayHello() {
console.log(this.name); // Tom
},
};
user.sayHello();
화살표 함수 내부의 this
const user = {
name: "Alice",
sayHi: () => {
console.log(this.name); // undefined (상위 스코프는 window)
},
};
→ 화살표 함수는 메서드로 사용 시 this가 예상과 다르게 동작함
4. call, apply, bind로 명시적 바인딩
✅ call
function greet() {
console.log(this.name);
}
const user = { name: "Jane" };
greet.call(user); // Jane
✅ apply (call과 유사하나 인자를 배열로 받음)
function add(a, b) {
return a + b;
}
console.log(add.apply(null, [2, 3])); // 5
✅ bind (새로운 함수 반환)
const person = {
name: "Lee",
};
function say() {
console.log(this.name);
}
const bound = say.bind(person);
bound(); // Lee
→ bind는 콜백으로 넘길 때 유용 (예: 이벤트 핸들러 내부에서 this 유지)
5. this 바인딩 문제 실전 예시
setTimeout에서 this가 엉킬 때
const obj = {
count: 0,
start() {
setTimeout(function () {
console.log(this.count); // undefined or window.count
}, 1000);
},
};
해결 1: 화살표 함수로
setTimeout(() => {
console.log(this.count); // obj.count
}, 1000);
해결 2: bind로 고정
setTimeout(function () {
console.log(this.count);
}.bind(this), 1000);
마치며
JavaScript 함수는 어떻게 선언되었는지, 어떻게 호출되었는지에 따라 this가 달라지는 동적 언어입니다.
특히 call, apply, bind는 this를 정확히 컨트롤할 수 있는 유용한 도구이며,
화살표 함수의 렉시컬 this와의 차이를 꼭 구분해서 써야 합니다.
📂 다음 글에서는:
- new와 생성자 함수 내부 this 구조
- 클래스 메서드에서의 this 고정 문제
- bind vs 화살표 함수의 실제 차이
등을 이어서 다루겠습니다.
'language > javascript' 카테고리의 다른 글
fetch API 사용법과 async/await 기반 에러 처리 패턴 (1) | 2025.04.26 |
---|---|
setTimeout vs Promise 실행 순서 비교와 마이크로태스크 큐 (0) | 2025.04.26 |
자바스크립트 콜백 함수 완전 정리 – 기초 개념부터 깊이 있는 이해까지 (0) | 2025.04.21 |
실행 컨텍스트와 호이스팅 – JavaScript 실행 구조의 핵심 이해하기 (1) | 2025.04.21 |
자바스크립트의 이벤트 루프와 태스크 큐 완전 이해하기 (0) | 2025.04.20 |
댓글