JavaScript Function.call / apply / bind - this

2025. 9. 9. 14:44·FrontEnd/JavaScript

함수가 그냥 실행되는 게 아니라, "누가 호출했냐"에 따라 표정이 바뀌는 존재라는 걸 깨닫기 시작하면 나오는 삼총사.
call, apply, bind 🧑‍💻

자바스크립트 함수는 단순히 "코드 묶음"이 아니라,
일급 객체라서 여기저기 넘기고, 변수에 담고, 메서드도 붙일 수 있다.

그중에서도 꽤 자주 이름이 나오는 애들이 있다.

  • Function.prototype.call
  • Function.prototype.apply
  • Function.prototype.bind

공통점은 하나다.

"이 함수가 실행될 때 this를 뭘로 볼지"를 내가 직접 정하는 도구

이번 글에서는 이 세 가지를 정리해보고,
실제로 언제 써먹는지까지 같이 정리해본다.


결론 먼저

한 줄로 요약하면 이렇게 볼 수 있다.

  • call / apply
    • 함수를 바로 실행하면서
    • "이번에 this는 이걸로 써" 하고 직접 지정하는 메서드
  • bind
    • 나중에 쓸 함수를 미리 만들어두면서
    • "이 함수는 앞으로 this를 이걸로 고정해"라고 약속하는 메서드

그리고 ES6 이후에는

  • 인수 배열을 펼칠 때 apply 대신 스프레드 연산자(...)를 많이 쓰게 됐고
  • 아예 this에 안 의존하는 순수 함수를 짜면 이런 메서드를 쓸 일이 거의 없다.

Function 객체와 this 제어

자바스크립트에서 함수는 일급 객체다.

  • 변수에 담을 수 있고
  • 다른 함수에 인자로 넘길 수 있고
  • 객체처럼 프로퍼티와 메서드를 가질 수 있다.

그래서 모든 함수는 내부적으로 Function.prototype을 상속받고,
그 덕분에 call, apply, bind 같은 메서드를 쓸 수 있다.

이 세 메서드는 공통적으로 이런 역할을 한다.

  • "이 함수를 실행할 때 this를 어떤 객체로 볼지 직접 정한다."

this를 잘 다룰수록

  • 메서드를 다른 객체에 빌려쓰거나
  • 이벤트 핸들러, 콜백에서 문맥을 잃지 않고
  • 공통 로직을 재사용하기 쉬워진다.

1. call - this와 인수를 직접 넘겨서 즉시 실행

call은 다음과 같이 생겼다.

fn.call(thisArg, arg1, arg2, ...);
  • thisArg: 이 함수 안에서 this로 쓸 대상
  • arg1, arg2, ...: 평소처럼 개별로 넘길 인수

예시를 보자.

function logUserInfo(action) {
  console.log(`[LOG] ${this.name} 님이 ${action}을(를) 수행했습니다.`);
}

const admin = { name: "관리자" };
const member = { name: "회원" };

logUserInfo.call(admin, "게시글 삭제");
// [LOG] 관리자 님이 게시글 삭제을(를) 수행했습니다.

logUserInfo.call(member, "댓글 작성");
// [LOG] 회원 님이 댓글 작성을(를) 수행했습니다.

여기서 logUserInfo는 원래 어느 객체에도 붙어 있지 않은 그냥 함수인데,
call을 쓰면 마치 admin.logUserInfo(...), member.logUserInfo(...)처럼
각각 다른 this를 가진 메서드처럼 동작하게 만들 수 있다.

언제 쓸까?

  • 특정 함수가 this를 기준으로 동작할 때
  • 같은 함수를 여러 객체에 빌려 쓰고 싶을 때
  • 즉시 실행하면서 this를 한 번만 바꾸면 될 때

2. apply - 인수만 배열로 받는 call

apply는 형태만 조금 다를 뿐, 하는 일은 call과 거의 같다.

fn.apply(thisArg, [arg1, arg2, ...]);
  • 첫 번째 인자: this로 쓸 대상
  • 두 번째 인자: 배열(또는 배열 비슷한 것)

예시를 보자.

function logUserInfo(action) {
  console.log(`[LOG] ${this.name} 님이 ${action}을(를) 수행했습니다.`);
}

const guest = { name: "게스트" };

logUserInfo.apply(guest, ["리뷰 열람"]);
// [LOG] 게스트 님이 리뷰 열람을(를) 수행했습니다.

원래는 이런 용도가 많았다.

Math.max.apply(null, [1, 2, 3]);
// 배열을 한 번에 넘기고 싶을 때

하지만 ES6 이후에는 이렇게 쓰는 경우가 많다.

Math.max(...[1, 2, 3]);

그래서 지금은 "인수가 배열로만 딱 있는 상황"이 아니라면
굳이 apply를 직접 쓰는 경우가 점점 줄어드는 편이다.

언제 쓸까?

  • 이미 배열로 모아 둔 값을 인자로 펼쳐야 할 때
  • 오래된 코드 스타일/라이브러리와 호환할 때

많은 경우에는 call + 스프레드(...) 조합으로도 충분히 대체할 수 있다.


3. bind - this를 고정한 함수를 미리 만들어 두기

bind는 앞의 둘이랑 조금 다르다.

  • call / apply: 지금 당장 실행
  • bind: 나중에 실행할 함수를 하나 새로 만들어서 돌려줌

기본 형태는 이렇게 생겼다.

const boundFn = fn.bind(thisArg, arg1, arg2, ...);
  • thisArg: 앞으로 이 함수에서 this로 쓸 대상
  • arg1, arg2, ...: 미리 고정해 둘 인수 (옵션)

예시를 보자.

