this

모든 내용은 책을 기반으로 작성되었습니다.

다른 대부분의 객체지향 언어에서 this는 클래스로 생성한 인스턴스 객체를 의미한다. 그러나 자바스크립트에서의 this는 어디서든 사용할 수 있다. 상황에 따라 this가 바라보는 대상이 달라진다.

상황에 따라 달라지는 this

this는 우리가 앞에서 선행한 실행컨텍스트 안에 있는 정보 중 하나이다. 이를 토대로 우리가 알 수 있는 것은 this도 실행컨테스트와 함께 지정된다는 것을 알 수 있다.

실제로 실행컨텍스트가 생성될 때 함께 결정된다.

실행컨텍스트는 함수가 호출될 때 생성되므로, this도 함수가 호출될 때 결정된다. 함수를 어떤 방식으로 호출하느냐에 따라 값이 달라진다.

전역공간에서의 this

전역 공간에서 this는 전역 객체이다. 전역 컨텍스트를 생성하는 주체는 전역 객체이기 때문에 이는 당연하다고 생각한다.

전역 변수를 선언하면 자바스크립트 엔진은 이를 전역 객체의 프로퍼티로 할당한다(전역 컨텍스트의 Lexical Environment는 전역 객체 그대로 참조.)

자바스크립트에서 window의 프로퍼티로 변수를 만드는 것과 var로 변수를 생성하는 경우에 있어 delete의 작동이 다른 것을 알 수 있다. 이는 사용자가 의도치 않게 삭제하는 것을 방지하는 차원에서 마련한 나름의 방어 전략이라고 한다.(configurable 속성의 차이)

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

함수를 실행하는 방법은 여러가지가 있다. 그중 가장 일반적인 방법은 함수로서 호출하는 경우와 메서드로서 호출하는 경우이다.

이 둘을 구분하는 유일한 차이는 독립성이다.

함수는 그 자체로 독립적인 기능을 수행하나, 메서드는 자신을 호출한 대상 객체에 관한 동작을 수행한다. 자바스크립트는 상황별로 this 키워드에 다른 값을 부여하게 함으로써 이를 구현했다.

흔히 자바스크립트에서 메서드를 객체의 프롶퍼티에 할당된 함수로 이해한다. 이는 반만 맞은 것인데, 할당한다고 해서 무조건 메서드가 되는 것이 아니라 객체의 메서드로 호출이 될 경우 비로소 메서드로 동작하는 것이 그렇지 않는 것은 함수로 동작하는 것이다.

그렇다면 "함수로서 호출"과 "메서드로서 호출"을 어떻게 구분할까? 바로 함수 앞에 점(.)이 있는지 여부만으로 간단하게 구분할 수 있다(진짜로).

점 표기법이든 대괄호 표기법이든 어떤 함수를 호출할 때 그 함수 이름(프로퍼티명) 앞에 객체가 명시돼 있는 경우에는 메서드로 호출한 것이며, 그렇지 않은 것은 함수로 호출한 것이다.

매서드 내부에서의 this

this에는 호출한 주체에 대한 정보가 담긴다. 어떤 함수를 메서드로서 호출하는 경우 호출 주체는 바로 함수명(프로퍼티명) 앞의 객체이다. 점 표기법의 경우 마지막 점 앞에 명시된 객체가 곧 this가 되는 것이다.

함수 내부에서의 this

어떤 함수를 함수로서 호출할 경우에는 this가 지정되지 않는다. 2장에서 실행 컨텍스트를 활성화할 당시에 this가 지정되지 않는 경우 this는 전역객체를 바라본다고 했다. 따라서 함수에서의 this는 전역객체이다.

더글라스 크락포드는 이를 명백한 설계상의 오류라고 지적합니다.

메서드의 내부함수에서의 this

결국 함수를 실행하는 당시의 주변 환경(메서드 내부인지, 함수내부인지 등)은 중요하지 않고, 오직 해당 함수를 호출하는 구문 앞에 점 또는 대괄호 표기가 있는지 없는지가 관건이다.

메서드의 내부 함수에서의 this를 우회하는 방법

대표적인 방법으로 변수를 활용하는 방법이다.

var obj = {
  outer: function() {
    console.log(this); // ??
    var innerFunc1 = function () {
      console.log(this); // ??
    }

    innertFunc1();

    var self = this;
    var innerFunc2 = function () {
      console.log(self); // ??
    }
    innerFunc2();
  }
}
obj.outer();

