[JS] this 바인딩 (콜백함수에서의 this)

colki·2021년 3월 30일
4

🔹 콜백함수에서의 this

콜백함수도 함수이기 때문에,
어떤 객체의 메서드로 있던 함수가 인자로 전달될 경우에는 객체의 메서드가 아닌 함수자체로 전달된다.

콜백함수 내부에서의 this는 해당 콜백함수의 제어권을 넘겨받은 함수가 정의한 바에 따르며, 정의하지 않은 경우에는 전역객체를 참조한다.


다음 예제에서 콘솔에 출력될 값은 각각 무엇일까?

let obj = {
  array: [1, 2, 3],
  foo: function(a, b) {
    console.log(this, a, b); // ?
  }
}

obj.foo(4, 5); --- (1)
[10,20,30].forEach(obj.foo); --- (2)

(1) obj.foo(4, 5);
. 앞에 obj라는 객체가 있으니 foo함수는 obj의 메서드 foo의 자격으로 호출이 된 것이다.
따라서 this는 . 앞의 객체인 obj를 가리키게 되고,
콘솔에는 {array: Array(3), foo: ƒ} 4 5 가 출력된다.


(2) [10,20,30].forEach(obj.foo);

obj.foo 가 통째로 forEach 함수의 인자로 들어가 있다.
생긴건 점이 붙은 메서드의 모양으로 들어가 있지만, 메서드를 그대로 준 게 아니라
그냥 콜백함수의 역할을 하는 함수로써 있는 것 뿐이다. 풀어보면 이러하다.

[10,20,30].forEach(function(a, b) {
  console.log(this, a, b); 
});

단순히 obj.foo가 나타내는 function(a, b) { console.log(this, a, b); }가 들어간 것에 불과하다.
이렇게 콜백함수로써 들어가 있을 경우에는 . 앞의 obj와 직접적인 연관성이 없다.

별도로 this를 설정하지 않았으므로, 이때의 this는 전역객체인 window를 바라보게 된다.
그래서 콘솔에는 forEach메서드의 결괏값이 나란히 출력된다.


객체의 소속이었던 메서드를 콜백함수로써(전달인자로) 전달하면,
그 콜백함수 내부에서 this는 더이상 그 자신의 속해있던 객체를 바라보지 않게 된다는 것이다. 😄




🔸 this 바인딩

this 와 함수를 묶는 것을 바인딩이라고 한다.
같은 코드를 조금씩 변형해서 바인딩 방법에 대해서 나열해보겠다.

① 일반 함수 호출과 메서드 호출

var obj = {
  outer: function () {
    var inner = function () {
      console.log(this); // --- (2)
    }
    
    console.log(this); // --- (1)
    inner();
  }
}

obj.outer();

(1) obj.outer();

this는 .앞의 객체를 가리키므로 obj {outer: ƒ} 가 출력된다.

(2) inner();

일반함수 호출 방식이므로 this는 전역객체 window를 가리키므로, window{ }가 출력된다.


② call을 이용한 this 바인딩

var obj = {
  outer: function () {
    var inner = function () {
      console.log(this); // --- (2)
    }

    console.log(this); // --- (1)
    inner.call(this);
  }
}

obj.outer();

(1) obj.outer();

this는 .앞의 객체를 가리키므로 obj {outer: ƒ} 가 출력된다.

(2) inner.call(this);

call메서드에서 첫번째 인자를 this로 설정하게 되어있으므로,
inner.call(this); 의 this는 outer내부의 this를 가리킨다.

outer내부의 this는 (1)결과대로 obj이므로 `inner.call(obj)와 마찬가지이다.
그렇기 떄문에 (2)역시 obj {outer: ƒ} 가 출력된다.


③ 변수의 레퍼런스에 할당

var obj = {
  outer: function () {
    var inner = function () {
      console.log(this); // --- (2)
    }

    console.log(this); // --- (1)
    inner.call(this);
  }
}

const objFunc = obj.outer;
objFunc();

obj.outer는 obj객체의 outer메서드의 모습이지만,
objFunc 변수의 레퍼런스에 할당되는 순간 this 가 가리키던 객체는 사라지면서 전역객체를 가리키고, 일반함수가 된다.

따라서 objFunc(); 일반 함수 실행의 방식으로 호출되고 있으므로 (1), (2)의 this는 모두 전역객체 window { }를 가리킨다.


💡 이것은 콜백함수일때 내부의 this가 가리키는 객체에 대해 다뤘던 상단의 내용과 같다.
위에서 살펴봤던 예제를 다시 보자.

여기서도 obj.foo 만 두고 봤을 땐 메서드 호출처럼 보이지만,
forEach(obj.foo === 전달인자로 들어간 함수) 인자로 들어가면서는 그저 하나의 함수일 뿐이다.

let obj = {
  array: [1, 2, 3],
  foo: function(a, b) {
    console.log(this, a, b); // ?
  }
}

obj.foo(4, 5); --- (1)
[10,20,30].forEach(obj.foo); --- (2)

④ bind를 이용한 this 바인딩

앞의 ②에서 call메서드를 이용한 this바인딩을 확인했다.
하지만 보다 직관적으로 해당 객체를 바인딩할 수 있는 bind 메서드가 있다.


🙋‍♂️ this 포스팅에서 다뤘던 bind메서드를 다시 상기하면,

bind는 call 메서드와 같이 인자값 설정하는 부분은 동일하다. 첫 번째 인자로는 this로 설정할 값, 두 번째 인자는 갯수제한 없이 값을 받을 수 있다.

하지만 bind는 함수를 바로 호출하는 것이 아니라, 함수에 this를 미리 바인딩하여 원본함수를 복제해 놓는다. 그렇게 미리 this값을 잡아두고, 실행할 때는 바인딩해두었던 함수를 호출하는데 겉보기엔은 일반 함수 호출 방식과 동일하다.
그래서 얼핏 보고 일반함수 호출이네? 하고 전역객체를 가리킬거라고 잘못 생각하기 쉽다.


var obj = {
  outer: function () {
    var inner = function () {
      console.log(this); // --- (2)
    }.bind(this);
    
    console.log(this); // --- (1)
    inner();
  }
}

obj.outer();

(1) obj.outer();

this는 .앞의 객체를 가리키므로 obj {outer: ƒ} 가 출력된다.

(2) inner();

inner 함수 뒤에.bind(this); 가 따라오는데, bind의 첫번째 인자를 this로 설정한다.
그래서 this가 가리키는 대상은 this가 된다.

inner(); 일반 함수 호출로 보이지만, bind로 바인딩을 미리 해뒀기 때문에
this는 전역객체를 가리키지 않고, this를 가리키게 된다.
여기서 this는 (1)에서 출력한 것처럼 obj이므로 똑같이 (2)에도 obj {outer: ƒ} 가 출력된다.

profile
매일 성장하는 프론트엔드 개발자

0개의 댓글