Core JavaScript #3

milkboy2564·2022년 12월 19일
0

상황에 따라 달라지는 this

자바스크립트에서 this는 기본적으로 실행 컨텍스트가 생성될 때 결정된다. 실행 컨텍스트는 함수를 호출할 때 만들어지므로, 다시 말해 this는 함수를 호출할 때 결정된다고 할 수 있다.

전역 공간에서의 this

전역 공간에서의 this는 전역 객체를 가리킨다.
브라우저 환경에서의 전역 객체는 window객체를 말하고 Node.js 환경에서의 전역 객체는 global 객체를 의미한다.

var 키워드를 사용하여 전역 변수를 생성하게 되면 자바스크립트 엔진은 이를 전역 객체의 프로퍼티로 할당한다. 이는 자바스크립트의 모든 변수는 사실 특정 객체의 프로퍼티로서 동작하기 때문이다.
여기서 말하는 특정 객체가 바로 실행 컨텍스트의 LexicalEnvironment가 된다.

앞서 var로 선언한 변수는 전역 객체의 프로퍼티로 할당이 된다고 했다.
다시 말해, var로 선언한 변수는 window 객체에 할당이 된다는 것이고 이는 곧 awindow.a가 같은 같은 가리킨다는 것을 알 수 있다.

그리고 window.a에 값을 할당하는 것은 var 키워드로 값을 할당한 것과 동일한 기능을 한다는 것 또한 알 수 있다.

이처럼 대부분의 경우 window에 직접 할당하는 것과 var 키워드로 할당하는 것이 동일하게 동작하지만 '삭제' 명령에 대해서는 전혀 다르게 동작한다.

삭제 동작의 경우 전역 객체의 프로퍼티에 할당된 값은 삭제가 되지만 전역 변수로 선언한 경우는 삭제가 되지 않는다.

이런 차이가 발생하는 이유는 전역 변수를 선언할 때 자바스크립트 엔진이 전역 객체의 프로퍼티로 값을 할당할 때 configurable 속성(변경 및 삭제 가능성)을 false로 정의하기 때문이다.

메서드로 호출할 때 해당 메서드 내부에서의 this

함수를 호출하는 방식은 "함수로서 호출"과 "메서드로서 호출" 두 가지 방식이 존재한다.

이 둘의 차이는 아래와 같이 this가 가리키는 대상이 달라지게 된다.

var foo = function(x) {
	console.log(this, x); // Window {...} 1
};
foo(1);

var obj = {
  method: func
};
obj.method(2); // {methof: f} 2

함수로서 호출과 메서드로서 호출을 구분하는 가장 간단한 방식은 함수 앞에 점(.)이 있는지 여부를 확인하는 것이다.
다시 말해, 점(.)이 있다면 객체의 메서드로서 호출하는 것이고 없다면 함수로서 호출하는 것이다.

함수로서 호출할 때 해당 함수 내부에서의 this

함수 내부에서 바인딩되는 this를 판별할 때는 함수의 주변 환경(메서드 내부인지, 함수 내부인지 등)은 중요하지 않고, 오직 해당 함수를 호출하는 구문 앞에 점 또는 대활호 표기가 있는지 없는지가 관건이 된다.

this를 바인딩하지 않는 함수
ES5까지는 호출 주체가 없는 경우 자동으로 전역 객체가 바인딩 되어서 이를 우회하는 방식으로 아래와 같이 this를 변수에 저장하는 방법으로 사용하곤 했다.

var obj = {
	outer: function() {
		console.log(this); // {outer: f}
      var innerFunc1 = function() {
		console.log(this); // Window {...}
      };
      innerFunc1();
      
      var self = this;
      var innerFunc2 = function() {
		console.log(this); // {outer: f}
      };
      innerFunc2();
    }
};
obj.outer();

ES6에서는 이러한 방식에서 벗어나 this를 바인딩하지 않는 화살표 함수가 나왔다. 화살표 함수는 this를 바인딩하는 과정 자체가 생략되어 자동으로 상위 스코프의 this를 상속받아 활용할 수 있게 됐다.

