바쁜 분은 이것만
this는 함수를 만든 곳이 아니라 부른 방식으로 정해진다 —obj.method()로 불러야 this가 obj- 메서드를 콜백으로 넘길 땐
() => obj.method()또는obj.method.bind(obj) - 메서드 안의 콜백은 화살표 함수로, 객체 메서드 자체는 화살표 함수로 만들지 않기
방금까지 잘 되던 메서드가 다른 함수에 넘기는 순간 이런 에러를 냅니다.
TypeError: Cannot read properties of undefined (reading 'count')
메서드 안의 this가 undefined가 되어서 this.count를 못 읽은 거예요. 어떤 경우에 this를 잃어버리는지, Node.js v26에서 하나씩 실행해 봤습니다. 파일은 .mjs(ES 모듈)로 실행했는데, 모듈과 클래스 안은 자동으로 strict 모드라 브라우저에서 type="module"이나 클래스를 쓸 때와 같은 조건이에요.
1. 메서드를 변수에 꺼내면 this가 사라진다
const user = {
name: "철수",
hello() { return "안녕, " + this.name; },
};
console.log("1)", user.hello());
const f = user.hello; // 메서드를 변수에 꺼내면
console.log("2)", f());
1) 안녕, 철수
file://~/work/p1.mjs:3
hello() { return "안녕, " + this.name; },
^
TypeError: Cannot read properties of undefined (reading 'name')
at hello (file://~/work/p1.mjs:3:34)
at file://~/work/p1.mjs:8:19
같은 함수인데 결과가 다릅니다. user.hello()처럼 점 앞에 객체를 붙여 부르면 this가 그 객체가 되고, f()처럼 그냥 부르면 this가 undefined예요. this는 함수를 어디에 적었는지가 아니라 어떻게 불렀는지로 정해집니다.
2. 가장 흔한 경우: 클래스 메서드를 콜백으로 넘길 때
class Counter {
constructor() { this.count = 0; }
inc() { this.count++; return this.count; }
}
const c = new Counter();
console.log("직접 호출:", c.inc());
const run = (fn) => fn(); // 콜백으로 넘기기
run(c.inc);
직접 호출: 1
file://~/work/p2.mjs:3
inc() { this.count++; return this.count; }
^
TypeError: Cannot read properties of undefined (reading 'count')
at inc (file://~/work/p2.mjs:3:11)
at run (file://~/work/p2.mjs:7:21)
스택을 보면 run 안에서 inc가 불렸고, 그때 this가 undefined였다는 게 보입니다.
run(c.inc)는 1번의 f = user.hello와 같습니다. 함수만 넘어가고 c는 따라가지 않아요. addEventListener, setTimeout, 배열의 map에 메서드를 그대로 넘길 때 다 같은 이유로 생깁니다. 해결은 두 가지예요.
// 위 코드에서 마지막 줄 run(c.inc)를 빼고 이어서 실행
console.log("bind:", run(c.inc.bind(c)));
console.log("화살표로 감싸기:", run(() => c.inc()));
bind: 2
화살표로 감싸기: 3
bind(c): this를 c로 고정한 새 함수를 만들어 넘깁니다.() => c.inc(): 넘기는 건 화살표 함수고, 그 안에서c.inc()로 점을 붙여 부르니 this가 c가 돼요. 읽기에는 이쪽이 더 쉽습니다.
3. 메서드 안의 콜백: function은 잃고, 화살표는 지킨다
const team = {
name: "A팀",
members: ["철수", "영희"],
listFn() {
return this.members.map(function (m) {
return m + "(" + (this ? this.name : this) + ")";
});
},
listArrow() {
return this.members.map((m) => m + "(" + this.name + ")");
},
};
console.log("function 콜백:", team.listFn());
console.log("화살표 콜백:", team.listArrow());
function 콜백: [ '철수(undefined)', '영희(undefined)' ]
화살표 콜백: [ '철수(A팀)', '영희(A팀)' ]
listFn 안의 this.members는 team인데, map에 넘긴 function 안으로 들어가면 this가 다시 undefined가 됩니다. map이 콜백을 그냥 fn(m)처럼 부르기 때문이에요. 화살표 함수는 자기 this가 없어서 바깥(listArrow)의 this를 그대로 씁니다. 메서드 안에서 콜백을 쓸 땐 화살표 함수가 기본이라고 보면 돼요.
4. 반대로, 객체 메서드 자체를 화살표로 만들면 안 된다
const obj = {
name: "객체",
arrow: () => typeof this === "undefined" ? "this는 undefined" : this,
normal() { return this.name; },
};
console.log("화살표 메서드:", obj.arrow());
console.log("일반 메서드:", obj.normal());
화살표 메서드: this는 undefined
일반 메서드: 객체
obj.arrow()처럼 점을 붙여 불러도 화살표 함수는 바깥 this를 씁니다. 여기서 바깥은 모듈 맨 위라 undefined예요. 객체 리터럴의 메서드는 normal() { } 형태로 쓰세요.
5. 에러도 안 나는 경우: setTimeout 안의 function
class Timer {
constructor() { this.sec = 0; }
startBad() {
setTimeout(function () { this.sec++; console.log("function:", this.sec); }, 10);
}
startGood() {
setTimeout(() => { this.sec++; console.log("화살표:", this.sec); }, 20);
}
}
const t = new Timer(); t.startBad(); t.startGood();
setTimeout(() => console.log("최종 t.sec =", t.sec), 50);
function: NaN
화살표: 1
최종 t.sec = 1
이번엔 에러 없이 NaN이 나왔습니다. setTimeout은 콜백의 this를 undefined가 아닌 다른 객체로 넣어 주기 때문이에요. Node.js에서는 Timeout 객체, 크롬에서 같은 코드를 돌리면 window였습니다(크롬 152에서 this === window → true, 결과는 똑같이 NaN). 없는 속성 sec에 1을 더하니 NaN이 되고, 정작 Timer의 sec는 안 바뀌어요. 에러가 안 나니 오히려 찾기 어려운 버그입니다.
참고: 모듈이 아닌 옛 방식 스크립트에서는
// t6.cjs (strict 모드 아님)
function show() { return this === globalThis ? "globalThis" : String(this); }
console.log("non-strict 함수 단독 호출:", show());
non-strict 함수 단독 호출: globalThis
strict 모드가 아니면 그냥 부른 함수의 this가 undefined 대신 전역 객체(브라우저에선 window)가 됩니다. 그래서 같은 실수를 해도 에러 대신 엉뚱한 값이 나와요. 이 글의 1~4번이 에러로 바로 드러난 건 모듈·클래스가 strict 모드이기 때문입니다.
정리
| 부르는 방식 | this |
|---|---|
obj.method() |
obj |
const f = obj.method; f() |
undefined (strict) / 전역 객체 (non-strict) |
| 콜백으로 넘긴 메서드 | 위와 같음 → bind 또는 () => obj.method() |
| 화살표 함수 | 만든 곳 바깥의 this |
| setTimeout에 넘긴 function | Node: Timeout 객체 / 브라우저: window |
참고: MDN — this, Arrow function expressions, Function.prototype.bind(), Strict mode
