[JS] this

cabbage·2023년 6월 19일

JS

목록 보기
38/43
post-thumbnail

기술면접을 준비하는 과정에서 자바스크립트의 this 개념에 대해 공부하였고 this 개념에 대해 정리한다. 이 정리글은 코어 자바스크립트(정재남)를 참고하여 작성하였다.

this

자바스크립트에서 this는 상황에 따라 바라보는 대상이 달라진다.

상황에 따라 this는 달라진다

this는 함수를 호출할 때 결정된다.
함수 호출 방식에 따라 this가 달라진다.

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

전역 공간에서의 this

전역 공간에서 this는 전역 객체를 가리킨다.

  • 개념상 전역 컨텍스트의 생성 주체가 전역 객체이기 때문이다.
  • 전역 객체는 자바스크립트 런타임 환경에 따라 다르다.
    • 브라우저: Window
    • Node.js: Global

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

메서드로서 호출할 때 this는 메서드 호출 주체(메서드명 앞의 객체)를 참조한다.

어떤 함수를 객체의 프로퍼티에 할당한다고 무조건 메서드가 되는 것은 아니다. 객체의 메서드로서 호출하는 경우에만 메서드로 동작하고 그렇지 않으면 함수로 동작한다.

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

var obj = {
    method: func,
};
obj.method(2);    // { method: ƒ func() } 2
  • func 함수 호출 결과 this가 전역 객체 Window를 참조한다.
  • obj 변수에 할당한 객체의 method 프로퍼티에 func 함수를 할당한다.
  • obj.method 호출 결과 this가 obj 객체를 참조한다.

obj의 method에 할당한 함수와 func에 할당한 함수는 같은 함수다. 원래의 익명함수는 그대로지만, 이를 변수에 담아 호출한 경우와 객체의 프로퍼티에 할당해 호출한 경우 this는 달라진다.

'함수로서 호출'과 '메서드로서 호출'은 함수 앞에 점(.)의 존재 여부로 간단하게 구분할 수 있다.

  • func(1): 점이 없으니 함수로서 호출
  • obj.method(2): 점이 있으니 메서드로서 호출

결국 어떤 함수를 호출할 때 함수 이름 앞에 객체가 명시되어 있는 경우에는 메서드로서 호출한 것이다. 그렇지 않으면 모두 함수로 호출한 것이다.

메서드 내부에서의 this

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

어떤 함수를 메서드로서 호출하는 경우, 호출 주체는 바로 함수명(프로퍼티명) 앞의 객체다.

var obj = {
    methodA: function () {
        console.log(this);
    },
    inner: {
        methodB: function () {
            console.log(this);
        }
    }
};
obj.methodA();
/*
{
  methodA: ƒ methodA(),
  inner: { methodB: ƒ methodB() }
}
(=== obj)
*/

obj.inner.methodB(); // { methodB: ƒ methodB() } (=== obj.inner)
  • obj.methodA(): methodA의 호출 주체는 obj다.
    • this에 obj에 대한 정보가 담긴다.
  • obj.inner.methodB(): methodB의 호출 주체는 obj.inner이다.
    • this에 obj.inner에 대한 정보가 담긴다.

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

함수 내부에서의 this

함수로서 호출한 함수 내부에서의 this는 전역 객체를 가리킨다.

어떤 함수를 함수로서 호출한 경우 this가 지정되지 않는다. this에는 함수 호출 주체에 대한 정보가 담기는데, 함수로서 호출한 경우 호출 주체를 명시하지 않으므로 호출 주체를 알 수 없기 때문이다.

실행 컨텍스트 활성화 당시 this가 지정되지 않으면 결국 this는 전역 객체를 참조하게 된다. 따라서 함수로서 호출한 함수 내부에서의 this는 전역 객체를 가리킨다.

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

메서드의 내부함수 역시 함수로서 호출하는지 메서드로서 호출하는지를 파악하면 this를 알 수 있다.

var obj1 = {
    outer: function () {
        console.log(this);             
        
        var innerFunc = function () {
            console.log(this);         
        };
        innerFunc();                   // Window
            
        var obj2 = {
            innerMethod: innerFunc
        };
        obj2.innerMethod();            // { innerMethod: ƒ innerFunc() } (=== obj2)
    }
};
obj1.outer();                          // { outer: ƒ outer() } (=== obj1)
  • obj1.outer()
    • outer 메서드 호출 시 호출 주체를 명시했으므로 메서드로서 호출한 것이다.
    • outer 호출 주체는 obj1이므로 this에 obj1이 바인딩된다.
  • innerFunc()
    • innerFunc 함수 호출 시 호출 주체를 명시하지 않았으므로 함수로서 호출한 것이다.
    • 함수로서 호출하므로 this가 지정되지 않는다.
    • this에 전역 객체가 바인딩된다.
  • obj2.innerMethod()
    • innerMethod 메서드 호출 시 호출 주체를 명시했으므로 메서드로서 호출한 것이다.
    • innerMethod 호출 주체는 obj2이므로 this에 obj2가 바인딩된다.

