LogoSEO Jing
  • All Posts
  • SEO Jing
  • okayJing
  • KD Team
  • CLAB Coreteam
  • Study

Contact Me

© 2026 SEOJing. All rights reserved.

자바스크립트 퀴즈북 리마인드 Day 9: this는 호출한 쪽이 정한다 | SEOJing

자바스크립트 퀴즈북 리마인드 Day 9: this는 호출한 쪽이 정한다

2026년 7월 5일·15분 읽기

오늘의 질문: “객체 안에 있던 메서드를 콜백으로 넘기면 this도 같이 따라갈까?”

먼저 맞혀보기

js
const user = {
  name: "Jing",
  sayName() {
    return this.name;
  },
};

const say = user.sayName;

console.log(user.sayName());
console.log(say());

첫 번째는 "Jing"입니다. 두 번째는 strict mode 기준으로 TypeError가 나거나, 환경에 따라 전역 객체를 보면서 undefined처럼 보일 수 있습니다. 같은 함수처럼 보여도 호출 형태가 달라졌기 때문입니다.

this는 “함수가 어디에 적혀 있었는가”보다 함수가 호출될 때 왼쪽에 어떤 객체가 남아 있는가를 먼저 봅니다.

this 호출 지점 결정 흐름

틀리기 쉬운 직감: 메서드로 만들면 this가 고정된다

객체 안에 함수를 넣으면 this가 그 객체에 영구히 묶인다고 생각하기 쉽습니다. 하지만 자바스크립트에서 함수는 값입니다. 값을 변수에 담거나 콜백으로 넘기는 순간, 원래 객체와의 연결은 자동으로 따라가지 않습니다.

js
const profile = {
  name: "Jing",
  print() {
    console.log(this.name);
  },
};

setTimeout(profile.print, 0); // 의도한 this가 아니다

이 코드는 “나중에 profile.print()를 호출한다”가 아닙니다. profile.print라는 함수 값을 꺼내서 timer에게 넘긴 것입니다. timer가 나중에 그 함수를 어떻게 호출하는지는 profile 객체와 별개입니다.

리뷰할 때 보는 네 가지 호출 모양

호출 모양this 판단리뷰 질문
obj.method()this는 보통 obj왼쪽 객체가 호출 순간에 남아 있나?
const fn = obj.method; fn()메서드 연결이 사라짐값만 분리한 뒤 호출하지 않았나?
fn.call(obj) / bind(obj)명시적으로 this를 지정하거나 고정의도한 객체를 정말 고정해야 하나?
() => this.method()arrow가 자기 this를 만들지 않고 바깥 값을 캡처바깥 this가 안전한 위치인가?

this 문제는 대부분 문법 암기보다 “함수 값이 이동했는가?”를 추적하면 잡힙니다.

클래스 메서드도 자동으로 안전하지 않다

프론트엔드 코드에서 자주 보는 실수는 클래스나 객체 메서드를 이벤트 핸들러로 바로 넘기는 경우입니다.

js
class Counter {
  count = 0;

  increment() {
    this.count += 1;
  }
}

const counter = new Counter();
button.addEventListener("click", counter.increment);

increment는 Counter 안에 정의되어 있지만, 이벤트 리스너로 넘어갈 때는 함수 값만 전달됩니다. 그래서 this가 counter라고 보장되지 않습니다.

의도가 “항상 counter를 증가시킨다”라면 고정해야 합니다.

js
button.addEventListener("click", counter.increment.bind(counter));

또는 클래스 필드 arrow function처럼 처음부터 바깥 인스턴스를 캡처하는 방식도 있습니다.

js
class Counter {
  count = 0;

  increment = () => {
    this.count += 1;
  };
}

둘 중 무엇이 낫다는 규칙보다, 리뷰에서는 “이 함수가 어디로 전달되고, 호출자가 누구인가?”를 먼저 봐야 합니다.

arrow function은 만능 해결책이 아니다

arrow function은 자기만의 this를 만들지 않습니다. 이 말은 메서드로 쓰면 오히려 의도가 틀어질 수 있다는 뜻이기도 합니다.