답: (1) obj1, (2) 전역객체(window), (3) obj2

결국 this 바인딩에 관해서는 함수를 실행하는 당시의 주변 환경(메서드 내부인지, 함수 내부인지 등)은 중요하지 않고, 오직 해당 함수를 호출하는 구문 앞에 점 또는 대괄호 표기가 있는지 없는지가 관건인 것이다.

이를 해결하기 위해서 self라는 변수를 outer 내부에 만들고 this를 할당하는 방식으로 해결했다(또는 _).

this를 바인딩하지 않는 함수

ES6에서는 함수 내부에서 this가 전역객체를 바라보는 문제를 보완하고자, this를 바인딩하지 않는 화살표 함수를 새로 도입했다. 화살표 함수는 실행컨텍스트를 생성할 때 this 바인딩 과정 자체가 빠지게 되어, 상위 스코프의 this를 그대로 활용할 수 있다. 내부 함수를 화살표 함수로 바꾸면 우회법이 불필요해진다.

그러나 ES5에서는 사용할 수 없다.

콜백 함수 호출 시 그 함수 내부에서의 this

setTimeout(function () {
  console.log(this); // ??
}, 300);

[1, 2, 3, 4, 5].forEach(function (x) {
  console.log(x); // ??
})

document.body.innerHTML += '<button id="a">클릭</button>';
document.body.querySelector('#a').addEventListener('click', function (a) {
  console.log(this, a); // ??
})

답 (1) 전역객체(Window), (2) 전역객체(Window), (3) 버튼 엘리먼트

특별히 정의하지 않는 경우에는 기본적으로 함수와 마찬가지로 전역객체를 바라본다.

생성자 함수 내부에서의 this

생성자 함수는 어떤 공통된 성질을 지니는 객체들을 생성하는 데 사용하는 함수이다.

자바스크립트는 함수에 생성자로서의 역할을 함께 부여했다. new 명령어와 함께 함수를 호출하면 해당 함수가 생성자로서 동작하게 된다. 그리고 어떤 함수가 생성자 함수로서 호출된 경우 내부에서의 this는 인스턴스 자신이 된다.

var Cat = function (name, age) {
  this.bark = '야옹';
  this.name = name;
  this.age = age;
}

var choco = new Cat('초코', 7);
var nabi = new Cat('나비', 5);
console.log(choco, nabi);

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

this에 특정 대상을 바인딩하는 방법이 있다.

call 메서드

Function.prototype.call(thisarg [, arg1[, arg2[, ...]]])

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

apply 메서드

Function.prototype.apply(thisarg [, argsArray])

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

bind 메서드

Function.prototype.bind(thisarg [, arg1[, arg2[, ...]]])

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

name 프로퍼티

bind 메서드를 적용해서 새로 만든 함수는 독특한 성질이 있다. 바로 name 프로퍼티에 'bound'라는 접두어가 붙는다는 것이다.

bind된 함수는 다시 bind를 할 수 없다.

화살표 함수의 예외 사항

ES6에 새롭게 도입된 화살표 함수는 실행 컨텍스트 생성 시 this를 바인딩하는 과정이 제외됐다. 즉 이 함수 내부에는 this가 아예 없으며, 접근하고자 하면 스코프체인상 가장 가까운 this에 접근하게 된다.

정리

  • 명시적으로 this를 바인딩 하지 않는다면 아래의 규칙을 따른다.
    • 전역공간에서 this는 전역객체(브라우저는 window, Node에서는 global)
    • 어떤 함수를 메서드로 호출한 경우, this는 메서드 호출 주체
    • 어떤 함수를 함수로 호출한 경우, this는 전역
    • 콜백 함수 내부에서의 this는 해당 콜백 함수의 제어권을 넘겨받은 함수가 정의한 바에 따르며, 정의되지 않는 경우 전역
    • 생성자 함수에서 this는 생성된 인스턴스
  • 명시적으로 바인딩 하는 방법
    • call, apply 메서드는 this를 명시적으로 지정하며, 함수 또는 메서드를 호출한다.
    • bind 메서드는 this 및 함수에 넘길 인수를 지정해서 함수를 만든다.
    • 요소를 순회하면서 콜백 함수를 반복 호출하는 내용의 일부 메서드는 별도의 인자로 this를 받을 수 있다.