javascript & Node.js/javascript

자바스크립트 this

반응형

다른 객체지향 언어에서는 this가 클래스에서만 사용할 수 있지만, 자바스크립트에서는 어디서든 사용할 수 있어서 사람들이 가장 혼란스러워하는 개념 중 하나를 정리했습니다.

1. 다양한 this

자바스크립트에서 this는 기본적으로 실행 컨텍스트가 생성될 때 함께 결정됨. (this는 함수를 호출할 때 결정됨)


1-1. 전역 공간 this

전역 공간에서 this는 전역 객체를 가리킴. (개념상 전역 컨텍스트를 생성하는 주체가 전역 객체이기 때문)

전역 객체는 자바스크립트 런타임 환경에 따라 다른 이름과 정보를 가짐
(브라우저 환경에서의 전역 객체 → window, Node.js 환경에서의 전역 객체 → global)

var a = 1;
console.log(a); // 1
console.log(window.a); // 1
console.log(this.a); // 1
  • 1) window.a, this.a가 1이 나오는 이유
    • 자바스크립트의 모든 변수는 특정 객체의 프로퍼티로 동작한다.
    • 실행 컨텍스트는 변수를 수집해서 LexicalEnvironment의 프로퍼티로 저장한다.
    • 이후 어떤 변수를 호출하면 LexicalEnvironment를 조회해서 일치하는 프로퍼티가 있을 경우 그 값을 반환한다.
  • 2) a를 직접 호출할 때도 1이 나오는 이유
    • 변수 a에 접근하고자 하면 스코프 체인에서 a를 검색하다가 가장 마지막에 도달하는 전역 스코프의 LexicalEnvironment(전역 객체)에서 해당 프로퍼티 a를 발견해서 그 값을 반환하기 때문이다.


1-2. 메소드 내부에서의 this

  • 함수 vs 메소드

함수는 그 자체로 독립적인 기능을 수행하는 반면

메소드는 자신을 호출한 대상 객체에 관한 동작을 수행한다.

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

var obj = {
    method: func
}

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

func() 함수를 호출했더니 this로 전역 객체 Window가 출력된다.

obj 객체의 method() 메소드를 호출했더니, this가 obj라고 출력된다.

함수로서의 호출 ⇒ func(1); ⇒ 함수 호출시 앞에 점이 없음.

메소드로서의 호출 ⇒ obj.method(2); 메소드 호출시 메소드 앞에 점이 있음.

  • 메소드 내부에서의 this

this에는 호출한 주체에 대한 정보가 담긴다.

어떤 함수를 메소드로 호출하는 경우 호출 주체는 함수명 앞의 객체이다. (점 표기법의 경우 마지막 점 앞으 명시된 객체가 곧 this가 되는 것)

var obj = {
    methodA: function() {
        console.log(this);
    }
}

obj.methodA(); // {methodA: f} (=== obj)


1-3. 함수 내부에서의 this

  • 함수 내부에서의 this그래서 실행 컨텍스트를 활성화할 당시에 this가 지정되지 않은 경우는 this는 전역 객체를 바라보기 때문에
  • 함수에서의 this는 전역객체를 가리킨다.
  • 어떤 함수를 함수로서 호출할 경우에는 this가 지정되지 않는다.
  • 메소드 내부함수에서의 this
      var obj1 = {
          outer: function() {
              console.log(this); // (1)
    
              var innerFunc = function() {
                  console.log(this); // (2), (3)
              }
              innerFunc(); // (2) 함수로서의 호출 => window(전역 객체)
    
              var obj2= {
                  innerMethod: innerFunc
              };
              obj2.innerMethod(); // (3) 메소드로서의 호출 = obj2
          }
      };
    
      obj1.outer(); // (1) 메소드로서의 호출 => obj1
  • 내부함수도 어떤 함수를 함수로서 호출했는지 메소드로서 호출했는지만 파악하면 this의 값을 정확히 맞출 수 있다.
  • 메소드 내부 함수에서의 this를 우회하는 방법