js
const user = {
  name: "Jing",
  sayName: () => this.name,
};

여기서 this는 user가 아닙니다. 객체 리터럴이 새 this를 만들어 주지 않기 때문입니다. arrow function은 “호출 지점의 this를 무시하고 바깥 this를 쓰겠다”는 선택에 가깝습니다.

그래서 리뷰 기준은 단순합니다.

  • 객체의 동작을 메서드로 표현해야 하면 일반 메서드가 자연스럽다.
  • 콜백으로 넘겨도 인스턴스/상위 스코프를 보존해야 하면 arrow나 bind를 고려한다.
  • this를 쓰지 않아도 되면 아예 인자를 명시적으로 받는 함수가 더 읽기 쉽다.

bind는 this만 고정하지 않는다

bind를 보면 “this를 맞췄으니 해결”이라고 넘기기 쉽습니다. 하지만 bind는 새 함수 값을 만듭니다. 그래서 이벤트 등록/해제 코드에서는 함수 identity까지 같이 봐야 합니다.

js
button.addEventListener("click", user.print.bind(user));

// 나중에 제거하려고 해도 다른 함수 값이다.
button.removeEventListener("click", user.print.bind(user));

위 코드는 removeEventListener가 기대한 대로 동작하지 않습니다. 두 번의 bind가 서로 다른 함수 객체를 만들었기 때문입니다. 고정된 콜백을 변수나 필드에 보관해야 제거할 수 있습니다.

js
const handleClick = user.print.bind(user);

button.addEventListener("click", handleClick);
button.removeEventListener("click", handleClick);

클래스에서도 같은 문제가 생깁니다.

js
class Counter {
  count = 0;

  constructor(button) {
    this.handleClick = this.increment.bind(this);
    button.addEventListener("click", this.handleClick);
  }

  increment() {
    this.count += 1;
  }

  destroy(button) {
    button.removeEventListener("click", this.handleClick);
  }
}

리뷰 포인트는 두 겹입니다. 첫째, 호출될 때 this가 의도한 객체인가? 둘째, 그 함수 값이 등록·해제·memoization 경계에서 같은 identity로 유지되는가? 프론트엔드에서는 두 번째 질문이 빠지면 메모리 누수나 중복 이벤트 핸들러가 남습니다.

this와 closure는 서로 다른 해결책이다

Day 7에서 본 클로저는 함수가 만들어진 렉시컬 환경의 바인딩을 따라갑니다. this는 그와 다릅니다. 일반 함수의 this는 보통 호출 형태로 정해지고, arrow function은 아예 자기 this를 만들지 않고 바깥 this를 캡처합니다.

js
const panel = {
  id: "settings",

  openLater() {
    setTimeout(function () {
      console.log(this.id);
    }, 0);
  },
};

이 코드에서 timer 콜백의 this는 panel이 아닙니다. 바깥 메서드가 panel.openLater()로 호출됐다는 사실이 안쪽 일반 함수 호출까지 자동으로 전달되지 않기 때문입니다.

js
const panel = {
  id: "settings",

  openLater() {
    setTimeout(() => {
      console.log(this.id);
    }, 0);
  },
};

arrow callback을 쓰면 안쪽 함수는 바깥 openLater 실행의 this를 캡처합니다. 이 경우에는 자연스럽습니다. 하지만 객체 리터럴의 메서드 자체를 arrow로 만들면 이야기가 달라집니다. 객체 리터럴은 새 this를 만들지 않으므로, arrow가 캡처할 “객체 자신”이 없습니다.

그래서 리뷰할 때는 “arrow가 더 최신 문법인가?”가 아니라 “이 함수는 호출자가 정하는 this를 받아야 하는가, 생성 위치의 this를 붙잡아야 하는가?”로 물어야 합니다.

React 함수 컴포넌트에서는 this를 줄이는 쪽이 기본값이다

React 함수 컴포넌트에서는 대개 this를 직접 다루지 않습니다. 상태와 props는 렉시컬 바인딩으로 읽고, 이벤트 핸들러는 closure로 만들어집니다.

jsx
function SaveButton({ articleId, onSave }) {
  const handleClick = () => {
    onSave(articleId);
  };

  return <button onClick={handleClick}>저장</button>;
}

