알다가도 모르겠는 apply, call 그리고 bind와 this

geonmo-nine
7 min readAug 15, 2018

### 들어가기에 앞서

사실 위의 apply, call, bind 세 메서드는 모두 `Function.prototype`의 매서드입니다. 따라서 모든 함수는 apply, call, bind 와 같은 메서드를 사용할 수 있습니다.

prototype에 대해 어려움을 느끼신다면 꼭 한번 제대로 이해하고 넘어가시길 추천합니다.

## Apply

### 정체

Apply 메서드는 위에서 언급한 것과 같이 어떤 함수에서도 사용할 수 있습니다.

(모든 함수는 `Function.prototype`을 상속하고 prototype chaining을 통해 접근가능)

apply를 사용하기 위해 가벼운 예시를 들어보겠습니다.

const developer = {  coffee: ‘없음’,  doIhaveCoffee() { console.log(this.coffee) }}function getCoffee() {  console.log(this)  this.coffee = “콜드브루”}developer.doIhaveCoffee() // ‘없음’getCoffee()//”window”, strict mode에서는 “undefined”

여기서 `doIhaveCoffee`는 객체의 함수(메서드)이므로 `this`는 `developer`객체를 의미합니다.

반면에 `getCoffee`는 함수(객체의 메서드가 아님)이므로 `this` 는 전역객체(window)가 됩니다. `this`에 대해서는 조금있다 더 살펴보겠습니다.

- 사실 `getCoffee`는 window의 메서드라서 `this`가 window를 의미합니다.

자 이제 apply를 사용해봅시다.

getCoffee.apply(developer,null)developer.doIhaveCoffee() // ‘콜드브루’

### 무슨 일이 일어난 걸까요?

- `getCoffee`를 호출합니다. 하지만 이번에 `this`는 `develope`객체를 가르키고 있고 따라서 `developer`의 `coffee`를 “콜드브루”로 바꿉니다.

이제 apply 매서드를 찾아봅니다.

> MDN says :

Syntax: function.prototype.apply(thisArg, [argsArray])

Summary: apply() 메소드는 주어진 this값과 arguments로 함수를 호출

### apply는 함수 내부의 this를 바꾸는군요! 정체를 알게되었습니다.

### 언제 사용할까? 그리고 왜?

언제 사용할까요? 자주 쓰이는 예는 DOM을 조작할 때 입니다.

<div class=”divTags”>1</div><div class=”divTags”>2</div><div class=”divTags”>3</div><div class=”divTags”>4</div><div class=”divTags”>5</div>
<script>

const divNodeList = document.querySelector(‘.divTags’)
const divHTMLCollection = document.getElementsByClassName(‘divTags’)
divNodeList.forEach(item=> {console.log(item.innerHTML)})
divHTMLCollection.forEach(item=> {console.log(item.innerHTML)})
</script>

위 스크립트는 `Uncaught TypeError` 에러가 납니다. 왜일까요?

`querySelector`,`getElementsClassName`메서드의 반환값인 NodeList, HTMLCollection 은 Array가 아닌 객체이기 때문입니다.

> NodeList 와 HTMLCollection는 유사배열 객체라고 부릅니다. 차이는 기회가 되면 다루겠습니다.

따라서 배열의 매서드인 `forEach`, `map`, `reduce` 등을 사용할 수 없습니다.

우리는 종종 객체에 위와 같은 배열 메서드를 사용해야 할때가 있습니다. 방법이 없을까요?

### apply를 사용하여 해결

다시 위의 스크립트를 아래와 같이 바꾸어 봅시다.

<script>const divNodeList = document.querySelector(‘.divTags’)const divHTMLCollection = document.getElementsByClassName(‘divTags’)Array.prototype.forEach.apply(divNodeList,[(item=> {console.log(item.innerHTML)})])Array.prototype.forEach.apply(divHTMLCollection,[(item=> {console.log(item.innerHTML)})])</script>

- apply 의 첫째 인자에는 this로 받을 divNodeList, divHTMLCollection 를 넘겨주고 두번째 인자에는 사용할 메서드의 인자를 배열 형태로 넘겨줍니다.

(지금 예에서는 `forEach`의 인자가 함수이므로 두번째 인자로 함수를 넘겨줌)

### 잘 출력되나요?

그런데 apply함수의 두번째 인자가 1개인데 배열로 넘길필요가 없겠네요.

## Call 은 쉽다!

`call`매서드는 위의 apply 를 이해했다면 거저 이해할수 있는 매서드입니다.

인자를 배열로 보내지 않고 나열하기만 하면 정말 `끝` 입니다.

해보죠.

<script>const divNodeList = document.querySelector(‘.divTags’)const divHTMLCollection = document.getElementsByClassName(‘divTags’)Array.prototype.forEach.call(divNodeList,(item=> {console.log(item.innerHTML)}))Array.prototype.forEach.call(divHTMLCollection,(item=> {console.log(item.innerHTML)}))</script>

쉽죠?

이제 마지막 메서드를 살펴보기로 하죠.

## Bind

다시 올라가서 처음 예제로 돌아갑시다.

const developer = {coffee: ‘없음’,doIhaveCoffee() { console.log(this.coffee) }}function getCoffee() {console.log(this)this.coffee = “콜드브루”}

여기서 `getCoffee`를 호출하면 `window`를 출력할텐데요. `bind`함수를 한번 붙여보겠습니다.

const developerCoffee = getCoffee.bind(developer)developerCoffee() // developer

위와 같이 `bind` 함수는 인자로 받은 객체를 this로 하는 것 외에 동일한 동작을 하는 새로운 함수를 리턴합니다.

> 리액트에서 arrow function 을 사용하지 않는다면 자주 볼 수 있습니다.

## 이상 apply, call 그리고 bind 와 this 였습니다.

긴글 읽어주셔서 감사합니다.

> 위 설명에 오류나 궁금한 사항있으시면 언제든지 지적해주세요 :)

--

--