this in setInterval

Danuel·2019년 4월 11일
4
post-thumbnail

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)는 지원하지 않는 문법이라는 이슈가 있습니다. 따라서, 지원해야 하는 브라우저가 이 문법을 지원하는지 먼저 살펴보아야 합니다.

profile
다뉴하는 코딩

2개의 댓글

comment-user-thumbnail
2019년 4월 12일

JavaScript를 하면서 이해하기 어려운 것이 있었다면 댓글로 알려주세요.

답글 달기
comment-user-thumbnail
2019년 4월 25일

4-1의 예제는 크롬 파이어폭스 엣지에서 실행이 안 되네요.


setInterval과 절달되는 함수의 this는 상관이 없을거에요.

자바스크립트에서 this는 함수를 실행할 때 결정됩니다.

정확히는 '함수 호출 코드'(call expression)를 평가하면서 결정되는데요.

const person = new Person(NAME);

const func = person.print;

// 1. property accessor (dot.) 사용
person.print();

// 2. 일반적인 함수 호출
func();

1번의 경우에는 호출 괄호 좌측을 평가할 때 base object가 person으로 지정되고, base object인 person이 print의 this로 사용됩니다.

2번의 경우에는 base object는 null로 지정됩니다. 다만 strict mode가 아닐 경우 base object는 global(브라우저라면 window)로 지정되어 this로 사용됩니다.

본문의 예제 코드에서 setInterval로 전달한 익명 함수에서 this가 person이 아니라 global(window)이 되는 이유는, 전달된 익명함수를 실행할 때 위의 '2번의 경우'처럼 base object가 없기 때문입니다.

사족

++ this를 결정할 때 person.print()처럼 "property accessor를 사용했냐, 안 했냐" 보다는 더 복잡한 과정을 거치기 때문에 property accessor를 사용했다고 무조건 this가 dot(.) 왼편의 객체가 되는 것은 아닙니다.

+++ this가 정확하게 어떻게 결정되는지 궁금하시면 이 글을 읽어보시는 걸 추천합니다.

답글 달기