[JS] call / apply / bind

javaScript 기초를 탄탄하게 다져보자

Posted by lim.Chuck on December 23, 2024

[JavaScript]

기초

  1. [JS] Js기초
  2. [JS] Js기초 변수
  3. [JS] Js기초 자료형
  4. [JS] Js기초 형변환
  5. [JS] Js기초 연산자
  6. [JS] Js기초 반복문
  7. [JS] Js기초 switch
  8. [JS] Js기초 function
  9. [JS] Js기초 객체
  10. [JS] Js기초 배열

중급

  1. [JS] Js중급 호이스팅(Hoisting)과 TDZ(Temporal Dead Zone)
  2. [JS] Js중급 생성자함수
  3. [JS] 객체 메소드(Object methods), 계산된 프로퍼티(Computed property)
  4. [JS] 심볼(Symbol)
  5. [JS] WeakMap WeakSet
  6. [JS] 숫자, 수학 method (Number, Math)
  7. [JS] 문자열 메소드(String methods)
  8. [JS] 배열 메소드(Array methods)
  9. [JS] 구조 분해 할당 (Destructuring assignment)
  10. [JS] 매개변수 리스트와 전개 문법(Rest parameters and spread syntax)
  11. [JS] 클로저(Closure)
  12. [JS] setTimeout / setInterval
  13. [JS] call / apply / bind
  14. [JS] 상속, 프로토타입(Prototype)
  15. [JS] 클래스(Class)
  16. [JS] 클로미스(Promise)
  17. [JS] 비동기 처리(Async/Await)
  18. [JS] Generator
  19. [JS] 메모리 릭(Memory Leak)

call, apply, bind는 함수의 this 바인딩을 제어하는 메서드들이야! 🎯

1. call 메서드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 기본 사용법
function greet() {
  console.log(`안녕하세요, ${this.name}님!`);
}

const person = { name: "김코딩" };
greet.call(person); // "안녕하세요, 김코딩님!"

// 매개변수 전달
function introduce(age, occupation) {
  console.log(`저는 ${this.name}, ${age}${occupation}입니다.`);
}

introduce.call(person, 20, "개발자");
// "저는 김코딩, 20살 개발자입니다."

2. apply 메서드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// call과 비슷하지만 매개변수를 배열로 전달
function introduce(age, occupation) {
  console.log(`저는 ${this.name}, ${age}${occupation}입니다.`);
}

const person = { name: "김코딩" };
introduce.apply(person, [20, "개발자"]);
// "저는 김코딩, 20살 개발자입니다."

// 배열 메서드 활용
const numbers = [5, 6, 2, 3, 7];

// Math.max는 this를 사용하지 않으므로 null 전달
const max = Math.max.apply(null, numbers); // 7
const min = Math.min.apply(null, numbers); // 2

3. bind 메서드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// this를 영구적으로 바인딩
function greet() {
  console.log(`안녕하세요, ${this.name}님!`);
}

const person = { name: "김코딩" };
const boundGreet = greet.bind(person);

boundGreet(); // "안녕하세요, 김코딩님!"

// 부분 적용(Partial Application)
function multiply(a, b) {
  return a * b;
}

const multiplyByTwo = multiply.bind(null, 2);
console.log(multiplyByTwo(4)); // 8

4. 실전 활용 예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class EventEmitter {
  constructor() {
    this.events = {};
  }

  on(event, callback) {
    // callback의 this를 현재 인스턴스로 바인딩
    const boundCallback = callback.bind(this);

    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(boundCallback);
  }

  emit(event, ...args) {
    if (this.events[event]) {
      this.events[event].forEach((callback) => {
        callback.apply(this, args);
      });
    }
  }
}

// 사용 예시
class Logger extends EventEmitter {
  log(message) {
    this.emit("log", message);
  }
}

const logger = new Logger();

logger.on("log", function (message) {
  console.log(`[${this.constructor.name}] ${message}`);
});

logger.log("테스트 메시지");
// "[Logger] 테스트 메시지"

5. 메서드 빌려쓰기 패턴

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const calculator = {
  numbers: [],

  sum() {
    return this.numbers.reduce((acc, num) => acc + num, 0);
  },

  avg() {
    return this.sum() / this.numbers.length;
  },
};

const mathSet = {
  numbers: [1, 2, 3, 4, 5],
};

// calculator의 메서드를 mathSet에서 사용
console.log(calculator.sum.call(mathSet)); // 15
console.log(calculator.avg.call(mathSet)); // 3

// Array 메서드 빌려쓰기
const arrayLike = {
  0: "a",
  1: "b",
  2: "c",
  length: 3,
};

// Array.prototype.slice를 빌려써서 진짜 배열로 변환
const array = Array.prototype.slice.call(arrayLike);
console.log(array); // ['a', 'b', 'c']

6. 커스텀 바인딩 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Component {
  constructor(name) {
    this.name = name;

    // 메서드의 this 바인딩 유지
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    console.log(`${this.name} 클릭됨!`);
  }

  // 화살표 함수를 사용하면 자동으로 this가 바인딩됨
  handleHover = () => {
    console.log(`${this.name} 호버됨!`);
  };
}

const button = new Component("버튼");

// DOM 이벤트 리스너로 사용
const element = document.createElement("button");
element.addEventListener("click", button.handleClick);
element.addEventListener("mouseover", button.handleHover);

꿀팁! 🍯

  1. call vs apply vs bind
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function greet(greeting, punctuation) {
  console.log(`${greeting}, ${this.name}${punctuation}`);
}

const person = { name: "김코딩" };

// call: 매개변수를 쉼표로 구분
greet.call(person, "안녕하세요", "!");

// apply: 매개변수를 배열로 전달
greet.apply(person, ["안녕하세요", "!"]);

// bind: 새로운 함수를 반환
const boundGreet = greet.bind(person, "안녕하세요");
boundGreet("!");
  1. 성능 최적화
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// BAD: 매번 새로운 바인딩
element.addEventListener('click', function() {
    this.handleClick();
}.bind(this));

// GOOD: 미리 바인딩
constructor() {
    this.handleClick = this.handleClick.bind(this);
}
element.addEventListener('click', this.handleClick);

// BETTER: 화살표 함수 사용
handleClick = () => {
    // this가 자동으로 바인딩됨
};

이제 call, apply, bind는 마스터! 😎 this 바인딩을 자유자재로 다룰 수 있을 거야!