본문 바로가기
  • soldonii's devlog
Javascript 공부/Advanced Javascript(-)

자바스크립트의 함수 호출 : call, apply, bind

by soldonii 2019. 9. 7.

*Udemy의 "Advanced Javascript Concepts"강의에서 학습한 내용을 정리한 포스팅입니다.

*자바스크립트를 배우는 단계라 오류가 있을 수 있습니다. 틀린 내용은 댓글로 말씀해주시면 수정하겠습니다. 감사합니다. :)


1. .call(), .apply(), .bind()

this를 자유자재로 조작하기 위해서는 .call(), .apply(), .bind() 메소드의 사용법에 익숙해져야 한다.

 

# .call()

function a() {
  console.log('hi');
}
a.call();

 

.call()과 .apply()는 모두 첫번째 인자로 실행할 대상 함수의 this 값을 대체할 다른 객체를 받는다. 

const wizard = {
  name: "Merlin",
  health: 100,
  heal() {
    return this.health = 100;
  }
}

const archer = {
  name: "Robin Hood",
  health: 30
}

wizard.heal();

 

이 코드를 보면, archer 객체의 경우 'health'가 30밖에 되지 않아 heal을 받고 싶지만 archer 객체에는 해당 기능을 가진 프로퍼티가 없기 때문에 이 때 wizard 객체로부터 해당 기능을 빌려올 수 있다. wizard 객체에 한 번만 해당 기능을 저장해 놓으면, 코드를 매번 반복하지 않아도 다른 객체에서 wizard 객체의 메소드를 빌려와서 쓸 수 있기 때문에 버그의 발생 가능성이 줄어들 뿐 아니라 코드가 DRY하게 된다.

 

const wizard = {
  name: "Merlin",
  health: 100,
  heal() {
    return this.health = 100;
  }
}

const archer = {
  name: "Robin Hood",
  health: 30
}

console.log('1', archer); // 1 {name: "Robin Hood", health: 30}
wizard.heal.call(archer);
console.log('2', archer); // 2 {name: "Robin Hood", health: 100}

 

wizard.heal.call(archer)를 해석해보자.

1) wizard 객체에 존재하는 프로퍼티 중 하나인 heal 함수(메소드)를 가져온 뒤에,

2) .call을 함으로써 heal 메소드를 실행시키게 된다.

3) 이 때, 실행 시에 heal 함수 내부에 존재하는 this의 값은 인자로 전달 받은 archer로 치환된다.

4) 따라서 heal 함수를 실행하면서 내부에서 return this.health 에서 this가 archer가 되므로 archer.health = 100과 같은 의미가 된다.

5) 최종적으로 archer 객체의 프로퍼티인 heal이 30에서 해당 함수의 실행 이후 100으로 변경된다.

 

# .apply()

.apply()는 기본적으로 .call()과 동일한 기능이다. 다만 .call()의 경우 첫번째 인자로 this를 대체할 객체를 전달한 뒤에 두번째 인자 이후부터는 해당 함수를 실행할 때 받아야 하는 인자를 하나 하나 넣는 반면, .apply()는 인자를 배열의 형태로 한 번만 전달한다는 점이 다르다.

const wizard = {
  name: "Merlin",
  health: 100,
  heal(num1, num2) {
    return this.health += num1 + num2;
  }
}

const archer = {
  name: "Robin Hood",
  health: 30
}

console.log('1', archer); // 1 {name: "Robin Hood", health: 30}
wizard.heal.call(archer, 50, 30);
wizard.heal.apply(archer, [50, 30]);
console.log('2', archer); // 2 {name: "Robin Hood", health: 110}

 

wizard 객체 내의 메소드 heal이 num1, num2 두개의 파라미터를 받도록 되어 있다. 이 경우에 다른 객체에서 이 함수를 가져다 쓸 때, 예시처럼 .call()을 사용하면 wizard.heal.call(archer, 50, 30) 이렇게 num1과 num2의 인자를 50, 30 각각 전달한다. 반면 .apply()를 사용하면 wizard.heal.apply(archer, [50, 30])이렇거 배열에 [num1, num2]를 담아서 전달한다. 결과물은 동일하다.

 

# .bind()

.call(), .apply()와 동일하게 다른 객체에 존재하는 함수를 가져와 또 다른 객체에서 사용할 수 있지만, .call()과 .apply()는 해당 함수를 즉시 실행시키는 반면, .bind()는 함수를 실행시키지 않고 새로운 함수를 리턴시킨다.