function logUserInfo(action) {
  console.log(`[LOG] ${this.name} 님이 ${action}을(를) 수행했습니다.`);
}

const manager = { name: "매니저" };

const boundLog = logUserInfo.bind(manager);

boundLog("회원 정지");
// [LOG] 매니저 님이 회원 정지를(를) 수행했습니다.

중요한 점은, bind를 하면

  • this가 고정된 새 함수가 생기고
  • 그 함수를 나중에 이벤트 핸들러나 콜백으로 넘겨도 this가 유지된다는 것

이 패턴은 특히 다음 상황에서 많이 쓰인다.

  • 이벤트 핸들러에서 클래스 인스턴스의 this를 잃고 싶지 않을 때
  • 콜백 지옥 속에서도 this를 일정하게 유지하고 싶을 때

기타 Function 관련 프로퍼티도 가볍게만

함수 객체에는 이런 프로퍼티들도 있다.

  • length
    • 함수가 기대하는 인수 개수
  • name
    • 함수 이름 (익명 함수면 비어 있거나 추론된 이름)
  • toString()
    • 함수 소스를 문자열로 반환 (디버깅 용도 정도로만 보기)
  • new Function(...)
    • 런타임에 문자열로 함수를 만드는 기능
    • eval과 비슷한 보안/성능 이슈 때문에 실전에서는 거의 비추천

내 체크리스트

call / apply / bind를 실제로 쓸지 말지 고민할 때, 대충 이렇게 생각하면 된다.

  • 이 함수가 this를 실제로 쓰고 있는가?
    • this를 안 쓴다면 굳이 이런 메서드로 조작할 이유가 없다.
  • 바로 실행해야 하는가, 아니면 나중에 실행할 콜백인가?
    • 바로 실행 → call 또는 apply
    • 나중에 실행 → bind로 this를 고정한 함수 생성
  • 인수가 이미 배열로 모여 있는가?
    • 그렇다면 apply 또는 스프레드(fn.call(obj, ...args) 등)를 고려한다.
  • 화살표 함수인가?
    • 화살표 함수는 자신의 this를 가지지 않고,
      바깥 스코프의 this를 그대로 가져오기 때문에
      call / apply / bind로 this를 바꿔도 기대한 대로 동작하지 않을 수 있다.

오늘 내용 한 줄씩 다시 정리

  • call / apply / bind는 함수가 실행될 때의 this를 어떻게 볼지 정하는 도구다.
  • call / apply는 즉시 실행, bind는 나중에 쓸 함수를 미리 만들어 둔다.
  • ES6 이후에는 스프레드(...)와 화살표 함수 덕분에
    이런 메서드를 매일같이 쓰지는 않지만,
    "this가 왜 이걸 가리키지?" 같은 버그를 이해하는 데는 여전히 핵심 개념이다.
  • 가능하다면 this에 의존하지 않는 함수(순수 함수)를 우선으로 두고,
    정말 필요할 때만 call / apply / bind를 꺼내 쓰는 습관이 코드 퀄리티를 지켜준다.

정리하자면,
"함수 자체는 그대로 두고, 이번에는 누구 입장에서 실행할지 바꾸고 싶을 때"
call / apply / bind를 떠올리면 된다.

저작자표시 변경금지 (새창열림)

'FrontEnd > JavaScript' 카테고리의 다른 글

ES6 클래스 vs 생성자 함수 - 요즘 자바스크립트에서 뭘 써야 할까?  (0) 2025.09.09
DOM으로 HTML 다루기 - innerHTML, insertAdjacentHTML, createContextualFragment  (0) 2025.09.09
[실무 기록] 폼 실시간 공유 3편 – Yjs Awareness로 상대 커서,선택 상태 보여주기  (0) 2025.09.08
[실무 기록] 폼 실시간 공유 2편 - WebSocket 서버 구조랑 네임스페이스 적응기  (0) 2025.09.08
[실무 기록] 폼 실시간 공유 1편 CRDT, Yjs, WebSocket을 파보기까지  (0) 2025.09.08
'FrontEnd/JavaScript' 카테고리의 다른 글
  • ES6 클래스 vs 생성자 함수 - 요즘 자바스크립트에서 뭘 써야 할까?
  • DOM으로 HTML 다루기 - innerHTML, insertAdjacentHTML, createContextualFragment
  • [실무 기록] 폼 실시간 공유 3편 – Yjs Awareness로 상대 커서,선택 상태 보여주기
  • [실무 기록] 폼 실시간 공유 2편 - WebSocket 서버 구조랑 네임스페이스 적응기
프론트엔드 개발자 jbeat
프론트엔드 개발자 jbeat
프론트엔드 개발자 블로그인데 일상도 쪼그으믐
  • 프론트엔드 개발자 jbeat
    jbeat 님의 블로그
    프론트엔드 개발자 jbeat
  • 전체
    오늘
    어제
    • 분류 전체보기 (44)
      • FrontEnd (43)
        • TypeScript (6)
        • JavaScript (18)
        • Next.js (3)
        • React (1)
        • Testing (2)
        • Third Party (1)
        • web (10)
        • Tooling (1)
        • coding test (0)
        • A.I (1)
      • 일상 (1)
        • wedding (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 인기 글

  • 태그

    컬렉션
    CrossOrigin
    고차함수
    WebSocket
    이터러블
    타입스크립트
    Android
    yjs
    omit
    javascript
    pick
    TypeScript
    주니어
    Next.js
    playwright
    코테
    CRDT
    배열
    Utility
    preconnect
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
프론트엔드 개발자 jbeat
JavaScript Function.call / apply / bind - this
상단으로

티스토리툴바