[JS] this - 상황에 따라 달라지는 this

이승혜·2021년 9월 4일
0

JS/JQUERY

목록 보기
8/10
post-thumbnail

이 글은 📕코어 자바스크립트 책을 바탕으로 정리한 글입니다.

자바스크립트 코드를 작성하면서 this를 종종 사용하는데,
this에 대한 정확한 작동 방식을 이해하지 못하다보니 this 사용을 꺼리게 된다(이게 왜 이 대상을 바라보는거야? 😫 라는 의문)..
다른 대부분의 객체지향 언어에서의 this란 클래스로 생성한 인스턴스 객체를 의미하지만,
자바스크립트에서의 this는 어디서든 사용할 수 있기 때문에 혼란을 야기하기 쉬운 것 같다.
this에 대한 정확한 작동 방식을 이해해보자
뭐든 알고 쓰자!!

💡 상황에 따라 달라지는 this

자바스크립트에서 this는 기본적으로 실행 컨텍스트가 생성될 때 함께 결정된다.
실행 컨텍스트는 함수를 호출할 때 생성되므로, 바꿔 말하면 this는 함수를 호출할 때 결정된다고 할 수 있다.
즉 함수를 어떤 방식으로 호출하느냐에 따라 값이 달라지는 것이다.

전역 공간에서의 this

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

전역 공간에서의 this(브라우저 환경)

📌 전역 공간에서만 발생하는 특이한 성질 하나
전역 변수를 선언하면 자바스크립트 엔진은 이를 전역객체의 프로퍼티로도 할당한다.
변수이면서 객체의 프로퍼티이기도 한 셈

var a = 1;
console.log(a); // 1
console.log(window.a); // 1
console.log(this.a); // 1

전역공간에서 선언한 변수 a에 1을 할당했을 뿐인데
window.athis.a 모두 1이 출력된다

console.log(this);

위 코드에서 this를 출력해보면,

a가 전역객체의 프로퍼티로 할당된 것을 볼 수 있다.

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

함수 vs. 메서드

어떤 함수를 실행하는 가장 일반적인 방법 두 가지

  • 함수로서 호출
  • 메서드로서 호출

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

❗️ 어떤 함수를 객체의 프로퍼티에 할당한다고 해서 그 자체로서 무조건 메서드가 되는 것이 아니라,
객체의 메서드로서 호출할 경우에만 메서드로 동작하고 그렇지 않으면 함수로 동작한다.
아래 예제로 확인해보자

함수로서의 호출, 메서드로서의 호출

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

// (1) 함수로서의 호출
func(1); // x=1

var obj = {
  method: func,
};

// (2) 메서드로서의 호출
obj.method(2); // x=2
  • (1) 함수로서의 호출 결과
  • (2) 메서드로서의 호출 결과

(1)에서 func를 호출했더니 this로 전역객체 Window가 출력되는 것을 볼 수 있다.
(2)에서 obj라는 변수에 객체를 할당하는데, 그 객체의 method 프로퍼티에 앞에서 func 함수를 할당했다.
그리고 obj의 method를 호출했더니 this가 obj를 가르킨다.

즉 원래의 익명함수는 그대로인데 이를 변수에 담아 호출한 경우와 obj 객체의 프로퍼티에 할당해서 호출한 경우 this가 달라지는 것.

함수로서의 호출과 메서드로서의 호출 구분 방법

함수 앞에 점(.)이 있는지의 여부만으로 간단하게 구분할 수 있다.
(물론 대괄호 표기법에 따른 경우도 메서드로서 호출한 것임)

var obj = {
  method: function (x) {
    console.log(this, x);
  },
};
obj.method(1);
obj['method'](2);

메서드 내부에서의 this

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

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

obj.methodA();
obj.inner.methodB();

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

함수 내부에서의 this

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

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

메서드 내부에서 정의하고 실행한 함수에서의 this는 무엇일까?
앞서 배웠듯 내부함수 역시 이를 함수로서 호출했는지 메서드로서 호출했는지만 파악하면 this의 값을 정확히 맞출 수 있다.

var obj1 = {
  outer: function () {
    console.log(this); // (1)
    var innerFunc = function () {
      console.log(this); // (2) (3)
    };
    innerFunc();

    var obj2 = {
      innerMethod: innerFunc,
    };
    obj2.innerMethod();
  },
};

obj1.outer();


다음과 같은 결과가 나온다

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