함수를 바로 실행하는 대신 특정 변수에 담아두었다가, 특정 조건이 성립되었을 때, 또는 필요할 때 이 변수를 불러와서 실행하고 싶을 경우 .bind()를 사용하면 좋다.

 

const wizard = {
  name: "Merlin",
  health: 100,
  heal(num1, num2) {
    return this.health += num1 + num2;
  }
}

const archer = {
  name: "Robin Hood",
  health: 30
}

console.log('1', archer); // 1 {name: "Robin Hood", health: 30}
const healArcher = wizard.heal.bind(archer, 100, 30);
healArcher();
console.log('2', archer); // 2 {name: "Robin Hood", health: 160}

 

healArcher라는 변수에 bind한 결과값(리턴되는 함수)를 담아두었다. 이후 필요에 따라서 이 변수를 참조하여 함수를 실행시키면 된다.

 

2. bind() and currying

// function currying
function multiply(a, b) {
  return a * b;
}

 

# 커링

커링이란, 어떤 함수가 여러 개의 매개변수를 받아야 할 경우에, 함수 실행 시 한 번에 여러 개의 매개변수를 전달하는 것 대신 한 번에 한 개의 매개변수만 전달하여 함수 실행을 여러 단계로 나누는 함수 사용 테크닉이다.

 

// function currying
function multiply(a, b) {
  return a * b;
}

let multiplyByTwo = multiply.bind(this, 2);
multiplyByTwo(4); // 8

let multiplyByTen = multiply.bind(this, 10);
multiplyByTen(4); // 40

 

multiply 함수는 2개의 변수를 받아야 하는데, .bind()를 이용하여 한 개의 변수만을 전달했을 때 리턴되는 함수를 multiplyByTwo 변수에 담아 둔 뒤, multiplyByTwo()변수 실행시 나머지 한 개의 변수를 추가로 전달해서 사용하는 테크닉이다.

 

커링을 사용하는 이유는, 특정 코드 조각을 추후에 재사용할 수 있기 때문이다.

var b = {
  name: 'jay',
  say() {console.log(this);} // this는 b 객체
}
var c = {
  name: 'jay',
  say() {return function() {console.log(this);}} // this는 window 객체(dynamically scoped)
}
var d = {
  name: 'jay',
  say() {return () => console.log(this);} // this는 d 객체
}

 

3. context vs. scope

컨텍스트와 스코프는 용어가 혼동되기 때문에 한 번 정리하고 넘어가자.

 

1) 컨텍스트(context)

컨텍스트는 객체를 기반으로 한 용어이다. 함수가 어떻게 호출되는지에 따라서 this가 지칭하는 객체가 달라지게 되는데, 이렇게 현재 this가 무엇을 지칭하는지를 구분짓는 것이 컨텍스트이다. 만약 함수 a와 함수 b의 this가 서로 다른 객체를 지칭하고 있을 경우, 두 함수의 컨텍스트는 서로 다르다.

 

2) 스코프(scope)

스코프는 함수를 기반으로 한 용어이다. 특정 함수가 실행될 때, 해당 함수에서 변수에 대한 접근 범위가 어디까지인지를 나타내는 개념이 스코프이다.


 

4. 바닐라코딩 this 학습내용 추가

1) 'use strict'로 strict mode가 구동될 때에는, 일반적인 함수 실행(regular function call), 즉 foo() 이렇게 실행할 경우에, 만약 해당 함수가 글로벌 실행 컨텍스트에서 실행됐을 경우에 함수 내의 this는 undefined이다.(use strict를 쓰지 않을 경우에는 this는 window이다.)

 

function foo() {
  console.log(this);
}

new foo();

 

2) new 키워드를 이용하여 함수를 실행시키면 빈 객체가 생성된 뒤 this가 빈 객체에 할당된다. 또한 new로 함수를 실행하면 해당 함수 내에 return statement가 없어도 자동적으로 해당 함수의 this를 리턴시킨다. 아래의 경우 따라서 { name: '바닐라코딩' } 객체가 리턴된다. 그니까 결국에는 하나의 객체를 만들어서 변수에 담아주는 행위와 같다.(고 볼 수 있다?!)

 

function foo() {
  this.name = '바닐라코딩';
}

let vanillaCoding = new foo();
console.log(vanillaCoding);

댓글