호출 주체가 없을 때는 자동으로 전역객체를 바인딩하지 않고 주변 환경의 this를 그대로 상속받아 사용할 수 있게 하고 싶다면

ES5까지의 방법 (자체적으로 내부 함수에 this를 상속할 방법이 없지만, 우회하는 방법으로 가능)

var obj = {
    outer: function() {
        console.log(this);

        var innerFunc1 = function() {
            console.log(this);
        }
        innerFunc1(); // (2) Window

        var self = this;
        var innerFunc2 = function() {
            console.log(self);
        }
        innerFunc2(); // {outer: f} === obj
    }
}
obj.outer(); // (1) {outer: f} === obj
  • this를 바인딩하지 않는 함수

ES6에서는 함수 내부에서 this가 전역객체를 바라보는 문제를 보완하고자, this를 바인딩하지 않는 화살표 함수를 새로 도입하였다.

화살표 함수는 실행 컨텍스트를 생성할 때 this 바인딩 과정 자체가 빠지게 되어, 상위 스코프의 this를 그대로 활용할 수 있다.

var obj = {
    outer: function() {
        console.log(this);
        var innerFunc = () => {
            console.log(this);
        };
        innerFunc(); // (2) obj -> 함수 호출이지만 화살표 함수여서 
                              // this가 전역 객체(Window)가 아닌 상위 스코프의 this인 obj를 가리킴
    }
};

obj.outer(); // (1) obj


1-4. 생성자 함수 내부에서의 this

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

객체지향 언어에서는 생성자를 클래스, 클래스를 통해 만든 객체를 인스턴스라고 한다. (생성자는 구체적인 인스턴스를 만들기 위한 일종의 틀이다.)

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

var Cat = function(name) {
    this.name = name;
}

var choco = new Cat('초코');
console.log(choco); // Cat {name: '초코'}


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

this의 규칙을 깨고 this에 별도의 대상을 바인딩하는 방법도 있다.


2-1. call 메소드

call 메소드는 메소드의 호출 주체인 함수를 즉시 실행하도록 하는 명령.

call 메소드의 첫 번째 인자를 this로 바인딩하고, 이후의 인자들을 호출할 함수의 매개변수로 한다.

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

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


2-2. apply 메소드

call 메소드와 기능적으로 완전히 동일하다.

call 메소드와 달리 apply 메소드는 두 번째 인자를 배열로 받아 그 배열의 요소들을 호출할 함수의 매개변수로 지정하는 차이가 있다.

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

func.apply({x :1}, [4, 5]; // {x: 1} 4 5


2-3. bind 메소드

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

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

func(1, 2); // Window{...} 1 2

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

cf) bind 메소드를 적용해서 새로 만든 함수는 name 프로퍼티에 동사 bind의 수동태인 bound라는 접두어가 붙는다.

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

var bindFunc1 = func.bind({x; 1});

console.log(func.name); // func
console.log(bindFunc1.name); // bound func


2-4. 화살표 함수의 예외사항

ES6에 새로 도입된 화살표 함수는 실행 컨텍스트 생성시 this를 바인딩하는 과정이 제외된다.

즉 이 함수 내부에는 this가 아예 없으며, this에 접근하고자 하면 스코프 체인상 가장 가까운 this에 접근하게 된다.

var obj = {
    outer: function() {
        console.log(this); // (1) obj
        var innerFunc = () => {
            console.log(this); // (2) obj => 스코프 체인상 가장 가까운 this인 obj
        };
        innerFunc();
    }
};

obj.outer();

내부 함수를 화살표 함수로 바꾸면, 별도의 변수로 this를 우회하거나 (1-3 2번째 예제) call/apply/bind (2-1, 2-2, 2-3)를 적용할 필요가 없어 더욱 간결하고 편리하다.

반응형