앞 글에서 우리는 'this'가 함수를 호출하는 방법에 의해 결정된다는 결론을 얻었다. 이에 관해서 You don't know JS에서는 호출부에 따라 'this'가 바인딩 되는 규칙을 다음과 같이 4가지로 정의했다.
함수가 평범하게 호출될 때 나머지 규칙이 적용되지 않는다면 기본적으로 적용되는 규칙이다.
function foo() {
console.log(this.a)
}
var a = 2
foo() // 2
var a = 2
와 같이 전역 스코프에 변수를 선언하면 동일한 이름의 전역 객체 프로퍼티가 생성된다. 따라서, 함수 호출 시에 기본 바인딩 규칙에 따라 'this'는 전역 객체를 가리키게 된다.
단, 엄격 모드Strict Mode에서는 전역 객체가 기본 바인딩 대상에서 제외된다. 그래서 이 경우에 'this'는 undefined
가 된다. (여기서 엄격 모드는 foo
내부에서의 엄격 모드를 가리킨다. foo
가 호출되는 호출부의 엄격 모드와는 상관이 없다.)
function foo() {
console.log(this.a)
}
var a = 2
(function() {
'use strict'
foo() // 2
})()
호출부에 함수를 참조하는 컨텍스트 객체가 있다면 'this'는 그 객체를 가리키게 된다.
function foo() {
console.log(this.a)
}
var obj = {
a : 2,
foo : foo
}
obj.foo() // 2
객체 obj
는 함수 foo
를 프로퍼티로 참조하고 있다. 그러므로 foo
호출 시에 foo
에 대한 컨텍스트 객체로 obj
가 지정되어 암시적 바인딩 규칙에 따라 'this'는 객체 obj
를 가리키게 된다.
암시적 바인딩 규칙을 따르려면 함수를 참조하는 객체에 프로퍼티로 추가해줘야하는 번거로움이 있다. 또한, 암시적 바인딩은 객체의 깊이에 따라 바인딩이 사라지는 암시적 소실이라는 문제도 생긴다. 그러므로 함수를 객체에 프로퍼티로 추가하지 않고도 함수가 해당 객체를 'this'로 바인딩할 수 있는 방법이 필요한 때이다.
이와 같이 함수가 'this'로 참조하는 객체를 명확히 지정하는 방식을 명시적 바인딩이라 하며, 자바스크립트를 이를 위해 call()
과 apply()
라는 두가지 메서드를 지원한다.
function foo() {
console.log(this.a)
}
var obj = {
a : 2,
}
foo.call(obj) // 2
call()
과 apply()
메서드는 받은 인자를 'this'로 바인딩한 뒤에 함수를 호출한다. 두 메서드는 동일한 역할을 하지만 call()
은 인자로 인수 목록을 받으며, apply()
은 인자로 인수 배열을 받는다.
비슷한 역할을 하는 메서드로 bind()
메서드가 있다. bind()
메서드는 첫 번째 인자로 받는 값을 'this'에 바인딩하고, 그 이후의 값들을 함수의 인자로 넘겨주는 새로운 함수를 반환한다.
new 바인딩 규칙은 new 표현식으로 생성된 객체를 'this'에 바인딩한다. (new 표현식은 전통적인 클래스 지향 언어의 생성자 함수를 호출하는 방식처럼 보이지만, 그 내부는 전혀 다르다. 이는 자바스크립트가 프로토타입 기반 언어이기 때문이다.)
function foo(a) {
this.a = a
}
var bar = new foo(2)
console.log(bar.a) // 2
앞에 new를 붙여 foo()를 호출했고 새로 생성된 객체는 foo 호출 시 this에 바인딩 된다.
위에서 설명한 바인딩 규칙들이 중첩 적용된 경우에는 어떤 바인딩 규칙이 먼저 적용될까?
함수의 호출부를 기반으로 다음과 같은 우선순위에 따라 바인딩 규칙이 적용된다.
이상으로 You don't know JS에서 언급한 'this'의 4가지 바인딩 규칙들에 대해 알아봤다. 규칙에는 언제나 그렇듯 예외가 있으며, 이와 관련된 내용은 책에 자세하게 설명되어 있으니 책을 꼭 읽어보는 것을 추천한다.