JavaScript · 4장 객체
메서드와 this: 누가 불렀는지가 정해요
객체에는 값뿐 아니라 그 값을 다루는 함수도 넣을 수 있어요. 이 함수 안에서 "자기 객체"를 가리킬 때 쓰는 말이 this예요. this가 무엇을 가리킬지는 함수를 만들 때가 아니라 부를 때 정해져요. 이 한 가지 규칙이 여러 버그의 원인이 돼요.
01. 객체 안의 함수, 메서드
객체의 속성 값이 함수면 그 함수를 메서드라고 불러요. 이름() { … }처럼 줄여 쓰는 문법이 기본이에요. 메서드 안의 this는 점 앞에 있는 객체, 즉 그 메서드를 부른 객체를 가리켜요.
const cart = {
items: [{ name: '연필', price: 500 }, { name: '공책', price: 2000 }],
total() {
let sum = 0;
for (const item of this.items) sum += item.price;
return sum;
},
};
console.log(cart.total()); // 2500 — this는 cart
this 대신 cart.items라고 직접 써도 지금은 동작해요. 하지만 그러면 같은 메서드를 다른 객체가 쓸 수 없고, 변수 이름이 바뀌면 메서드도 고쳐야 해요. this는 "지금 나를 부른 객체"라서 그런 걱정이 없어요.
02. this는 부르는 순간 정해져요
같은 함수라도 어느 객체를 통해 부르느냐에 따라 this가 달라져요. 함수가 어디서 만들어졌는지는 상관없어요.
'use strict';
function introduce() {
return `저는 ${this.name}이에요`;
}
const a = { name: '하늘', introduce };
const b = { name: '가람', introduce };
console.log(a.introduce()); // 저는 하늘이에요
console.log(b.introduce()); // 저는 가람이에요
console.log(introduce()); // 점 앞에 객체가 없으면?
마지막 줄처럼 점 앞에 객체 없이 부르면 this는 undefined예요(엄격 모드 기준). 그래서 this.name을 읽다가 TypeError가 나요. 모듈과 클래스는 늘 엄격 모드라서(엄격 모드), 요즘 코드에서는 이 동작이 기본이라고 생각하면 돼요.
03. this를 잃어버리는 순간
실무에서 가장 많이 만나는 버그는 메서드를 객체에서 떼어 내서 쓸 때예요. 메서드를 변수에 담거나, 다른 함수에 콜백으로 넘기면 "점 앞의 객체"가 사라져요.
'use strict';
const user = {
name: '하늘',
greet() { console.log(`안녕, ${this?.name}`); },
};
user.greet(); // 안녕, 하늘
const greet = user.greet; // 메서드를 떼어 냈어요
greet(); // 안녕, undefined
setTimeout(user.greet, 50); // 안녕, (빈칸) — 이번에도 user가 아니에요
변수에 담아 부르면 this가 undefined예요. setTimeout은 넘겨받은 함수를 부를 때 this를 전역 객체(브라우저에서는 window)로 정해서, user와 상관없는 전역의 name(여기서는 빈 문자열)을 읽었어요. 어느 쪽이든 user가 아니라는 게 문제예요. 예제에서는 오류 대신 결과를 보려고 this?.name(옵셔널 체이닝)을 썼는데, 보통 코드라면 this.name에서 TypeError가 나거나 엉뚱한 값을 읽어요. 해결 방법은 두 가지예요.
- 화살표 함수로 감싸서 넘기기:
setTimeout(() => user.greet(), 50)— 안에서user.greet()로 부르니 점 앞에 user가 있어요. bind로 묶어 두기:setTimeout(user.greet.bind(user), 50)— this를 user로 고정한 새 함수를 만들어요(6장에서 자세히).
04. 화살표 함수에는 자기 this가 없어요
화살표 함수는 자기만의 this를 만들지 않고, 자기를 감싼 곳의 this를 그대로 써요. 이 성질 때문에 쓰임이 갈려요.
'use strict';
const timer = {
seconds: 0,
start() {
// 메서드 안의 화살표 함수: start의 this(timer)를 그대로 써요 — 좋아요
setTimeout(() => {
this.seconds++;
console.log('경과', this.seconds);
}, 50);
},
// 화살표 함수로 만든 메서드: 감싼 곳(객체 바깥)의 this를 써서 timer가 아니에요 — 피해요
whoAmI: () => this,
};
timer.start(); // 0.05초 뒤에 "경과 1"
console.log(timer.whoAmI() === timer); // false — 이 줄이 먼저 찍혀요
정리하면, 메서드는 이름() { } 문법으로 만들고, 메서드 안에서 넘기는 콜백은 화살표 함수로 쓰면 대부분의 this 문제를 피할 수 있어요.
1초마다 수를 세려는 카운터인데, 결과가 NaN이에요(실행 블록에서는 0.05초 간격으로 줄였어요). 원인을 찾아 1, 2, 3이 나오게 고쳐 보세요.
const counter = {
count: 0,
start() {
let n = 0;
const id = setInterval(function () {
this.count++;
console.log(this.count);
if (++n === 3) clearInterval(id);
}, 50);
},
};
counter.start();
해답 보기
setInterval은 넘겨받은 function을 부를 때 this를 전역 객체로 정해요. 그래서 this.count는 counter의 count가 아니라 전역의 count(없음 → undefined)이고, 여기에 ++를 해서 NaN이 됐어요. 화살표 함수로 바꾸면 자기 this를 만들지 않고 start의 this(counter)를 그대로 써요.
const id = setInterval(() => {
this.count++;
console.log(this.count); // 1, 2, 3
if (++n === 3) clearInterval(id);
}, 50);
05. 실전 포인트
- 언제 쓰나요? 객체가 자기 데이터를 다루는 동작을 가질 때 메서드와
this를 써요. 같은 모양의 객체를 여러 개 만든다면 클래스(9장)로 이어져요. - 대신 쓰는 것: 메서드를 콜백으로 넘길 땐
() => obj.method()로 감싸거나bind를 써요. 객체 메서드를 화살표 함수로 만들지 않아요. 화면 프레임워크에서는this없이 함수와 클로저로 상태를 다루는 방식이 많이 쓰여요(6장). - 흔한 실수:
button.addEventListener('click', obj.save)처럼 메서드를 그대로 넘기는 것, 콜백 안에서function을 써서 this가 바뀌는 것, 화살표 함수 메서드에서this를 쓰는 것이에요.
정리
- 객체 안의 함수가 메서드예요. 메서드 안의
this는 점 앞의 객체, 즉 부른 객체예요. this는 함수를 만들 때가 아니라 부를 때 정해져요. 점 앞에 객체가 없으면undefined(엄격 모드)예요.- 메서드를 변수에 담거나 콜백으로 넘기면
this를 잃어요. 화살표 함수로 감싸거나bind해요. - 화살표 함수는 감싼 곳의
this를 써요. 메서드 안 콜백엔 좋고, 메서드 자체로는 쓰지 않아요.
const f = obj.method; f();에서 this는 무엇일까요?
엄격 모드에서는 undefined예요. 점 앞에 객체 없이 불렀기 때문이에요. 메서드를 변수에 담는 순간 객체와의 연결이 끊긴다고 생각하면 돼요.
setTimeout(obj.save, 100)을 안전하게 고치는 방법 두 가지는?
setTimeout(() => obj.save(), 100)처럼 화살표 함수로 감싸거나, setTimeout(obj.save.bind(obj), 100)처럼 this를 묶어서 넘겨요.
객체 메서드를 화살표 함수로 만들면 왜 문제일까요?
화살표 함수는 자기 this가 없어서, 객체가 아니라 객체를 만든 바깥쪽의 this를 써요. 그래서 this.속성으로 자기 데이터를 읽을 수 없어요.