20220906 TIL

엄강우·2022년 9월 6일
0

TIL

목록 보기
43/43

this

이전에 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를 실행하는 것처럼 보이지만 barfoo를 단일로 실행하는 것과 다른바 없이 되어서 기본 바인딩이 적용됩니다.

그리고 함수를 인자로 넘기는 경우에도 소실이 일어나게 됩니다.

명시적 바인딩

call, apply, bind를 통해 바인딩하는 방법입니다.

  1. bind

    fn.bind(객체)와 같이 바인딩 하여서 객체에 바인딩 된 함수를 리턴합니다.

  2. call

    fn.call(객체, ...인자) 객체에 바인딩된 함수를 실행합니다. 하지만 객체의 인자를 스프레드된 형태로 넣어야합니다

  3. apply

    fn.call(객체, [인자]) 객체에 바인딩된 함수를 실행합니다. 하지만 객체의 인자를 배열 형태로 넣어야합니다.

new 바인딩

new 연산자로 생성자를 호출하면 다음과 같은 일들이 저절로 일어납니다.

  1. 새 객체가 툭 만들어 집니다.
  2. 새로 생성된 객체의 [[Prototype]]이 연결됩니다.
  3. 새로 생성된 객체는 해당 함수 호출 시 this로 바인딩 됩니다.
  4. 이 함수가 자신의 또 다른 객체를 반환하지 않는 한 new와 함게 호출된 함수는 자동으로 생성된 객체를 반환한다.

그래서 우선수위가 어떻게 되는데?

예제를 통해 한번 알아보겠습니다.

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가 결정되는 규칙을 우선 순위에 다라 차례대로 정리 해봅시다.

  1. new 함수를 통해 호출 했다면 새로 생선된 객체가 this입니다.
  2. call, apply 혹은 bind로 바인딩 되었다면 지정된 객체가 this입니다/
  3. 함수 콘텍스트를 즉, 객체를 소유 또는 포함하는 형태로 호출 했다면 -> 콘텍스트 객체가 this입니다.
  4. 그 왜의 경우는 this는 기본값이면 비 엄격모드는 global입니다.

ES6 이후로 등장한 화살표 함수

일반적인 함수는 지금 까지 살펴본 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를 무조건 어취적으로 포착합니다.

그리고 화살표 함수의 어휘적 바인딩은 절대로 오버라이드 할 수 없습니다.

화살표 함수는 이벤트 처리기나 콜백에 가장 널리 쓰입니다.

profile
안녕하세요 프론트엔드 개발자를 꿈꾸는 엄강우입니다.

0개의 댓글