이 코드는 this를 고정할 필요가 없습니다. 필요한 값인 articleId와 onSave가 명시적으로 함수 안에 들어옵니다. 그래서 새 코드에서는 메서드의 this를 살리는 것보다 인자와 closure로 의도를 드러내는 편이 더 단순할 때가 많습니다.

하지만 라이브러리 어댑터나 DOM API를 감싸는 코드는 다릅니다.

ts
type ResizeCallback = (this: HTMLElement, rect: DOMRect) => void;

function onResize(element: HTMLElement, callback: ResizeCallback) {
  const observer = new ResizeObserver(() => {
    callback.call(element, element.getBoundingClientRect());
  });

  observer.observe(element);
  return () => observer.disconnect();
}

이런 API는 this를 계약으로 제공할 수도 있습니다. 이 지점은 Effective TypeScript Day 12의 public API 타입과 연결됩니다. 콜백이 this를 받는 API라면 문서와 타입, 실제 호출 방식이 모두 맞아야 합니다. 반대로 React props로 공개하는 callback이라면 this 없는 함수 타입이 더 안전합니다.

AI가 만든 코드에서 자주 보이는 실수

AI는 this 에러를 만나면 세 가지 방향으로 과하게 고치는 편입니다.

  1. 모든 메서드에 .bind(this)를 붙인다.
  2. 모든 메서드를 class field arrow로 바꾼다.
  3. 객체 리터럴의 메서드까지 arrow function으로 바꾼다.

셋 다 정답일 때가 있지만, 무조건 적용하면 다른 문제가 생깁니다. .bind는 함수 identity를 바꾸고, class field arrow는 인스턴스마다 함수를 만들며, 객체 리터럴 arrow는 객체 자신을 this로 받지 않습니다. 리뷰에서는 먼저 실패한 호출 형태를 찾고, 그 호출 형태를 유지할지 바꿀지 정해야 합니다.

작은 규칙으로 정리하면 이렇습니다.

상황우선 검토할 선택이유
객체의 동작을 메서드로 호출일반 메서드obj.method() 호출 형태가 의도를 잘 드러냄
메서드를 이벤트/타이머로 전달저장된 bound 함수 또는 wrapperthis와 함수 identity를 함께 관리
class callback이 인스턴스를 계속 봐야 함class field arrow 또는 constructor bind생성 시점의 인스턴스 this를 고정
React 함수 컴포넌트명시적 인자와 closurethis 없이 props/state 의존성을 읽음
public callback APIthis 타입 또는 this: void 명시사용자가 어떤 호출 방식을 기대해야 하는지 드러냄

프론트엔드 코드 리뷰 체크리스트

  • 메서드를 변수에 담거나 props/callback으로 넘기면서 this 연결이 끊기지 않았는가?
  • 이벤트 핸들러, timer, Promise callback 안에서 this가 실제 호출자를 기준으로 안전한가?
  • bind가 렌더마다 새 함수를 만들며 불필요한 리렌더나 removeEventListener 실패를 만들지 않는가?
  • arrow function을 객체 메서드처럼 쓰면서 this가 객체라고 착각하지 않았는가?
  • React 함수 컴포넌트라면 this 대신 명시적 인자와 closure로 더 단순하게 표현할 수 없는가?
  • 공개 callback API라면 this를 제공하는 계약인지, this를 쓰면 안 되는 계약인지 타입과 문서가 일치하는가?

this는 특별한 마법이라기보다 호출 규칙입니다. 코드 리뷰에서는 “이 함수가 정의된 자리”보다 “나중에 누가 어떤 모양으로 부르는지”를 따라가면 훨씬 덜 헷갈립니다.

연결해서 읽기

  • Day 7 closure 글과 같이 읽으면 좋습니다. closure는 생성 위치의 렉시컬 바인딩을 보고, 일반 함수의 this는 호출 형태를 봅니다.
  • Day 8 event loop 글과도 이어집니다. timer나 Promise callback으로 넘겨진 함수는 나중에 다른 호출 형태로 실행될 수 있습니다.
  • Effective TypeScript Day 12는 callback this를 public API 타입으로 어떻게 드러낼지 다룹니다.