호출 당시 주변 환경의 this를 상속받아 사용할 수 없을까?

변수를 검색하면 우선 가장 가까운 스코프의 LE를 찾고 없으면 상위 스코프를 탐색하듯이, this역시 현재 컨텍스트에 바인딩된 대상이 없으면 직전 컨텍스트의 this를 바라볼 수 있었으면 좋겠다.
ES5까지는 자체적으로 내부함수에 this를 상속할 방법이 없지만 이를 우회할 방법이 있긴했다.

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

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


위와 같이 outer 스코프에서 self라는 변수에 this를 저장한 상태에서 출력해주는 방식이다.

this를 바인딩하지 않는 함수

ES6에서는 함수 내부에서 this가 전역객체를 바라보는 문제를 보완하고자, this를 바인딩하지 않는 화살표 함수(arrow function)을 새로 도입했다!
화살표 함수는 실행 컨텍스트를 생성할 때 this 바인딩 과정 자체가 빠지게 되어, 상위 스코프의 this를 그대로 활용할 수 있다.
즉 위에서 설명한 우회 방식을 사용하지 않아도 된다는 것!!!

var obj = {
  outer: function () {
    console.log(this);
    var innerFunc = () => {
      console.log(this);
    };
    innerFunc();
  },
};
obj.outer();

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

함수 A의 제어권을 다른 함수(또는 메서드) B에게 넘겨주는 경우 함수 A를 콜백 함수라 한다.
이때 함수 A는 함수 B의 내부 로직에 따라 실행되며, this역시 함수 B 내부 로직에서 정한 규칙에 따라 값이 결정된다.

콜백 함수도 함수이기 때문에 기본적으로 this가 전역객체를 참조하지만, 제어권을 받은 함수에서 콜백 함수에 별도로 this가 될 대상을 지정한 경우에는 그 대상을 참조하게 된다.

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

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

document.body.innerHTML += '<button id="a">클릭</button>';
document.body.querySelector('#a').addEventListener('click', function (e) {
  console.log(this, e); // (3)
});
  • (1)의 결과

    setTimeout 함수는 300ms 만큼 시간 지연을 한 뒤 콜백 함수를 실행하라는 명령이다. 0.3초 뒤 전역객체가 출력됨

  • (2)의 결과

    forEach 메서드는 배열의 각 요소를 앞에서부터 차례로 꺼내어 그 값을 콜백 함수의 첫 번째 인자로 삼아 함수로 실행하라는 명령이다.
    전역객체와 배열의 각 요소가 총 5회 출력

  • (3)의 결과

    addEventListener는 지정한 엘리먼트에 "click"이벤트가 발생할 때마다 그 이벤트 정보를 콜백 함수의 첫 번째 인자로 삼아 함수를 실행하라는 명령이다.
    버튼을 클릭하면 지정한 엘리먼트와 클릭 이벤트에 관한 정보가 담긴 객체가 출력된다.

📌 (1)의 setTimeoute 함수와 (2)의 forEach 메서드는 그 내부에서 콜백 함수를 호출할 때 대상이 될 this를 지정하지 않는다. 따라서 this는 전역객체를 참조
한편 (3)의 addEventListener 메서드는 콜백 함수를 호출할 때 자신의 this를 상속하도록 정의돼있다. 즉 점(.)앞부분이 곧 this가 되는 것!

생성자 함수 내부에서의 this

생성자 함수는 어떤 공통된 성질을 지니는 객체들을 생성하는 데 사용하는 함수다.
객체지향 언어에서는 생성자를 클래스(class), 클래스를 통해 만든 객체를 인스턴스(instance)라고 한다.

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

var Cat = function (name, age) {
  this.bark = '야옹';
  this.name = name;
  this.age = age;
};
var ddong = new Cat('뚱이', 7); // (1)
var coco = new Cat('코코', 6); // (2)
console.log(ddong, coco);


Cat이란 변수에 익명 함수를 할당했다.
이 함수 내부에서는 this에 접근해서 bark, name, age 프로퍼티에 각각 값을 대입한다.
그리고 new 명령어와 함께 Cat 함수를 호출해서 변수 ddong, coco에 각각 할당했다.
즉 (1)에서 실행한 생성자 함수 내부에서의 this는 ddong 인스턴스를,
(2)에서 실행한 생성자 함수 내부에서의 this는 coco 인스턴스를 가르키는 것을 알 수 있다.

profile
더 높이

0개의 댓글