this 바인딩

박찬욱·2023년 7월 31일
0

TIL

목록 보기
6/21
post-thumbnail

this 바인딩은 함수 호출 방식에 따라 동적으로 결정된다.

함수 호출 방식this 바인딩
일반 함수 호출전역 객체
메서드 호출메서드를 호출한 객체
생성자 함수 호출생성자 함수가 생성할 인스턴스
Function.prototype.apply/call/bind 메서드에 의한 간접호출메서드에 첫번째 인수로 전달한 객체

여기서 짚고 넘어가야할 점은 상위 스코프를 결정하는 렉시컬 스코프와는 결정시기가 다르다는 것이다.
렉시컬 스코프는 함수가 정의되어 함수 객체가 생성되는 시점에서 상위 스코프가 결정되지만 this 바인딩은 호출 시점에 결정된다.


일반함수 호출

var value = 1;

const obj = {
  value: 100,
  foo(){
    console.log("foo's this", this);
    console.log("foo's this.value", this.value);
    
    function bar(){
      console.log("bar's this:", this);
      console.log("bar's this.value:", this.value);
    }
  }
};

obj.foo();

foo는 obj 객체에서 호출된 메서드이기때문에 foo가 가리키는 this는 자신을 호출한 객체인 obj에 바인딩된다. 따라서 this는 obj이다.

반면에 메서드 내부에 정의된 중첩함수일지라도 일반함수로 호출이 된다면 this는 전역 객체가 바인딩이 된다.

var로 선언된 value는 전역 객체의 프로퍼티가 되기 때문에 bar함수의 this.value는 1이 출력이 될 것이다.


메서드 호출

메서드 내부의 this는 메서드를 소유한 객체가 아니라 메서드를 호출한 객체에 바인딩된다. 이 말을 이해하기 위해서는 메서드는 객체에 포함된 것이 아니라 독립적으로 존재하는 별도의 객체라는 사실을 알고 있어야한다.

const person = {
  name : 'Lee',
  getName () {
    return this.name;
  }
};

const anotherPerson = {
  name : 'Kim'
};

anotherPerson.getName = person.getName;
console.log(anotherPerson.getName()); // Kim

const getName = person.getName;
console.log(getName()); // window.name

즉, getName 메서드는 다른 객체의 프로퍼티에 할당하는 것으로 다른 객체의 메서드가 될 수도 있고, 일반 변수에 할당하여 일반 함수로 호출될 수도 있다.
왜냐하면 메서드가 가리키는 함수 객체는 객체에 포함된 것이 아니라 독립적으로 존재하는 별도의 객체이기 때문이다.

따라서 메서드 내부의 this는 메서드를 호출한 객체에 바인딩이 된다.


생성자 함수 호출

생성자 함수, 클래스 모두 미래에 생성될 인스턴스가 바인딩된다.


apply/call/bind 메서드에 의한 간접호출

apply/call 메서드는 함수를 호출하는 것이다.

function getThisBinding () {
  return this;
}

const thisArg = { a : 1 };

console.log(getThisBinding()); 

console.log(getThisBinding.apply(thisArg));
console.log(getThisBinding.call(thisArg));

메서드의 첫번째 인수로 전달한 특정 객체를 호출한 함수의 this에 바인딩한다.

apply와 call의 차이는 호출할 함수에 인수를 전달하는 방식만 다르다.
apply는 배열의 형식으로, call은 쉼표로 구분한 리스트의 형식으로 호출할 함소에 인수를 전달한다.

apply와 call의 대표적인 용도로는 arguments 객체와 같은 유사 배열 객체에 배열 메서드를 사용할 수 있게한다.

bind 메서드는 위의 두 메서드와 다르게 함수를 호출하지 않는다. 첫번째 인수로 전달한 값으로 this 바인딩이 교체된 함수를 새롭게 생성해 반환한다.

const person = {
  name: 'Lee',
  foo(callback) {
    setTimeout(callback,100);
  }
};

person.foo(function(){
  console.log(`Hi! my name is ${this.name}.`);
});

this.name의 값이 Lee가 출력되어야하는데 원하는대로 작동하지 않는다. 이유는 콜백 함수에서 가리키는 this는 일반 함수에서 호출되었기 때문에 전역 객체를 가리키게된다.
콜백 함수는 외부 함수의 헬퍼 함수 역할을 하기 때문에 외부 함수와 this가 같지 않으면 문맥상 문제가 발생한다.

따라서 bind 메서드를 이용해 this를 일치시킬 수 있다.

const person = {
  name: 'Lee',
  foo(callback) {
    setTimeout(callback.bind(this),100);
  }
};

person.foo(function(){
  console.log(`Hi! my name is ${this.name}.`);
});

비동기함수 내부 콜백함수의 this

const person = {
  name: "Lee",
  foo(callback) {
    setTimeout(callback, 100);
  },
};

person.foo(() => {
  console.log(`Hi! my name is ${this.name}.`);
});

화살표함수는 this에 대한 바인딩에 정적으로 결정된다. 즉, 함수에서 제일 근접한 상위 스코프에 this가 정적으로 바인딩된다는 것이다.

그렇기 때문에 콜백 함수로 화살표함수를 작성하면 this.name의 값이 콜백 함수의 상위 스코프인 foo의 this 즉, foo를 호출한 person 객체에 바인딩이되어 'Lee'를 출력할 것이라고 예상했다.

하지만 setTimeout 내부의 비동기로 동작하는 콜백함수는 setTimeout이 종료된 이후에 호출된다. 따라서 콜백 함수가 호출되는 시점에서 this는 전역 객체를 가리키게 된다.

따라서 화살표 함수의 this를 person 객체에 바인딩하면서 콜백함수의 다형성을 확보하고 싶으면 다음과 같이 수정해야한다.

const person = {
  name: "Lee",
  foo(callback) {
    setTimeout(() => {
      callback(this.name); // 콜백 함수에 this.name을 전달하여 다른 로직에 활용
    }, 100);
  },
};

person.foo((name) => {
  console.log(`Hi! my name is ${name}.`);
});

person.foo((name) => {
  console.log(`Hello, ${name}!`);
});
profile
대체불가능한 사람이다

0개의 댓글

관련 채용 정보