this
자바스크립트의 함수에는 메서드가 호출된 개체를 참조하는 this라는 특수한 변수가 있다. this의 값은 함수 호출에 따라 동적으로 변하며, 이 때문에 작은 문제 상황에 직면하게 된다.
함수는 정적 범위(static/lexical scope)와 동적 범위의 두 가지 범위(scope)에서 실행된다. '정적 범위'는 함수를 둘러싸는 범위이고, '동적 범위'는 함수를 호출한 범위이다.
자바스크립트에서 함수는 생성자, 메서드(객체의 일부), 함수(서브 루틴, subroutines)의 역할을 하는데, 함수가 서브루틴(이름이 겹쳐 아래부터는 서브루틴으로 명명)의 역할을 할 때 this가 동적으로 변해 문제가 생긴다. 서브루틴이 객체에 대해 호출되지 않기 때문에 this의 값은 strict 모드에서 정의되지 않고 전역 범위로 설정된다. 이때문에 콜백함수를 사용하는게 어렵게 만든다.
아래 예제를 보자.
const person = {
default: "Hello",
greet: function (names) {
names.forEach(function (name) {
console.log(`${this.default} ${name}`);
});
},
};
person.greet(["world", "heaven"]);
// undefined world
// undefined heaven
위 코드를 보면 names배열에 forEach 함수에 서브 루틴을 전달하고 있다. 이때의 서브 루틴의 this값은 undefined이며, 외부 메서드인 greet의 this에는 접근할 수 없다.
그래서 이 문제를 해결하기 위해서는 이 서브루틴에 정적(lexical) this가 필요하며, 변수에 this를 할당한 다음 클로저를 통해 서브루틴에 접근하는 방식을 사용해야 한다.
const person = {
default: "Hello",
greet: function (names) {
const lexcicalThis = this;
names.forEach(function (name) {
console.log(`${lexcicalThis.default} ${name}`);
});
},
};
person.greet(["world", "heaven"]);
// Hello world
// Hello heaven
하지만 이런 식으로 코드를 작성한다면 문제야 해결은 되지만, 위 코드를 보는 동료들 또한 왜 이렇게 문제를 해결했는지에 대한 지식도 있어야 하며, 사이드 이펙트가 생길 여지조차 커진다.
그렇다면 위 방식 대신 화살표 함수를 사용해 보면 어떨까?
화살표 함수와 this
화살표 함수는 그 자체의 사용법을 매우 간단하다(Arrow function - MDN). 다만 화살표 함수는 화살표 함수 자신의 this 값을 가질 수 없으며, 대신 this는 화살표 함수를 둘러싸는 범위(정적 컨텍스트)의 this를 가져온다.
위의 예제를 화살표 함수를 이용하여 수정하면 아래와 같다.
const person = {
default: "Hello",
greet: function (names) {
names.forEach((name) => {
console.log(`${this.default} ${name}`);
});
},
};
person.greet(["world", "heaven"]);
// Hello world
// Hello heaven
this의 바인딩에 따른 분류
위 글은 this를 좀 더 이해하기 쉽게 풀어쓴 내용이다. 조금 더 정적인(?) 내용을 원한다면 아래의 내용을 참고하자.
기본 바인딩(Default binding)
- 전역 공간에서 this는 전역 객체를 참조한다.
- [브라우저]에서 함수가 단독 호출되면, "비엄격 모드"에서는 window가 바인딩되고, "엄격 모드"에서는 전역 객체는 바인딩 대상에서 제외되기 때문에 undefined가 바인딩 된다.
- [Node]에서 함수가 단독 호출되면 this는 마찬가지로 전역 객체에 바인딩이 되는데, 노드 환경에서는 전역객체인 global에 바인딩이 되고, 전역 코드에서의 this는 exports에 바인딩이 된다.
암시적 바인딩(Implicity binding)
- 함수를 메서드로서 호출한 경우 this 는 메서드 호출 주체(메서드명 앞에 객체)를 참조한다.
- 함수를 함수로써 호출한 경우 this 는 전역 객체를 참조한다. (메서드의 내부 함수도 동일)
- 콜백 함수 내부에서의 this 는 해당 콜백 함수의 제어권을 넘겨받은 함수가 정의한 바에 따르며, 정의 하지 않은 경우는 전역 객체를 참조한다.
- 생성자 함수에서의 this 는 생성될 인스턴스를 참조한다.
명시적 바인딩(Explicit binding)
- call, apply 메서드는 this 를 명시적으로 지정하면서 함수 또는 메서드를 호출한다.
- bind 메서드는 this 및 함수에 넘길 인수를 일부 지정해서 새로운 함수를 만든다.
- 요소를 순회하면서 콜백 함수를 반복 호출하는 내용의 일부 메서드는 별도의 인자로 this 를 받기도 한다.
참고) 더글러스 크락포드가 생각한 this
더글러스 크락포드는 그의 저서 [How JavaScript Works]에서 this를 완전히 사용하지 않을 것을 권장한다. this가 없이 프로그램을 만드는 것은 생각보다 쉬우며, this를 언어에서 제거해도 언어는 여전히 튜링 완전(Turing Complete)하기 때문이다.
this는 대명사이다. 언어에 this가 있는 것만으로도, 그 언어에 대해서 말하는 것이 얼마나 힘들지 모른다.
깃북으로 보기
https://ajdkfl6445.gitbook.io/study/javascript/arrow-function-and-this
'Javascript' 카테고리의 다른 글
[Javascript] 프로토타입(Prototype) (0) | 2022.01.01 |
---|---|
호이스팅(hoisting) (0) | 2021.12.30 |
Object를 만드는 3가지 방법 (0) | 2021.09.20 |
이젠 Modern JavaScript로 publish 하자 (0) | 2021.05.22 |
리팩터링(Refactoring)을 하는 이유 (0) | 2020.09.05 |