함수가 그냥 실행되는 게 아니라, "누가 호출했냐"에 따라 표정이 바뀌는 존재라는 걸 깨닫기 시작하면 나오는 삼총사.
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를 바꿔도 기대한 대로 동작하지 않을 수 있다.
- 화살표 함수는 자신의 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 |