setInterval 함수 안에서 this를 사용하면 예상과는 다른 동작 을 합니다. 이 동작을 이해하기 위해서는 JavaScript의 2가지 동작을 먼저 알아볼 필요가 있습니다.

JavaScript의 시작

JavaScript는 웹에서부터 시작했습니다. 초기에는 HTML의 script 태그 안에서 작성하는 형태였고 태그를 간단하게 조작하는 수준이었기 때문에, 모듈 시스템을 구현해야 할 필요성을 느끼지 못 했습니다.

시간이 흐르면서 웹은 많은 기대를 받으면서 발전을 하기 시작했고, 복잡한 코드도 어떻게든 작동을 해야 할 뿐만 아니라, 서로 다른 script 태그에서 변수를 공유할 방법이 필요했습니다.

function run() {
  foo = "bar";
  // window.foo = 'bar';
}

run();

console.log(foo); // window.foo

그 방법이 window라는 글로벌 변수이며, 접근 가능한 scope 안에서 변수를 선언한 적이 없고 변수 선언자(var 등)가 없으면 window의 속성으로 할당하는 동작을 도입했습니다.

반대로 보면, window를 굳이 작성하지 않아도 언제든지 window에 접근을 할 수 있기도 합니다.

JavaScript의 this

JavaScript의 this는 다른 언어와는 동작이 묘하게 다릅니다.

작성한 시점에서 this의 scope를 결정하는 것이 아니라, 실행 시점에 해당 this의 scope를 결정하기 때문입니다.

var foo = {
  bar: 0,
  baz: function() {
    return this.bar;
  }
};

console.log(foo.baz()); // 0

var baz = foo.baz;
console.log(baz()); // undefined

예시 코드에서 foo.baz 메서드의 this는 foo를 가리킵니다. 따라서 this.barfoo.bar 일 것이라고 예상할 수 있습니다.

실제로 그런지 확인해보면 1번째 console.log 에서는 0 이 나오지만, 2번째 console.log 처럼 foo.baz 를 따로 변수에 할당해서 확인해보면 undefined 를 출력합니다.

따로 변수에 할당하면 해당 함수를 실행하는 시점에서의 thisfoo 를 발견할 수 없기 때문에 더 상위의 scope를 찾는데, 이 코드에서는 더 이상 scope가 없고 따라서 thiswindow 를 가리킵니다. 최종적으로, window.bar 의 값을 return 하는 함수이기 때문에 undefined 를 출력한 것입니다.

살펴보았듯이 JavaScript의 this 는 실행 시점에 scope를 결정합니다.

setInterval의 this

드디어 setInterval의 동작을 살펴보겠습니다.

class Person {
  constructor(name) {
    this.name = name;
  }

  print() {
    setInterval(function() {
      console.log(this.name);
    }, 1000);
  }
}

const NAME = "Danuel";
const person = new Person(NAME);
person.print();

person.print 는 1초 마다 this.name 을 출력하는 함수입니다.

'Danuel' 을 출력할 것이라 예상할 수 있지만, 예상과는 다르게 undefined 를 출력합니다.

Person.print 메서드 안에서 쓰인 setInterval은 사실 window.setInterval 입니다. 그렇기 때문에 setInterval에서 this를 사용하면 window를 가리키고, window.name 은 아무런 값이 없기 때문에 undefined 를 출력한 것입니다.

우리는 setInterval의 동작을 이해했습니다.

setInterval에서 this 사용하기

동작을 살펴본 것만으로도 충분한가 하면 아직은 아쉬운 부분이 있습니다.

"""그렇다면 this를 쓸 수 없나요?"""

쓸 수 있습니다! 3가지 방법을 소개하겠습니다!

  1. 변수 할당을 통한 scope 이동

    class Person {
      constructor(name) {
        this.name = name;
      }
    
      print() {
        var setInterval = window.setInterval;
        setInterval(function() {
          console.log(this.name);
        }, 1000);
      }
    }
    
    const NAME = "Danuel";
    const person = new Person(NAME);
    person.print();

    변수로 할당해서 this를 window에서 Person로 가리키게 하는 원리입니다.

    가장 간단한 방법이지만 JavaScript를 조금 더 이해하고 있어야 의도를 알 수 있다는 이슈가 있습니다. JavaScript에 익숙하지 않거나 다른 언어를 하던 분이라면 무의미한 코드라고 생각하고 var setInterval = window.setInterval 을 지울 수도 있습니다.

  2. this를 가리키는 변수 생성

    class Person {
      constructor(name) {
        this.name = name;
      }
    
      print() {
        var self = this;
        setInterval(function() {
          console.log(self.name);
        }, 1000);
      }
    }
    
    const NAME = "Danuel";
    const person = new Person(NAME);
    person.print();

    setInterval 바깥에서 this를 가리키는 변수를 생성하고, 그 변수를 사용하는 방법입니다.

    1번째 방법과는 다르게, JavaScript에 대한 이해가 부족하더라도 코드를 읽으면서 자연스럽게 이해할 수 있습니다. 만약 이해를 못 하더라도 무엇인가 의도가 있을 것이라 예상할 수 있습니다.

  3. Arrow Function 사용

    class Person {
      constructor(name) {
        this.name = name;
      }
    
      print() {
        setInterval(() => {
          console.log(this.name);
        }, 1000);
      }
    }
    
    const NAME = "Danuel";
    const person = new Person(NAME);
    person.print();

    자신의 상위 scope를 가리키는 Arrow Function의 특성을 이용하는 방법입니다.

    앞서 소개한 방법과 다르게 JavaScript의 동작을 이해해야 할 필요가 없다는 장점이 있지만, 인터넷 익스플로러(Internet Explorer)는 지원하지 않는 문법이라는 이슈가 있습니다. 따라서, 지원해야 하는 브라우저가 이 문법을 지원하는지 먼저 살펴보아야 합니다.