함수는 단순히 소괄호를 열고 닫는 방법외에도, 메소드를 이용해 실행할 수도 있다.
function foo() {
return 'bar';
}
foo()
foo.call()
foo.apply()
.call, .apply 호출은 명시적으로 this를 지정하고 싶을 때 사용한다.
첫번째 인자가 항상 this 값이 된다.
// null을 this로 지정한다. Math는 생성자가 아니므로 this를 지정할 필요가 없다.
Math.max.apply(null, [5, 4, 1, 6, 2]) // 6
// spread operator의 도입으로 굳이 apply를 이용할 필요가 없어졌다.
Math.max(...[5, 4, 1, 6, 2]) // 6
// '피', '땀', '눈물'을 this로 지정한다.
''.split.call('피, 땀, 눈물', ',')
// 다음과 정확히 동일한 결과를 리턴한다.
'피, 땀, 눈물'.split(',')
let allDivs = document.querySelectAll('div') // Node.js라는 유사 배열이다.
// allDivs를 this로 지정한다.
[].map.call(allDivs, function(el) {
return el.clssName
})
// allDivs는 유사 배열이므로 map메소드가 존재하지 않는다.
// 그러나, Array prototype으로부터 map 메소드를 빌려와 this를 넘겨 map 을 실행할 수 있다.
.bind는 .call과 유사하게 this 및 인자를 바인딩하나, 당장 실행하는 것이 아닌 바인딩된 함수를 리턴하는 함수이다.
첫번째 인자는 this, 두번째 인자부터는 필요한 파라미터를 전달한다.
fn.bind(this값, 인자1, 인자2, ...)
bind는 call, apply에 비해 비교적 유용한 사용예가 많이 존재한다.
bind는 이벤트 핸들러에서 이벤트 객체 대신 다른 값을 전달하고자 할때 유용하다.
동적으로 여러 개의 버튼을 만들고, 각각의 이벤트 핸들러에 각기 다른 값을 바인딩해야 할 경우를 생각해보자.
다음 예제에서 기대하는 결과는 각 버튼을 클릭할 때에, alert에 사용자 정보가 표시되게 만드는 것이다.
예제
이때 bind를 이용해 인자를 지정하되, 즉시 실행되지 않게 만들 수 있다.
이 때에 this값은 중요하지 않으므로 null과 같이 아무것도 넘기지 않아도 된다.
Solution1
users.forEach(user => {
let btn = document.createElement('button');
btn.textContent = user.name;
btn.onclick = handleClick.bind(null, user);
target.appendChild(btn);
})
Solution2
// bind를 사용하지 않고 익명 함수로 문제를 해결할 수도 있다.
users.forEach(user => {
let btn = document.createElement('button');
btn.textContent = user.name;
btn.onclick = () => {
handleClick(user)
}
target.appendChild(btn);
})
setTimeout은 시간 지연을 일으킨 후 함수를 비동기적으로 실행하게 하는 함수이다.
이 함수는 명시적으로 항상 window객체를 this 바인딩하는 특징이 있다.
그래서 다음과 같은 문제 상황이 발생할 수 있다.
예제
class Rectangle {
construcotr(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의 인스턴스가 아니라는 것을 확인할 수 있다.
그럼 this 값은 무엇일까? printArea 함수 도입 부분에 console.log(this)를 추가해 확인해보면
이 문제를 해결하기 위해 bind를 이용할 수 있다. printAsync 부분을 다음과 같이 바꿔보자
Solution1
printAsync() {
// 1초 후 사각형의 넓이가 콘솔에 표시한다
setTimeout(this.printArea.bind(this), 2000)
}
Solution2
// 화살표 함수 도입
printAsync() {
// 1초 후 사각형의 넓이를 콘솔에 표시한다
setTimeout(() => {
this.printArea()
}, 2000)
}