Day 9 복습 퀴즈

Quiz1 / 5
Q.다음 코드에서 strict mode 기준으로 두 번째 호출이 위험한 이유는 무엇일까요?
js
const say = user.sayName;
say();

Post Q&A

오케이징에게 물어보기

자바스크립트 퀴즈북 리마인드 Day 9: this는 호출한 쪽이 정한다 전체를 기준으로 질문과 피드백을 받아요.답을 본 뒤에는 이 내용을 댓글로 달아서 서징에게도 물어볼 수 있어요. 작성자가 직접 볼 수 있어요!

0/500

포스트 목록

/study/javascript-quizbook
파일 13개, 폴더 0개
자바스크립트 퀴즈북 리마인드 Day 1: 숫자는 왜 가끔 믿을 수 없을까자바스크립트 퀴즈북 리마인드 Day 2: 같은 값인지 묻는 네 가지 방법자바스크립트 퀴즈북 리마인드 Day 3: + 연산자와 ToPrimitive 흐름자바스크립트 퀴즈북 리마인드 Day 4: 프로퍼티 descriptor와 freeze의 경계자바스크립트 퀴즈북 리마인드 Day 5: 참조, 복사, Map/Set의 메모리 감각자바스크립트 퀴즈북 리마인드 Day 6: 스코프 체인과 호이스팅자바스크립트 퀴즈북 리마인드 Day 7: 클로저 버그와 오래된 값자바스크립트 퀴즈북 리마인드 Day 8: 이벤트 루프와 Promise 타이밍자바스크립트 퀴즈북 리마인드 Day 9: this는 호출한 쪽이 정한다자바스크립트 퀴즈북 리마인드 Day 10: 함수는 값이고 경계다자바스크립트 퀴즈북 리마인드 Day 11: class를 봐도 프로토타입을 읽는다자바스크립트 퀴즈북 리마인드 Day 12: 이벤트는 target에서 끝나지 않는다자바스크립트 퀴즈북 리마인드 Day 13: 모듈은 파일 묶음이 아니라 의존성 그래프다

같은 섹션의 대표 이미지

13 posts · latest first
ESM live binding과 dynamic import, circular dependency 리뷰 포인트를 보여주는 module graph 다이어그램
Study26. 07. 09.

자바스크립트 퀴즈북 리마인드 Day 13: 모듈은 파일 묶음이.

ESM live binding, default/named export, circular dependency, dynamic import, CJS와 ESM 차이를 프론트엔드 코드 리뷰 관점에서 정리합니다.

26. 07. 09.SEOJing
DOM 이벤트가 capture 단계로 내려가고 bubble 단계로 올라가는 경로 다이어그램
Study26. 07. 08.

자바스크립트 퀴즈북 리마인드 Day 12: 이벤트는.

DOM 이벤트의 capture/bubble, delegation, default action, React synthetic event 경계를 프론트엔드 코드 리뷰 관점에서 정리합니다.

26. 07. 08.SEOJing
인스턴스에서 프로토타입과 Object.prototype으로 이어지는 프로퍼티 조회 경로 다이어그램
Study26. 07. 07.

자바스크립트 퀴즈북 리마인드 Day 11: class를 봐도.

프로토타입 조회, class 문법, 인스턴스 필드와 prototype 메서드 차이를 프론트엔드 코드 리뷰 관점에서 정리합니다.

26. 07. 07.SEOJing
입력, 순수 계산, 출력, 부수 효과 경계를 나누어 함수 설계를 보여주는 다이어그램
Study26. 07. 06.

자바스크립트 퀴즈북 리마인드 Day 10: 함수는 값이고 경계다.

고차 함수, 순수 함수, 부수 효과, async 함수의 Promise 반환을 코드 리뷰에서 어떻게 읽을지 정리합니다.

26. 07. 06.SEOJing
메서드 호출, 분리된 함수, 이벤트 콜백, bind와 arrow function의 this 차이를 보여주는 다이어그램
Study26. 07. 05.

