시대의 명작 코어 자바스크립트를 읽고 자바스크립트 마스터에 도전합니다...
상황별로 this에 값이 바인딩 되는 규칙을 깨고 별도의 대상을 바인딩 하는 방법도 있다.
Function.prototype.call(thisArg[, arg1[, arg2[, ...]]])
call 메서드는 메서드의 호출 주체인 함수를 즉시 실행하도록 하는 명령어이다. call 메서드의 첫번째 인자를 this로 바인딩하고, 이후의 인자들은 호출할 함수의 매개변수가 된다. 이렇게 하면 함수가 실행되어도 this가 전역객체를 참조하는 것이 아니라 임의의 지정된 객체를 바라보게 된다. 메서드로서 호출할 때도 call 메서드를 이용하면 this가 객체가 되는 것이 아니라 지정된 객체를 바라본다!!
// 함수로 호출할 때
var func = function (a, b, c) {
console.log(this, a, b, c);
};
func(1, 2, 3); // Window{ ... } 1 2 3
func.call({ x: 1 }, 4, 5, 6); // { x: 1 } 4 5 6
// 메서드로 호출할 때
var obj = {
a: 1,
method: function (x, y) {
console.log(this.a , x, y);
}
};
obj.method(2, 3); // 1 2 3
obj.method.call({ a: 4 }, 5, 6); // 4 5 6
apply 메서드는 call 메서드와 거의 동일하다고 볼 수 있다. 다만 apply 메서드는 두번째 인자를 배열로 받아 호출한 함수의 매개변수로 지정한다.
Function.prototype.apply(thisArg, [, argsArray])
유사배열객체는 call, apply 메서드를 사용해서 배열 메서드를 차용할 수 있다.
유사배열객체 : 키가 0 또는 양의 정수인 프로퍼티가 존재하고, length 프로퍼티의 값이 0 또는 양의 정수인 객체
var obj = {
0: 'a',
1: 'b',
length: 2
};
Array.prototype.push.call(obj, 'c');
console.log(obj); // { 0: 'a', 1: 'b', 2: 'c', length: 3 }
var arr = Array.prototype.slice.call(obj);
console.log(arr); // [ 'a', 'b', 'c' ]
function aaa () {
var args = Array.prototype.slice.call(arguments);
args.forEach(function (arg) {
console.log(arg);
});
}
aaa(1, 2, 3); // 1 2 3
: querySelectorAll, getElementsByClassName 등의 Node 선택자로 선택한 결과
document.body.innerHTML = '<div>a</div><div>b</div><div>c</div>';
var nodeList = document.querySelectorAll('div');
var nodeArr = Array.prototype.slice.call(nodeList);
nodeArr.forEach(function (node) {
console.log(node);
// <div>a</div>
// <div>b</div>
// <div>c</div>
});
* 문자열은 length 프로퍼티가 읽기 전용**이기 때문에 원본에 변경을 가하는 메서드 (push, pop, shift, unshift, splice ...)를 사용하면 에러가 뜬다. concat처럼 대상이 반드시 배열이여야 하는 경우에도 제대로 된 결과를 얻을 수 없다.
var str = 'mia lee';
Array.prototype.push.call(str, ' pushed string');
// 에러 발생!
Array.prototype.concat.call(str, 'zzang');
// [String {'mia lee'}, 'zzang']
// 원본에 변경을 가하지 않는 경우
Array.prototype.every.call(str, function(char) { return char !== ' ' }); // false
Array.prototype.some.call(str, function(char) { return char === ' ' }); // true
var newArr = Array.prototype.map.call(str, function(char) { return char + '!'; });
console.log(newArr); // ['m!', 'i!', 'a!', 'l!', 'e!', 'e!']
var newStr = Array.prototype.reduce.apply(str, [
function(string, char, i) { return string + char + i; }, ''
]);
console.log(newStr); // 'm0i1a2 3l4e5e6'
생성자에 공통된 내용이 있을 경우에 call
이나 apply
를 사용해서 중복을 줄일 수 있다.
function Person(name, age) {
this.name = name;
this.age = age;
}
function Student(name, age, school) {
Person.call(this, name, age);
this.school = school;
}
function Employee(name, age, company) {
Person.call(this, name, age);
this.company = company;
}
var jh = new Student('지형', '26', '홍익대');
var mia = new Employee('미아', '36', '구글');
배열에서 최대/최소값을 구하는 경우를 비교해보자.
var numbers = [10, 20, 3, 18, 93];
var max = min = numbers[0];
numbers.forEach(function(number) {
if (number > max) {
max = number;
}
if (number < min) {
min = number;
}
});
console.log(max, min);
var numbers = [10, 20, 3, 18, 93];
var max = Math.max.apply(null, numbers);
var min = Math.min.apply(null, numbers);
console.log(max, min);
와우..! 쏘 이지!!!
var numbers = [10, 20, 3, 18, 93];
var max = Math.max(...numbers);
var min = Math.min(...numbers);
console.log(max, min);
Function.prototype.bind(thisArg[, arg1[, arg2[, ...]]])
call
메서드와 거의 유사하지만, 바로 호출하지 않고 새로운 함수를 반환한다. 인자도 지정하여 부분으로 적용된 함수를 구현할 수 있다.
var func = function (name, surname) {
console.log(this, name, surname);
};
func('dongwon', 'kang'); // Window{ ... } dongwon kang
// this를 지정
var person1 = func.bind('great');
person1('mia', 'lee'); // great mia lee
// this를 지정하고 부분 적용 함수를 구현
var person2 = func.bind('great', 'jieun');
person1('lee'); // great jieun lee
bind 메서드를 적용해서 만든 함수에는 name 프로퍼티에 bound라는 접두어가 붇는다. 코드를 추적할때 유용하게 사용할 수 있다.
console.log(func.name); // func
console.log(person1.name); // bound func
self 등의 우회법 없이, call, apply, bind 메서드로 상위 컨텍스트의 this를 그대로 바라보게 할 수 있다.
// call 메소드 사용
var obj = {
outer: function() {
console.log(this);
var innerFunc = function () {
console.log(this);
};
innerFunc.call(this);
}
}
// bind 메소드 사용
var obj = {
outer: function() {
console.log(this);
var innerFunc = function () {
console.log(this);
}.bind(this);
innerFunc();
}
}
화살표 함수를 사용하면 스코프체인상 가장 가까운 this에 접근하게 된다. 화살표 함수를 사용한다면 굳이 this를 우회하거나 위의 메소드들을 사용하지 않아도 된다.
var obj = {
outer: function() {
console.log(this);
var innerFunc = () => {
console.log(this);
};
innerFunc();
}
}
콜백 함수를 인자로 받는 메서드 중 일부는 this로 지정할 객체를 직접 인자로 지정할 수 있는 경우도 있다.
Array.prototype.forEach(callback[, thisArg])
Array.prototype.map(callback[, thisArg])
Array.prototype.filter(callback[, thisArg])
Array.prototype.some(callback[, thisArg])
Array.prototype.evert(callback[, thisArg])
Array.prototype.find(callback[, thisArg])
Array.prototype.findIndex(callback[, thisArg])
Array.prototype.flatMap(callback[, thisArg])
Array.prototype.from(arrayLike[, callback[, thisArg]])
Set.prototype.forEach(callback[, thisArg])
Map.prototype.forEach(callback[, thisArg])
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); // 여기 두번째 인자로 전해준 this!
},
average: function () {
return this.sum / this.count;
}
};