티스토리 뷰

this를 이미 한 차례 정리하긴 했지만(https://developer-alle.tistory.com/327) 이번에는 this 바인딩 예시 위주의 포스팅을 해보려고 한다.

this의 개념

객체지향 프로그래밍에서 객체는 상태(state)를 나타내는 프로퍼티와 동작(behavior)을 나타내는 메서드를 하나의 단위로 묶은 자료 구조다. 동작을 나타내는 메서드는 자기 자신이 속한 객체의 상태를 참조하고 변경할 수도 있어야 한다. 이 때, 자기 자신이 속한 객체를 참조하기 위한 식별자가 this다. this 는 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기 참조 식별자다.

this의 결정 방식

다른 언어들과는 다르게 자바스크립트에서 this는 함수가 호출되는 방식에 따라 동적으로 결정된다. 어떻게 선언되었든지 간에 호출을 어떻게 하느냐에 따라 this가 가리키는 객체가 다를 수 있다.

이미 정리한 포스팅에서도 언급했다시피 아래 함수 호출 방식 4가지에 따라 this 바인딩이 달라진다.

함수 호출 방식 this 바인딩
일반 함수 호출 전역 객체
메서드 호출 메서드를 호출한 객체
생성자 함수 호출 생성자 함수가 생성할 인스턴스
Function.prototype.apply/call/bind 메서드에 의한 간접 호출 해당 메서드의 첫 번째 인수로 전달한 객체

일반 함수로 호출할 때 전역 객체에 this 바인딩이 되는 건 중첩 함수(다른 함수 내부에서 정의된 함수)여도 마찬가지고, 콜백이어도 마찬가지다. 그래서 헷갈리기 쉬운 것 중 하나가 콜백 함수에서 this를 참조할 때이다. 아래 예시들을 보자.

콜백 함수에서의 this

Timer라는 생성자 함수가 있고 seconds라는 내부 변수를 갖고 있다. setInterval 함수는 1초마다 Timer 객체 안에 정의된 seconds 변수의 값을 1씩 증가시키려고 한다. 그리고 타이머 인스턴스를 만든 뒤, setTimeout을 설정하여 3.1초 뒤에 인스턴스의 seconds 변수 값이 어떻게 되어있는지 확인한다. 의도한대로라면 3초 이상 흘렀기 때문에 seconds 값은 3이어야 한다.

function Timer () {
  this.seconds = 0;
  setInterval(function () { this.seconds++ }, 1000);
}

var timer = new Timer();
setTimeout(() => console.log(timer.seconds), 3100); // 0

하지만 예시에서 출력되는 값은 0이다. 그 이유는 setInterval의 첫 번째 인자인 콜백 함수가 1초 뒤에 _일반 함수로 호출_되기 때문이다. 위에서 언급했듯이 일반 함수로 호출하면 this는 전역 객체에 바인딩이 된다. 즉 전역 객체의 변수인 seconds를 찾아서 연산을 한 것이지 timer 객체의 변수인 seconds에 연산을 한 것이 아니기 때문에, 값은 여전히 초기값 그대로 0이 출력된다.

이런 실수를 피하기 위해서 할 수 있는 방법이 몇 가지 있다.

첫 번째는 화살표 함수를 사용하는 것이다. 화살표 함수는 ES6에서 추가된 함수 정의 방식인데, => 를 통해서 간략하게 함수를 나타낼 수 있다. 화살표 함수가 기존 함수와 다른 점은 화살표 함수는 함수 자체의 this 바인딩을 갖지 않고, 스코프 체인을 통해 상위 스코프의 this를 참조한다는 점이다.

아래 예시에서 보면 setInterval의 콜백을 화살표 함수로 정의했을 뿐인데 3이 정상적으로 출력되는 것을 확인할 수 있다. 상위 스코프의 this를 참조해서 Timer 생성자 함수가 생성할 인스턴스를 가리키게 되었기 때문이다.

function Timer () {
  this.seconds = 0;
  setInterval(() => this.seconds++, 1000); // 화살표 함수
}

var timer = new Timer();
setTimeout(() => console.log(timer.seconds), 3100); // 3

두 번째는 this를 변수에 할당해서 클로저를 이용하는 것이다. this는 키워드이고 식별자 역할을 하지만 변수는 아니다. 즉 상위 스코프의 this를 그대로 참조할 수는 없고, 이 값을 어떤 변수에 할당해서 그 변수에 접근함으로써 상위 스코프의 this에 접근하는 것이 가능하다는 뜻이다.

아래 예시에서는 self라는 변수에 this를 할당했고, 콜백 함수 내에서 self를 참조함으로써 인스턴스의 변수인 seconds 값이 3으로 출력되는 것을 볼 수 있다.

function Timer () {
  this.seconds = 0;
  var self = this; // 상위 스코프 변수에 할당한 후, 클로저를 사용해서 접근한다. 
  setInterval(function () { self.seconds++ }, 1000);
}

var timer = new Timer();
setTimeout(() => console.log(timer.seconds), 3100); // 3

세 번째는 명시적으로 this를 바인딩하는 것이다. () => this.secondsfunction () { this.seconds }.bind(this) 의 this 바인딩은 동일하다. bind 메서드를 사용해서 콜백 함수 바깥의 this 바인딩을 전달한다.

function Timer () {
  this.seconds = 0;
    setInterval(function () { this.seconds++ }.bind(this), 1000); // 명시적 바인딩
}

var timer = new Timer();
setTimeout(() => console.log(timer.seconds), 3100); // 3

Ref

ponyfoo.com/articles/es6-arrow-functions-in-depth

 

ES6 Arrow Functions in Depth

The daily saga of es6-in-depth articles continues. Today we’ll be discussing Arrow Functions. In previous articles we’ve covered destructuring and template …

ponyfoo.com

위키북스 <모던 자바스크립트 Deep Dive>

'공부일지(TIL) > JavaScript' 카테고리의 다른 글

[JavaScript] Math.floor vs parseInt (feat. ~~)  (1) 2021.05.08
[JavaScript] 비동기 프로그래밍  (0) 2021.04.04
[JavaScript] Getter  (0) 2021.04.01
[JavaScript] 숫자(Number) 타입  (0) 2021.03.23
[JavaScript] 어휘 구조  (0) 2021.03.23
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함