bind 메소드

feelslikemmmm·2020년 10월 20일
0

javascript

목록 보기
27/37
post-thumbnail

bind 메소드


.bind.call과 유사하게 this 및 인자를 바인딩하나, 당장 실행하는 것이 아닌 바인딩된 함수를 리턴하는 함수입니다.

첫번째 인자는 this, 두번째 인자부터는 필요한 파라미터를 전달합니다.

fn.bind(this, 인자1, 인자2, ...)

bind는 call, apply에 비해 비교적 유용한 사용 예가 많이 존재합니다. 두 가지 예를 먼저 살펴봅시다.

case 1: 이벤트 핸들러

bind는 이벤트 핸들러에서 이벤트 객체 대신 다른 값을 전달하고자 할 때 유용합니다. 아래와 같은 상황을 가정해봅시다.

<button id="btn">클릭하세요</button>
let btn = document.querySelector('#btn')
btn.onclick = handleClick

function handleClick() {
  console.log(this)
}

질문

  • 위의 예제에서, handleClick에서 확인하는 this 값은 무엇일까요?

위의 예제에서 bind를 써서 this를 변경하는 방법을 알아봅시다.

let btn = document.querySelector('#btn')
// 추후 이벤트에 의해 불리게 될 함수에서, this는 {hello: 'world'}가 됩니다.
btn.onclick = handleClick.bind({ hello: 'world'})

function handleClick() {
  console.log(this)
}

조금 더 유용한 예제를 살펴봅시다. 동적으로 여러 개의 버튼을 만들고, 각각의 이벤트 핸들러에 각기 다른 값을 바인딩해야 할 경우를 생각해봅시다. (Twittler 스프린트에서 이와 비슷한 구현을 경험해보았을 것입니다.)

다음 예제에서 기대하는 결과는 각 버튼을 클릭할 때에, 이름이 콘솔로 표시되게 만드는 것입니다.

좀 더 유용한 예제

<div id="target"></div>
let target = document.querySelector('#target')
let users = ['김코딩', '박해커', '최초보']

users.forEach(function(user) {
  let btn = document.createElement('button')
  btn.textContent = user
  btn.onclick = handleClick
  target.appendChild(btn)
});
  
  
function handleClick() {
  console.log(this)
}

위와 같이 코드를 작성하면, 동적으로 생성되는 각각의 버튼을 클릭하면 button 엘리먼트 자체가 콘솔에 표시될 것입니다. 이 때 bind를 이용해 출력하고 싶은 값을 this로 넘기거나, 혹은 인자로 보낼 수 있습니다.

Solution 1:

let target = document.querySelector('#target')
let users = ['김코딩', '박해커', '최초보']

users.forEach(function(user) {
  let btn = document.createElement('button')
  btn.textContent = user
  btn.onclick = handleClick.bind(user) // 이렇게 바꿔볼 수 있습니다.
  target.appendChild(btn)
});
  
  
function handleClick() {
  console.log(this)
}

Solution 2:

let target = document.querySelector('#target')
let users = ['김코딩', '박해커', '최초보']

users.forEach(function(user) {
  let btn = document.createElement('button')
  btn.textContent = user
  btn.onclick = handleClick.bind(null, user) // 굳이 this를 이용하지 않더라도 인자로 넘길 수도 있습니다.
  target.appendChild(btn)
});
  
  
function handleClick(user) {
  console.log(user)
}

bind를 사용하지 않고 익명 함수로 문제를 해결할 수도 있습니다.

Solution 3:

let target = document.querySelector('#target')
let users = ['김코딩', '박해커', '최초보']

users.forEach(function(user) {
  let btn = document.createElement('button')
  btn.textContent = user
  btn.onclick = function() {
    handleClick(user)
  }
  target.appendChild(btn)
});
  
  
function handleClick(user) {
  console.log(user)
}

case 2: setTimeout

setTimeout은 시간 지연을 일으킨 후 함수를 비동기적으로 실행하게 하는 함수입니다. 이 함수는 명시적으로 항상 window 객체를 this 바인딩하는 특징이 있습니다. 그래서 다음과 같은 문제 상황이 발생할 수 있습니다.

예제

class Rectangle {
  constructor(width, height) {
    this.width = width
    this.height = height
  }
  
  getArea() {
    return this.width * this.height
  }

  printArea() {
    console.log('사각형의 넓이는 ' + this.getArea() + ' 입니다')
  }
  
  printSync() {
    // 즉시 사각형의 넓이를 콘솔에 표시합니다
    this.printArea()
  }
  
  printAsync() {
    // 1초 후 사각형의 넓이를 콘솔에 표시합니다
    setTimeout(this.printArea, 2000)
  }
}

let box = new Rectangle(40, 20)
box.printSync() // '사각형의 넓이는 800 입니다'
box.printAsync() // 에러 발생!

에러를 통해 this가 Rectangle의 인스턴스가 아니라는 것을 확인할 수 있습니다.

Uncaught TypeError: this.getArea is not a function
    at printArea (<anonymous>:12:36)

질문

  • 그렇다면, this 값은 무엇일까요? printArea 함수의 도입 부분에 console.log(this) 를 추가해 직접 확인해보세요.

이 문제를 해결하기 위해 bind를 이용할 수 있습니다. printAsync 부분을 다음과 같이 바꿔봅시다.

Solution 1:

printAsync() {
  // 1초 후 사각형의 넓이를 콘솔에 표시합니다
  setTimeout(this.printArea.bind(this), 2000)
}

익명 함수를 써야 한다면 다음과 같이 바꿀 수 있겠지만, 그리 깔끔한 방법은 아닙니다.

Solution 2 (권장되지 않음):

printAsync() {
  // 1초 후 사각형의 넓이를 콘솔에 표시합니다
  let self = this
  setTimeout(function() {
    self.printArea()
  }, 2000)
}

화살표 함수를 도입해봅시다. 다음은 잘 작동하는 예제입니다. 화살표 함수의 this는 뭔가 다르게 작동되고 있다는 사실을 알아내셨나요?

Solution 3:

printAsync() {
  // 1초 후 사각형의 넓이를 콘솔에 표시합니다
  setTimeout(() => {
    this.printArea()
  }, 2000)
}
profile
꾸준함을 잃지 말자는 모토를 가지고 개발하고 있습니다 :)

0개의 댓글