실행 컨텍스트에 대한 정리를 하다 this 바인딩 부분도 공부할 것이 많아 따로 정리하고싶었다.
(1)에서는 this의 정의와 쓰이게 된 이유, 일반 함수 호출, 메서드 호출, 생성자 함수 호출 등 상황별로 어떤 값이 this에 바인딩 되는지를 알아보았다.
(2)에서는 규칙을 깨고 별도의 대상을 명시적으로 this에 바인딩하는 방법에 대해 알아볼 것이다.
call
메서드Function.prototype.call(thisArg[, arg1[, arg2[, ...]]])
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
메서드Function.prototype.apply(thisArg[, argsArray])
apply
메서드는 call
메서드와 기능적으로 완전히 동일하다.
apply
메서드는 두 번째 인자를 배열로 받아 그 배열의 요소들을 호출할 함수의 매개변수로 지정한다는 점에서만 차이가 있다.
var func = function (a, b, c) {
console.log(this, a, b, c);
};
func.apply({ 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.apply({ a: 4 }, [5, 6]); // 4 5 6
call
/ apply
메서드의 활용var obj = {
0: 'a',
1: 'b',
2: 'c',
length: 3
};
Array.prototype.push.call(obj, 'd');
console.log(obj); // { 0: 'a', 1: 'b', 2: 'c', 3: 'd', length: 4 }
// 프로퍼티를 추가함.
var arr = Array.prototype.slice.call(obj);
console.log(arr); // [ 'a', 'b', 'c', 'd']
// 매개변수를 넘기지 않아 얕은 복사 수행, 배열 메서드라 배열로 반환
객체에는 배열 메서드를 직접 적용할 수 없으나 키가 0 또는 양의 정수인 프로퍼티가 존재하고 length 프로퍼티의 값이 0 또는 양의 정수인 객체.
즉, 배열의 구조와 유사한 객체. 유사배열객체의 경우 call
또는 apply
메서드를 이용해 배열 메서드를 차용할 수 있다.
함수 내부에서 접근할 수 있는 arguments
객체도 유사배열객체이므로 배열로 전환해서 활용할 수 있다. querySelectorAll
, getElementsByClassName
등의 Node
선택자로 선택한 결과인 NodeList
또한 적용 가능하다.
Array.prototype.slice() - MDN
Function.prototype.call() - MDN
NodeList - MDN
유사배열객체에는 call/apply
메서드를 이용해 모든 배열 메서드를 적용할 수 있다. 배열처럼 인덱스나 length
프로퍼티를 지니는 문자열에 대해서도 마찬가지.
하지만 문자열의 경우 length
프로퍼티가 읽기 전용이기 때문에 원본 문자열에 변경을 가하는 메서드(push
, pop
, shift
, unshift
, splice
등)는 에러를 던지며 concat
처럼 대상이 반드시 배열이어야 하는 경우에는 제대로 된 결과를 얻을 수 없음
call/apply
를 이용해 형변환하는 것은 본래의 메서드 의도와는 동떨어진 활용법이다. 이에 ES6에서는 유사배열객체 또는 순회 가능한 모든 종류의 데이터 타입을 배열로 전환하는 Array.from
메서드를 새로 도입했다.
var obj = {
0: 'a',
1: 'b',
2: 'c',
length: 3
};
var arr = Array.from(obj);
console.log(arr); // ['a', 'b', 'c']
생성자 내부에 다른 생성자와 공통된 내용이 있을 경우 call
또는 apply
를 이용해 다른 생성자를 호출하면 간단하게 반복을 줄일 수 있다.
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 by = new Student('윤보현', 'male', '단국대');
var jn = new Employee('임재령', 'female', 'Google');
apply
활용여러 개의 인수를 받는 메서드에게 하나의 배열로 인수들을 전달하고 싶을 때 apply
메서드를 사용하면 좋다.
예를 들어 배열에서 최대/최솟값을 구해야한다.
(1) 직접 구현할 경우
var numbers = [10, 20, 3, 16, 45];
var max = min = numbers[0];
numbers.forEach(function(number) {
if (number > max) {
max = number;
}
if (number < min) {
min = number;
}
});
console.log(max, min); // 45 3
(2) 여러 인수를 받는 메서드 Math.max/Math.min
에 apply
를 적용할 경우
var numbers = [10, 20, 3, 16, 45];
var max = Math.max.apply(null, numbers);
var min = Math.min.apply(null, numbers);
console.log(max, min); // 45 3
(3) ES6의 펼치기 연산자 spread operator 활용할 경우
const numbers = [10, 20, 3, 16, 45];
const max = Math.max(...numbers);
const min = Math.min(...numbers);
console.log(max, min); // 45 3
call/apply
메서드는 명시적으로 별도의 this
를 바인딩하면서 함수 또는 메서드를 실행하는 훌륭한 방법이지만 오히려 this
를 예측하기 어렵게 만들어 코드 해석을 방해한다는 단점이 있다. 하지만 ES5 이하의 환경의 실무에서 광범위하게 활용되고 있음.
bind
메서드Function.prototype.bind(thisArg[, arg1[, arg2[, ...]]])
ES5에서 추가된 기능, 즉시 호출하지 않고 넘겨받은 this
및 인수들을 바탕으로 새로운 함수를 반환하는 메서드이다.
함수에 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
// this 만을 지정한 것
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
// this 지정과 함께 부분 적용 함수를 구현
var func = function (a, b, c, d) {
console.log(this, a, b, c, d);
};
var bindFunc = func.bind({ x: 1 }, 4, 5);
console.log(func.name); // func
console.log(bindFunc.name); // bound func
name
프로퍼티에 동사 bind의 수동태인 bound라는 접두어가 붙는다.
name
프로퍼티가 'bound abc'라면 이는 곧 함수명이 abc인 원본 함수에 bind
메서드를 적용한 새로운 함수라는 의미이다.
기존의 call
이나 apply
보다 코드를 추적하기에 수월해짐.
this
를 내부함수나 콜백 함수에 전달하기메서드의 내부함수에서 메서드의 this
를 그대로 바라보게 하기 위한 방법.
변수를 활용한 우회법보다 더 깔끔하게 처리 가능.
var obj = {
outer: function () {
console.log(this);
var innerFunc = function () {
console.log(this);
};
innerFunc.call(this);
}
};
obj.outer();
var obj = {
outer: function () {
console.log(this);
var innerFunc = function() {
console.log(this);
}.bind(this);
innerFunc();
}
};
obj.outer();
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(); // obj { logThis: f, ... }
ES6에 새로 도입된 화살표 함수는 실행 컨텍스트 생성시 this
를 바인딩하는 과정이 제외됨. 함수 내부에는 this
가 없으며 접근하고자 하면 스코프체인상 가장 가까운 this
에 접근하게 됨.
var obj = {
outer: function () {
console.log(this);
var innerFunc = () => {
console.log(this);
};
innerFunc();
}
};
obj.outer();
별도의 변수로 this
를 우회하거나 call/apply/bind
를 적용할 필요가 없어 더욱 간결하고 편리함.
this
를 받는 경우 (콜백 함수 내에서의 this)콜백 함수를 인자로 받는 메서드 중 일부는 추가로 this
로 지정할 객체thisArg
를 인자로 지정할 수 있는 경우가 있다. 이런 메서드의 thisArg
값을 지정하면 콜백 함수 내부에서 this
값을 원하는 대로 변경할 수 있음. 보통 여러 내부 요소에 대해 같은 동작을 반복 수행해야 하는 배열 메서드에 많이 포진되어있다.
아래 규칙은 명시적 this
바인딩이 없는 한 늘 성립한다.
this
는 전역객체(브라우저에서는 window, Node.js에서는 global)를 참조한다.this
는 메서드 호출 주체(메서드명 앞의 객체)를 참조한다. (.
과 []
)this
는 전역객체를 참조한다.this
는 해당 콜백 함수의 제어권을 넘겨받은 함수가 정의한 바에 따르며, 정의하지 않은 경우에는 전역객체를 참조한다.this
는 생성될 인스턴스를 참조한다.위 규칙에 부합하지 않는 경우엔 아래 내용을 바탕으로 this
를 예측한다
call/apply
메서드는 this
를 명시적으로 지정하면서 함수 또는 메서드를 호출한다.bind
메서드는 this
및 함수에 넘길 인수를 일부 지정해서 새로운 함수를 만든다.this
를 받기도 한다. (추후에 콜백함수를 배우며 짚어볼 예정)