this가 결정되는 시점
this는 기본적으로 실행 컨텍스트가 생성될 때 함께 결정됩니다. 실행 컨텍스트는 함수를 호출할 때 생성되므로, 바꿔 말하면 this는 함수를 호출할 때 결정된다고 할 수 있습니다.
함수와 메서드의 차이
함수는 그 자체로 독립적인 기능을 수행하는 반면, 메서드는 자신을 호출한 대상 객체에 관한 동작을 수행합니다. 흔히 메서드를 ‘객체의 프로퍼티에 할당된 함수’로 이해하지만 객체의 메서드로서 호출할 경우에만 메서드로 동작하고, 그렇지 않으면 함수로 동작합니다.
메서드로서 호출할 때 그 메서드 내부에서의 this가 가리키는 대상
this에는 호출한 주체에 대한 정보가 담깁니다. 어떤 함수를 메서드로 호출하는 경우 호출 주체는 바로 함수명(프로퍼티명) 앞의 객체입니다. 점 표기법의 경우 마지막 점 앞에 명시된 객체가 곧 this가 되는 것입니다.
함수로서 호출할 때 그 함수 내부에서의 this가 가리키는 대상
어떤 함수를 함수로서 호출할 경우에는 this가 지정되지 않습니다. this가 지정되지 않은 경우 this는 전역 객체를 바라보게 됩니다. 따라서 함수에서의 this는 전역 객체를 가리킵니다.
메서드의 내부함수에서의 this가 가리키는 대상
메서드 내부함수를 함수로서 호출할 수도 있고 메서드로서 호출할 수도 있습니다. this 바인딩에 관해서는 함수를 실행하는 당시의 주변 환경(메서드 내부인지, 함수 내부인지 등)은 중요하지 않고, 오직 해당 함수를 호출하는 구문 앞에 점 또는 대괄호 표기가 있는지 없는지가 관건입니다.
메서드 내부 함수에서의 this를 우회하는 방법
내부 함수의 상위 스코프에서 self라는 변수에 this를 저장한 이후 내부 함수에서 사용하는 방식이 있습니다.
화살표 함수의 this
함수 내부에서 this가 전역 객체를 바라보는 문제를 보완하고자 ES6에서는 this를 바인딩하지 않는 화살표 함수를 도입했습니다. 화살표 함수는 실행 컨텍스트를 생성할 때 this 바인딩 과정 자체가 빠지게 되어, 상위 스코프의 this를 그대로 활용할 수 있습니다.
콜백 함수 호출 시 그 함수 내부에서의 this가 가리키는 대상
콜백함수도 함수이기 때문에 기본적으로 this가 전역객체를 참조하지만, 제어권을 받은 함수에서 콜백 함수에 별도로 this가 될 대상을 지정한 경우엔 그 대상을 참조하게 됩니다.
// (1) 전역 객체 출력: 대상이 될 this를 지정하지 않음
setTimeout(function () { console.log(this); }, 300);
// (2) 전역 객체 출력: 대상이 될 this를 지정하지 않음
[1, 2, 3, 4, 5].forEach(function (x) { // (2)
console.log(this, x);
});
// (3) elements 출력: addEventListener 메서드는 콜백함수를 호출할 때 자신의 this를 상속하도록 되어 있음.
document.body.innerHTML += '<button id="a">클릭</button>';
document.body.querySelector('#a')
.addEventListener('click', function (e) { // (3)
console.log(this, e);
});
생성자 함수 내부에서의 this가 가리키는 대상
자바스크립트는 함수에 생성자로서의 역할을 함께 부여했기 때문에 new 명령어와 함께 함수를 호출하면 해당 함수가 생성자로서 동작하게 됩니다. 그리고 어떤 함수가 생성자 함수로서 호출된 경우 내부에서의 this는 곧 새로 만들 인스턴스 자신이 됩니다.
명시적으로 this를 바인딩하는 방법
메서드도 마찬가지로 임의의 객체를 this로 지정할 수 있습니다.
call 메서드와 apply 메서드의 차이점
apply 메서드는 call 메서드와 기능적으로 완전히 동일한데, 메서드의 호출 주체인 함수를 즉시 실행시키는 역할을 수행합니다. call 메서드는 첫 번째 인자를 this로 바인딩하고 나머지 모든 인자들을 호출할 함수의 매개변수로 지정하는 반면, apply 메서드는 두 번째 인자를 배열로 받아 그 배열의 요소들을 호출할 함수의 매개변수로 지정한다는 점에서만 차이가 있습니다.
bind 메서드
bind 메서드는 ES5에서 추가된 기능으로, call과 비슷하지만 즉시 호출하지는 않고 넘겨받은 this 및 인수들을 바탕으로 새로운 함수를 반환하기만 하는 메서드입니다. 다시 새로운 함수를 호출할 때 인수를 넘기면 그 인수들은 기존 bind 메서드를 호출할 때 전달했던 인수들의 뒤에 이어서 등록됩니다. 즉 bind 메서드는 함수에 this를 미리 적용하는 것과 부분 함수를 구현하는 두 가지 목적을 모두 지닙니다.
유사배열객체에 배열 메서드를 적용하려면?
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);
// slice 메서드에 매개변수를 넘기지 않으면 원본의 얕은 복사본을 반환함
console.log(arr); // [ 'a', 'b', 'c', 'd' ]
객체에는 배열 메서드를 직접 적용할 수 없지만 유사배열객체의 경우 call 또는 apply 메서드를 이용해 배열 메서드를 차용할 수 있습니다. 함수 내부에서 접근할 수 있는 arguments 객체 혹은 NodeList도 유사배열객체이므로 위의 방법으로 배열로 전환해서 활용할 수 있습니다. 문자열의 경우 length 프로퍼티가 읽기 전용이기 때문에 원본 문자열에 변경을 가하는 메서드(push, pop, shift, splice 등)는 에러를 던집니다.
사실 call/apply를 이용해 형변환하는 것은 ‘this를 원하는 값으로 지정해서 호출한다’라는 본래 메서드의 의도와는 다소 동떨어진 활용법이라 할 수 있습니다. 이에 ES6에서는 유사배열객체 또는 순회 가능한 모든 종류의 데이터 타입을 배열로 전환하는 Array.from 메서드를 새로 도입했습니다.
여러 인수를 묶어 하나의 배열로 전달하는 방법
apply 메서드를 사용하면 여러 인수를 묶어 하나의 배열로 전달할 수 있습니다. ES6에서는 펼치기 연산자(spread operator)를 이용하면 더욱 간편하게 작성할 수 있습니다.
bind 메서드의 name 프로퍼티
bind 메서드를 적용해서 새로 만든 함수는 한 가지 독특한 성질이 있습니다. name 프로퍼티에 동사 bind의 수동태인 ‘bound’라는 접두어가 붙는다는 점입니다. 어떤 함수의 name 프로퍼티가 'bound xxx'라면 이는 곧 함수명이 xxx인 원본 함수에 bind 메서드를 적용한 새로운 함수라는 의미가 되므로 기존의 call이나 apply보다 코드를 추적하기에 더 수월해진 면이 있습니다.
상위 컨텍스트의 this를 내부함수나 콜백함수에 전달하는 방법
self 등의 변수를 활용한 우회법과 더불어 call, apply, bind 메서드를 이용하는 방법이 있습니다. 또한 콜백 함수에 대해서도 bind 메서드를 이용하면 this 값을 사용자의 입맛에 맞게 바꿀 수 있습니다.