이전에 this
는 함수가 호출 될때를 잘 보아야 한다고 했다. 그래서 함수 호출부 즉 함수 호출코드부터 확인하고 this
가 가르키는 것이 무엇인지 찾아보도록 하겠습니다.
함수의 호출 지점으로 돌아가면 금세 확인할 수 있을거 같지만 코딩 패턴에 따라 그리 쉽지 않는 경우가 있다. 중요한건 호출 스택을 생각해보는 것이며 이 중 호출부는 현재 실행중인 함수 직전의 호출 코드내부에 있다.
function baz() {
console.log('baz')
bar(); // <- bar의 호출부
}
function bar() {
console.log('bar')
foo(); // <- foo의 호출부
}
function foo() {
console.log('foo')
}
baz() // <- baz의 호출부
다음과 같이 조목 조목 호출부를 잘 따져보아야 이해할 수 있습니다.
함수가 실행되는 동안 this
가 무엇을 참조할지를 호출부가 어떻게 결정하는지 알아보자. 호출부를 꼼꼼히 살펴보고 다음에 열거할 4가지 규칙 중 어느 것이 해당하는지 확인합니다.
일단 규칙별로 하나씩 살펴보고 여러 규칙을 중복으로 적용할 경우 우선순위를 정해봅시다.
첫번째 규칙은 가장 평범한 함수 호출인 단독 함수 실행
에 관한 규칙으로 나머지 규칙에 해당하지 않을 경우 적용되는 this
의 기본 규칙입니다.
function foo() {
conosole.log(this.a)
}
var a = 2;
foo() // 2
전역 스코프에 a
라는 변수를 선언하면 변수명과 같은 이름의 전역 객체 프로퍼티가 생성됩니다. 이는 서로의 사본이 아닌 동전의 앞뒷면이라고 생각하면 됩니다.
foo()
함수를 호출하면 this.a
는 전역 객체 a입니다. 기본 바인딩이 적용되어 this
는 전역 객체를 참조합니다.
기본 바인딩 규칙이 적용됐다는건 어찌 알 수 있을까? foo()
함수의 호출부를 보면 foo()
는 지극히 평범한 있는 그대로의 함수 레퍼런스로 호출하였습니다. 나머지 규칙을 논할 여지도 없이 기본 바인딩이라고 할 수 있습니다.
엄격모드에서는 전역 객체가 기본 바인딩에서 제외 됩니다.
호출부에서의 엄격모드가 아니라 this에 접근할때 엄격모드 인지가 중요합니다.
두번째 규칙은 호출부에 콘텍스트 객체가 있는지 확인하는 것이다.
function foo() {
console.log(this.a)
}
var obj = {
a: 2,
foo: foo
}
obj.foo() // 2
foo
함수를 obj에서 프로퍼티로 참조하고 있습니다. foo
를 처음부터 프로퍼티로 선언하든 나중에 레퍼런스로 추가하는 obj 객체가 이 함수를 정말로 소유 하거나 포함 한것 은 아닙니다.
그러나 호출부는 obj 콘텍스트 foo
를 참조하므로 obj 객체는 함수 호출 시점에 함수의 래퍼런스를 소유하거나 포함한다고 볼 수 있습니다.
암시적 바인딩 규칙에 따르면 바로 이 콘텍스트 객체가 함수 호출 시 this
에 바인딩 됩니다.
암시적으로 바인딩된 함수에서 바인딩이 소실되는 겨우가 있는데 this
바인딩이 뜻 밖에 헷갈리기 쉬운 경우다.
function foo(){
console.log(this.a)
}
var obj = {
a: 2,
foo: foo
}
var bar = obj.foo
var a = '전역?'
bar() // 전역?
bar
가 마치 obj.foo
를 실행하는 것처럼 보이지만 bar
는 foo
를 단일로 실행하는 것과 다른바 없이 되어서 기본 바인딩이 적용됩니다.
그리고 함수를 인자로 넘기는 경우에도 소실이 일어나게 됩니다.
call, apply, bind
를 통해 바인딩하는 방법입니다.
bind
fn.bind(객체)
와 같이 바인딩 하여서 객체에 바인딩 된 함수를 리턴합니다.
call
fn.call(객체, ...인자)
객체에 바인딩된 함수를 실행합니다. 하지만 객체의 인자를 스프레드된 형태로 넣어야합니다
apply
fn.call(객체, [인자])
객체에 바인딩된 함수를 실행합니다. 하지만 객체의 인자를 배열 형태로 넣어야합니다.
new 연산자로 생성자를 호출하면 다음과 같은 일들이 저절로 일어납니다.
[[Prototype]]
이 연결됩니다.예제를 통해 한번 알아보겠습니다.
function foo() {
console.log(this.a)
}
var obj1 = {
a:2,
foo:foo
}
var obj2 = {
a:3,
foo: foo
}
obj1.foo.call(obj2) // 3
결과를 보면 명시적 바인딩이 암시적 바인딩 보다 우선순위에 있다고 볼 수 있습니다.
function foo(something) {
this.a = something
}
var obj1 = {
foo: foo
}
var obj2 = {}
obj1.foo(2)
console.log(obj1.a) // 2
obj1.foo.call(obj2, 3)
console.log(obj2.a) // 3
var bar = new obj1.foo(4)
console.log(obj1.a) // 2
console.log(bar.a) // 4
new를 통한 바인딩이 암시적 바인딩 보다 우선순위에 있습니다.
그럼 명시적바인딩과 new 바인딩은 누가 우선순위에 있을까요?
명시적 바인딩이 우선순위에 있습니다.
호출부에서 this
가 결정되는 규칙을 우선 순위에 다라 차례대로 정리 해봅시다.
this
입니다.this
입니다/this
입니다.this
는 기본값이면 비 엄격모드는 global
입니다.일반적인 함수는 지금 까지 살펴본 4가지 규칙을 준수합니다. 하지만 화살표 함수는 4가지 표준 규칙 대신 에두른 스코프를 보고 this
를 알아서 바인딩 한다.
function foo() {
return () => {
// 여기서 'this'는 어휘적으로 'foo()'에서 상속 됩니다.
console.log(this.a)
}
}
var obj1 = {
a: 2
}
var obj2 = {
a: 3
}
var bar = foo.call(obj1)
bar.call(obj2) // 2, 3이 아니다.
foo()
내부에서 생성된 화살표 함수는 foo()
호출 당시 this
를 무조건 어취적으로 포착합니다.
그리고 화살표 함수의 어휘적 바인딩은 절대로 오버라이드 할 수 없습니다.
화살표 함수는 이벤트 처리기나 콜백에 가장 널리 쓰입니다.