outer 메서드의 내부함수 innerFunc를 함수로서 호출했을 때와 메서드로서 호출했을 때의 this 바인딩 결과가 다르다. 같은 함수이지만 this 바인딩 결과가 다르다.

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

메서드의 내부함수를 호출 주체 없이 호출하는 경우에도 자동으로 호출 당시 주변 환경의 this를 그대로 사용하기 위해 변수를 활용하는 우회 방법이 있다.

var obj1 = {
    outer: function () {
        console.log(this);             
        
        var innerFunc1 = function () {
            console.log(this);
        };
        innerFunc1();                  // Window
        
        var self = this;
        var innerFunc2 = function () {
            console.log(self);
        }
        innerFunc2();                  // { outer: ƒ outer() } (=== obj)
    }
};
obj1.outer();                          // { outer: ƒ outer() } (=== obj1)
  • innerFunc1 내부에서 this는 전역 객체를 참조한다.
  • innerFunc2 내부에서 self는 this(obj1)를 참조한다.
    • outer 스코프에서 self 변수에 this를 할당한 상태에서 innerFunc2를 호출한다.

this를 바인딩하지 않는 화살표 함수

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

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

var obj = {
    outer: function () {
        console.log(this);
        
        var innerFunc = () => {
            console.log(this);
        };
        innerFunc();         // { outer: ƒ outer() } (=== obj)
    }
};
obj.outer();                 // { outer: ƒ outer() } (=== obj)
  • outer 메서드의 innerFunc 내부함수에 화살표 함수를 할당하였다.
  • innerFunc의 호출 주체를 명시하지 않아도 this에는 obj 객체가 바인딩된다.
    • 상위 스코프인 outer 스코프의 this를 그대로 활용한다.

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

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

콜백 함수도 함수이므로 기본적으로 this가 전역 객체를 참조한다. 하지만 콜백 함수의 제어권을 받은 함수가 콜백 함수에 별도로 this가 될 대상을 지정하면 콜백 함수의 this가 그 대상을 참조한다.

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

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

document.body.innerHTML += '<button>클릭</button>';
document.body.querySelector('#a')
    .addEventListener('click', function (e) {
        console.log(this, e);
    });
  • setTimeout, forEach의 콜백 함수에서 this는 전역 객체를 참조한다.
  • addEventListener 메서드는 콜백 함수를 호출할 때 자신의 this를 상속하게 한다.
    • addEvnetListener 메서드의 점 앞부분이 곧 this가 된다.

콜백 함수의 경우, 콜백 함수의 제어권을 가지는 함수가 콜백 함수에서의 this가 무엇을 참조할지를 결정한다. 결정하지 않으면 기본적으로 함수와 마찬가지로 전역 객체를 참조한다.

생성자 함수 내부에서의 this

생성자 함수에서의 this는 생성될 인스턴스를 참조한다.

자바스크립트는 함수에 생성자 역할을 부여했다.

  • new 명령어와 함께 함수를 호출하면 해당 함수는 생성자로서 동작한다.
  • 어떤 함수가 생성자 함수로 호출된 경우 내부에서의 this는 새로 만들 인스턴스가 된다.

인스턴스 생성 과정

  • new 명령어와 함께 생성자 함수를 호출한다.
  • 생성자의 prototype 프로퍼티를 참조하는 __proto__ 프로퍼티가 있는 객체(인스턴스)를 만든다.
  • 생성자 함수 본문을 통해 프로퍼티들을 해당 객체(this)에 부여한다.
var Cat = function (name, age) {
    this.name = name;
    this.age = age;
};
var choco = new Cat('choco', 5);
var nabi = new Cat('nabi', 3);

console.log(choco);
console.log(nabi);
/*
Cat {
  name: 'choco',
  age: 5,
  __proto__: { constructor: ƒ Cat() }
}
Cat {
  name: 'nabi',
  age: 3,
  __proto__: { constructor: ƒ Cat() }
}
*/
  • 변수 Cat에 익명함수(생성자 함수)를 할당했다.
    • 이 익명함수 내부에서는 this에 접근해 name, age 프로퍼티에 값을 할당한다.
  • new 명령어와 함께 Cat 생성자 함수를 호출해 생성한 인스턴스를 변수 choco, nabi에 할당했다.
    • choco, nabi를 출력하면 각각의 인스턴스가 출력된다.
  • new 명령어와 함께 Cat 생성자 함수를 호출할 때 생성자 함수 내부에서의 this는 각각의 인스턴스를 가리키는 것을 알 수 있다.

참고

  • 코어 자바스크립트 - 정재남
profile
캐비지 개발 블로그입니다. :)

0개의 댓글