notes
22장. this
모던 JS Deep Dive — this 바인딩 규칙과 호출 방식별 동작 핵심 정리.
참고: 이웅모 《모던 자바스크립트 Deep Dive》 22장 · [MDN this](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this) · [MDN bind](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind)
이벤트 리스너에 메서드만 넘기다 this가 날아간 적이 있다.
스코프는 작성 위치, this는 호출 방식. 축을 섞으면 평생 헷갈린다. 이 장은 호출 네 갈래를 손으로 다시 긋는다.
flowchart TD
CALL[호출 방식] --> T[this]
CALL --> M["obj.method()"] --> TO[obj]
CALL --> F["fn()"] --> TG["undefined(strict) / 전역(슬롭)"]
CALL --> N["new Fn()"] --> TN[새 인스턴스]
CALL --> B["call/apply/bind"] --> TX[명시적 값]
CALL --> AR[화살표] --> LEX[렉시컬 this — 호출과 무관]
22.1 this 키워드
객체는 상태(프로퍼티)와 행동(메서드)을 묶는다. 메서드가 “자기 객체”를 가리킬 수단이 this다. 다만 JS의 this는 다른 언어의 “인스턴스 고정 this”와 다르다. 함수가 어떻게 호출됐는지에 따라 동적으로 바뀐다.
렉시컬 스코프(식별자 해석)는 함수가 정의될 때 상위 스코프가 정해지고, this는 호출될 때 정해진다. 이 한 줄이 이 장의 열쇠다.
22.2 함수 호출 방식과 this 바인딩
같은 함수도 호출 형태에 따라 this가 갈린다.
- 일반 함수 호출
- 메서드 호출
- 생성자 호출
apply/call/bind에 의한 간접 호출
(+ 화살표는 별 규칙: 자기 this 바인딩 없음)
22.2.1 일반 함수 호출
foo()처럼 호출. 슬롭에선 this가 전역 객체. strict에선 `undefined`.
function foo() {
console.log(this);
}
foo(); // 슬롭: window/global , strict: undefined
function outer() {
function inner() {
console.log(this);
}
inner(); // 역시 일반 호출 — 전역 또는 undefined
}
콜백으로 넘어간 메서드도 “일반 호출”이 되는 순간이 많다.
const obj = {
n: 1,
m() { return this.n; },
};
const fn = obj.m;
// fn(); // strict: this undefined → TypeError on this.n
22.2.2 메서드 호출
obj.method() — 점 앞의 객체가 this.
중요: “메서드를 소유한 객체”가 아니라 호출 시 점 앞에 적은 객체.
const person = {
name: 'Lee',
getName() {
return this.name;
},
};
const other = { name: 'Kim', getName: person.getName };
console.log(person.getName()); // Lee
console.log(other.getName()); // Kim — 호출자가 other
프로토타입에 있는 메서드도 마찬가지. 인스턴스로 호출하면 this는 인스턴스.
22.2.3 생성자 함수 호출
new Foo() — this는 새로 만들어진 인스턴스(17장 과정).
function Circle(radius) {
this.radius = radius;
}
const c = new Circle(5);
console.log(c.radius); // 5
new 없이 호출하면 일반 함수 규칙으로 떨어져 전역 오염(슬롭) 또는 TypeError(strict에서 this.xxx 할당)가 난다.
22.2.4 apply / call / bind
Function.prototype 메서드로 this를 명시.
fn.call(thisArg, a, b)fn.apply(thisArg, [a, b])fn.bind(thisArg, a)→ this가 고정된 새 함수
function greet(msg) {
return `${msg}, ${this.name}`;
}
const user = { name: '민섭' };
console.log(greet.call(user, 'hi'));
console.log(greet.apply(user, ['hi']));
const bound = greet.bind(user, 'hi');
console.log(bound());
이벤트·타이머·배열 메서드에 메서드를 넘길 때 bind가 자주 등장한다.
class Button {
constructor(label) {
this.label = label;
}
click() {
console.log(this.label);
}
}
const b = new Button('save');
// el.addEventListener('click', b.click); // this 분실 위험
// el.addEventListener('click', b.click.bind(b));
// el.addEventListener('click', () => b.click());
화살표 함수와 this
화살표는 자기 this 바인딩이 없다. 정의 위치의 렉시컬 this를 그대로 쓴다. call/apply/bind로 못 바꾼다(무시).
const obj = {
value: 100,
foo() {
setTimeout(() => console.log(this.value), 0); // 100 — foo의 this
},
bad() {
setTimeout(function () {
console.log(this.value); // 슬롭: 전역.value, strict: undefined.value
}, 0);
},
};
메서드를 화살표로 정의하면 obj.method()여도 this가 객체가 아닐 수 있다(상위 스코프 this). 객체 리터럴 메서드는 축약 `method()` 또는 `function`.
클래스 필드 화살표 click = () => …는 인스턴스에 this를 묶는 패턴으로 쓰이지만, 프로토타입 공유는 안 된다.
표
예제 — 왜 이렇게 쳐 봤냐
const counter = {
n: 0,
inc() { this.n += 1; return this.n; },
};
const { inc } = counter; // 분해하는 순간 메서드 분리
// inc(); // this 분실
const incBound = counter.inc.bind(counter);
console.log(incBound()); // 1
function once(fn) {
let done = false;
return function (...args) {
if (done) return;
done = true;
return fn.apply(this, args); // 호출부의 this 전달
};
}
WHY: 고차 함수가 this를 삼키지 않으려면 apply/call로 되넘겨야 한다.
헷갈리던 장면 / 함정
- 스코프와 this를 같은 문제로 묶기
- 콜백에 메서드 참조만 전달
- 화살표를 메서드로 사용
- 화살표에 bind로 this를 바꾸려 함
- class 메서드를 분해해서 전달
- 슬롭 모드 전역 this에 의존한 코드
질문 → 자답
Q. 클래스 필드 화살표 vs 프로토타입 메서드? A. 필드 화살표는 인스턴스마다 함수, this 고정에 유리. 프로토타입 메서드는 공유·메모리에 유리, 전달 시 bind/화살표 래핑 필요.
Q. bind는 항상 새 함수?
A. 예. 참조 동일성(===)이 깨지므로 add/removeEventListener 쌍에 같은 참조를 써야 한다.
Q. this를 아예 안 쓰는 법은? A. 클로저로 필요한 값만 캡처, 또는 명시 인자로 객체 전달. 팀 컨벤션 문제.
웹 보강 메모
MDN this 표와 bind 페이지의 “bound function” 설명을 다시 읽었다. React/DOM 코드에서 this.method 전달·class 필드 화살표 사용처를 검색해 일관성을 점검했다.
실무로 바로
- 리스너:
() => obj.method()또는bind, 제거 시 같은 참조 - strict/ESM 전제 — 일반 호출 this=undefined
- 메서드 정의는 축약/
function, 콜백 안 화살표로 렉시컬 this - 리뷰 체크: “이 함수 this 기댓값?”
여기서 잠깐 💁♂️
23장은 실행 컨텍스트 — this 바인딩이 컨텍스트 구성 요소로 들어가는 이야기다. 22에서 호출 지도를 외우고 들어가면 덜 아프다.