명시적으로 this를 바인딩하는 방법

Call

Call 메서드는 메서드의 호출 주체인 함수를 즉시 실행하도록 하는 명령이다.
첫 번째 인자로 this를 바인딩하고, 이후의 인자들을 호출 함수의 매개변수로 한다.

함수를 그냥 실행하면 this 는 전역 개체를 참조하지만 call 메서드를 사용하면 임의로 this에 바인딩할 객체를 지정해줄 수 있다.

var func = function(a, b, c){
	console.log(this, a, b, c);
};

func(1, 2, 3); // Window {...} 1 2 3
func.call({x: 1}, 4, 5, 6); // {x: 1} 4 5 6

메서드도 마찬가지로 그냥 호출하면 this는 메서드를 호출한 객체를 가리키지만 call 메서드를 이용해 임의로 this에 바인딩할 객체를 지정해줄 수 있다.

apply

apply 메서드는 기능적으로 call 메서드와 완벽하게 동일하다.
차이는 call 메서드는 첫 번째 인자를 제외한 모든 인자들을 호출할 함수의 매개변수로 지정하는 반면, apply 메서드는 두 번째 인자로 배열을 받아 해당 배열의 요소들을 호출할 함수의 매개변수로 지정한다.

var func = function(a, b, c) {
	console.log(this, a, b, c);
};
func.apply({x: 1}, [4, 5, 6]); // {x: 1} 4, 5, 6

var obj = {
  a: 1,
  method: function(x, y) {
  	console.log(this.a, x, y);
  }
}
obj.method.apply({a: 4}, [5, 6]); // 4, 5, 6

bind

bind 메서드는 call과 비슷하지만 즉시 호출하지는 않고 넘겨 받은 this 및 인수들을 바탕으로 새로운 함수를 반환하기만 하는 메서드다.

bind 메서드로 생성한 새로운 함수를 호출할 때 인수를 넘기면 해당 인수들은 기존 bind 메서드를 호출할 때 전달했던 인수들의 뒤에 이어서 등록된다.

즉, bind 메서드는 함수에 this를 미리 적용하는 것과 부분 적용 함수를 구현하는 두 가지의 목적을 모두 지닌다.

var func = function(a, b, c, d) {
	console.log(this, a, b, c, d);
};
func(1, 2, 3, 4); // Window {...} 1 2 3 4

var bindFunc1 = func.bind({x: 1});
bindFunc(5, 6, 7, 8); // {x: 1} 5 6 7 8

var bindFunc2 = func.bind({x: 1}, 4, 5);
bindFunc2(6, 7); // {x: 1} 

정리

다음 규칙은 명시적 this 바인딩이 없는 한 늘 성립한다.

  • 전역공간에서의 this 는 전역객체(브라우저에서는 Windo, Node.js에서는 global)를 참조한다.
  • 어떤 함수를 메서드로서 호출한 경우 this 는 메서드 호출 주체(메서드명 앞의 객체)를 참조한다.
  • 어떤 함수를 함수로서 호출한 경우 this 는 전역객체를 참조한다. 메서드의 내부함수에서도 마찬가지다.
  • 콜백 함수 내부에서의 this 는 해당 콜백 함수의 제어권을 넘겨받은 함수가 정의한 바에 따르며, 정의하지 않은 경우에는 전역객체를 참조한다.
  • 생성자 함수에서의 this 는 생성될 인스턴스를 참조한다.

다음은 명시적 this 바인딩이다. 위 규칙에 부합하지 않는 경우에는 다음 내용을 바탕으로 this 를 예측할 수 있다.

  • call, apply 메서드는 this 를 명시적으로 지정하면서 함수 또는 메서드를 호출한다.
  • bind 메서드는 this 및 함수에 넘길 인수를 일부 지정해서 새로운 함수를 만든다.
  • 요소를 순회하면서 콜백 함수를 반복 호출하는 내용의 일부 메서드는 별도의 인자를 this 로 받기도 한다.
profile
FE 개발자

0개의 댓글