자바스크립트 퀴즈북 리마인드 Day 9: this는 호출한 쪽이.

this 바인딩을 정의 위치가 아니라 호출 형태, 메서드 분리, arrow function, bind 기준으로 읽는 방법을 코드 리뷰 관점에서 정리합니다.

26. 07. 05.SEOJing
콜 스택이 비워진 뒤 마이크로태스크 큐가 먼저 실행되고 다음 태스크로 넘어가는 이벤트 루프 다이어그램
Study26. 07. 04.

자바스크립트 퀴즈북 리마인드 Day 8: 이벤트 루프와.

콜 스택, 태스크 큐, 마이크로태스크 큐를 기준으로 Promise와 setTimeout 실행 순서를 코드 리뷰 관점에서 정리합니다.

26. 07. 04.SEOJing
클로저가 생성 위치의 바인딩을 기억해 나중 실행에서 오래된 값을 읽을 수 있음을 보여주는 다이어그램
Study26. 06. 29.

자바스크립트 퀴즈북 리마인드 Day 7: 클로저 버그와 오래된.

클로저가 값 복사가 아니라 렉시컬 환경 참조라는 점을 stale closure, 루프 콜백, React 이벤트 리뷰 관점에서 정리합니다.

26. 06. 29.SEOJing
스코프 체인과 호이스팅 관계를 단순화한 다이어그램
Study26. 06. 28.

자바스크립트 퀴즈북 리마인드 Day 6: 스코프 체인과 호이스팅.

스코프 체인, 렉시컬 환경, var/let/const 호이스팅 차이를 콜백·상태 버그 리뷰 관점에서 다시 정리합니다.

26. 06. 28.SEOJing
JavaScript 객체 참조와 Map, Set, WeakMap의 참조 구조를 비교한 다이어그램
Study26. 06. 27.

자바스크립트 퀴즈북 리마인드 Day 5: 참조, 복사,.

객체 참조와 얕은 복사, Object와 Map/Set의 차이, WeakMap/WeakSet이 필요한 메모리 상황을 프론트엔드 코드 리뷰 관점에서 정리합니다.

26. 06. 27.SEOJing
JavaScript 프로퍼티 descriptor가 data descriptor와 accessor descriptor로 갈라지는 구조 다이어그램
Study26. 06. 26.

자바스크립트 퀴즈북 리마인드 Day 4: 프로퍼티.

객체 프로퍼티를 key/value가 아니라 descriptor로 읽는 법, getter/setter와 defineProperty, preventExtensions/seal/freeze의 경계를 코드 리뷰 관점에서 정리합니다.

26. 06. 26.SEOJing
객체가 ToPrimitive를 거쳐 + 연산자의 문자열 연결 또는 숫자 덧셈으로 갈라지는 흐름 다이어그램
Study26. 06. 25.

자바스크립트 퀴즈북 리마인드 Day 3: + 연산자와.

객체가 원시값으로 바뀌는 ToPrimitive 흐름, + 연산자의 문자열 연결과 숫자 덧셈 분기, Symbol.toPrimitive가 코드 리뷰에서 왜 중요한지 정리합니다.

26. 06. 25.SEOJing
==, ===, Object.is, SameValueZero의 차이를 정리한 JavaScript equality 다이어그램
Study26. 06. 24.

자바스크립트 퀴즈북 리마인드 Day 2: 같은 값인지 묻는 네.

==, ===, Object.is, SameValueZero가 각각 어떤 비교 알고리즘을 쓰는지 정리하고, includes와 indexOf, Map/Set 키 비교에서 생기는 프론트엔드 리뷰 포인트를 잡습니다.

26. 06. 24.SEOJing
JavaScript Number, safe integer, NaN, -0, BigInt를 한 장으로 정리한 다이어그램
Study26. 06. 23.

자바스크립트 퀴즈북 리마인드 Day 1: 숫자는 왜 가끔 믿을.

자바스크립트의 Number가 왜 정수처럼 보여도 부동소수점 모델 위에서 움직이는지, NaN과 -0, safe integer, BigInt를 프론트엔드 코드 리뷰 관점에서 다시 정리합니다.

26. 06. 23.SEOJing