명시적으로 this를 바인딩하는 방법
this
에 별도의 대상을 바인딩하는 방법이 있다고 합니다.
call
, apply
, bind
와 같은 함수를 사용하는 방법을 소개하겠습니다.
call 메서드
Function.prototype.call(thisArg[, arg1[, arg2[, ...]]])
call
메서드는 메서드의 호출 주체인 함수를 즉시 실행하는 명령어입니다.
이때, call
메서드의 첫번째 인자를 this
로 바인딩하고, 이후 인자들을 호출할 함수의 매개변수로 합니다.
apply 메서드
Function.prototype.apply(thisArg[, argsArray])
apply
메서드는 call
메서드와 기능적으로 동일합니다.
call
메서드와 다른 점은 두번째 인자를 배열로 받아 그 배열의 요소들을 호출한 함수의 매개변수로 지정합니다.
call / apply 메서드의 활용
유사배열객체(array-like object)에 배열 메서드를 적용
객체에는 배열 메서드를 직접 적용할 수 없습니다.
유사배열객체도 마찬가지 입니다. 하지만 call
이나 apply
메서드를 이용해서 배열 메서드를 차용할 수 있습니다.
유사배열객체란 키가 0이상의 정수인 프로퍼티가 존재하고 length 프로퍼티의 값이 0 또는 양의 정수인 객체입니다.
var obj = {
0: 'a',
1: 'b',
2: 'c',
length: 3,
};
Array.prototype.push.call(obj, 'd'); // Add 'd' to obj
var arr = Array.prototype.slice.call(obj); // copy obj to array
위 코드와 같이 배열 메서드인 push를 객체 obj에 적용해서 프로퍼티 3
에 d
를 추가할 수 있습니다.
또한, slice 배열 메서드를 적용해 객체를 배열로 전환할 수 있습니다.
이외에도 arguments
, NodeList
에 모든 배열 메서드를 적용할 수 있습니다.
더 나아가, 배열과 같이 인덱스와 length
프로퍼티를 지니는 문자열에 대해서도 마찬가지로 call
,apply
를 활용해 배열 메서드를 적용할 수 있습니다. 그러나 문자열은 length
프로퍼티가 읽기 전용이기 때문에 원본 문자열에 변경을 가하는 메서드는 에러가 나고, 대상이 반드시 배열이어야 하는 경우에는 제대로 작동하지 않습니다.
사실 이러한 call
, apply
를 통해 형변환하는 것은 this와는 다소 동떨어진 방법입니다. 그래서 ES6에서는 순회가능한 모든 데이터 타입을 배열로 전환하는 Array.from
이라는 메서드를 새로 도입했습니다.
var obj = {
0: 'a',
1: 'b',
2: 'c',
length: 3,
};
var arr = Array.from(obj);
console.log(arr); // ['a', 'b', 'c']
생성자 내부에서 다른 생성자 호출
function Person(name, gender) {
this.name = name;
this gender = gender;
}
function Student(name, gender, school) {
Person.call(this, name, gender);
this.school = school;
}
function Employee(name, gender, company) {
Person.apply(this, [name, gender]);
this.company = company;
}
var kim = new Student('김김김', 'male', 'High School');
var lee = new Student('이이이', 'female', 'Middle School');
위 코드와 같이 생성자 내부에서 다른 생성자와 공통된 내용이 있을 경우 call
또는 apply
메서드를 이용해 다른 생성자를 호출하면 간단하게 반복을 줄일 수 있습니다.
여러 인수를 묶어 하나의 배열로 전달하고 싶을 때 - apply 활용
var numbers = [10, 20, 3, 16, 45];
var max = Math.max.apply(null, numbers);
var min = Math.min.apply(null, numbers);
여러개의 인수를 받는 메서드에게 하나의 배열로 인수들을 전달하고 싶을 때 apply
메서드를 사용하면 코드를 더 간단하게 작성할 수 있습니다.
const numbers = [10, 20, 3, 16, 45];
const max = Math.max(...numbers);
const min = Math.min(...numbers);
참고로 ES6에서는 펼치기 연산자로 간단하게 작성 가능합니다.
call
, apply
메서드는 명시적으로 별도의 this
를 바인딩하면서 함수 또는 메서드를 실행하는 좋은 방법이지만 오히려 이로 인해 this
를 예측하기 어렵게 만들어 코드 해석을 방해한다는 단점이 있습니다. 그럼에도 불구하고 ES5이하의 환경에서는 마땅한 대안이 없어서 실무에서 광범위하게 사용됩니다.
bind 메서드
Function.prototype.bind(thisArg[, arg1[, arg2[, ...]]])
bind
는 ES5에서 추가된 기능으로, call
와 비슷하지만 함수를 즉시 호출하지 않고 넘겨받은 this
및 인수들을 바탕으로 새로운 함수를 반환하는 메서드입니다.
var func = function (a, b, c, d) {
console.log(this, a, b, c, d);
};
func(1, 2, 3, 4); // Window 1 2 3 4
var bindFunc1 = func.bind({ x: 1 });
bindFunc1(5, 6, 7, 8); // { x:1 } 5 6 7 8
var bindFunc2 = func.bind({ x: 1 }, 4, 5);
bindFunc2(6, 7); // { x:1 } 4 5 6 7
bindFunc2(8, 9); // { x:1 } 4 5 8 9
다시 새로운 함수를 호출할 때 인수를 넘기면 그 인수들은 기존 bind
메서드를 호출할 때 전달했던 인수들의 뒤에 이어서 등록됩니다.
bind
메서드는 함수에 this
를 미리 적용하는 것과 부분 적용 함수를 구현하는 두 가지 목적을 가집니다.
name 포로퍼티
bind
메서드를 적용해서 새로 만든 함수는 name
프로퍼티에 'bound'라는 접두어가 붙습니다. 이를 통해 기존의 call
나 apply
보다 코드를 추적하기 더 수월하게 해줍니다.
상위 컨텍스트의 this를 내부함수나 콜백 함수에 전달하기
메서드의 내부함수에서 메서드의 this
를 그대로 바라보게 하는 방법에는 self 변수를 만드는 방법도 있지만, 보다 깔끔하게 call
, apply
, bind
를 쓰는 방법도 있습니다.
var obj = {
outer: function () {
console.log(this); // outer
var innerFunc = function () {
console.log(this); // outer
};
innerFunc.call(this);
},
};
obj.outer();
var obj = {
outer: function () {
console.log(this); // outer
var innerFunc = function () {
console.log(this); // outer
}.bind(this);
innerFunc();
},
};
obj.outer();
콜백 함수를 인자로 받는 함수나 메서드 중에서 기본적으로 콜백함수 내애서의 this
에 관여하는 함수 또는 메서드에 대해서도 bind
메서드를 이용하면 this
의 값을 조작할 수 있습니다.
var obj = {
logThis: function () {
console.log(this);
},
logThisLater1: function () {
setTimeout(this.logThis, 500);
},
logThisLater2: function () {
setTimeout(this.logThis.bind(this), 1000);
},
};
obj.logThisLater1(); // Window
obj.logThisLater2(); // logThis
화살표 함수의 예외사항
ES6에서 새롭게 도입된 화살표 함수는 실행 컨텍스트 생성 시 this
를 바인딩하는 과정이 빠졌습니다. 즉 함수 내부에는 this
가 없고, 접근하고자 하면 스코프체인상 가장 가까운 this
에 접근합니다.
var obj = {
outer: function () {
console.log(this); // outer
var innerFunc = () => {
console.log(this); // outer
};
innerFunc();
},
};
obj.outer(); // outer
별도의 인자로 this를 받는 경우(콜백 함수 내에서의 this)
콜백 함수를 인자로 받는 메서드 중 일부는 추가로 this
로 지정할 객체를 인자로 지정할 수 있는 경우가 있습니다. 이러한 메서드를 이용하면 콜백함수 내부에서 this
값을 원하는 대로 변경할 수 있습니다.
주로 반복 수행하는 배열 메서드에 많이 포진되어 있으며, 같은 이유로 ES6에 새로 나온 Set
, Map
등의 메서드에도 일부 존재합니다. 대표적으로 forEach
가 있습니다.
var report = {
sum: 0,
count: 0,
add: function () {
var args = Array.prototype.slice.call(arguments);
args.forEach(function (entry) {
this.sum += entry;
++this.count;
}, this);
},
average: function () {
return this.sum / this.count;
},
};
report.add(60, 85, 95);
console.log(report.sum, report.count, report.average()); // 240 3 80
콜백 함수를 실행할 때, 콜백 함수 내부에서의 this
는 forEach
함수의 두번째 인자로 전달해준 this
가 바인딩 됩니다.