이번 포스팅에서는 자바스크립트의 this에 대해 알아보고자 한다.
< 목차 >
- this란?
- this 바인딩
- 화살표 함수와 this
1. this란?
일반적으로 객체지향 언어에서 this는 클래스로 생성한 인스턴스 객체를 의미한다. 하지만 자바스크립트의 this는 클래스에서만 사용되는 것이 아니라 어디서든 사용될 수 있으며 상황에 따라 this가 바라보는 대상이 달라지기도 한다. 이러한 특성 때문에 자바스크립트를 공부할 때 혼란스러움을 겪는 개념 중의 하나가 바로 this가 아닐까 싶다.
this가 난해한 이유는 바로 상황에 따라 this가 가리키는 대상이 달라지기 때문인데, this가 바라보는 대상이 무엇인지에 대한 실마리는 this가 결정되는 시점에서 얻을 수 있다. 자바스크립트에서 this는 기본적으로 실행 컨텍스트(Execution context)가 생성될 때 함께 결정된다. 실행 컨텍스트는 함수를 호출할 때 생성되므로 바꿔 말하면 this는 함수를 호출할 때 결정된다고 할 수 있다. 함수를 어떤 방식으로 호출하는지에 따라 this의 값이 달라지는 것이다. 이를 좀 더 그럴듯한 말로 포장하면 "함수를 호출할 때, 함수가 어떻게 호출되었는지에 따라 this에 바인딩할 객체가 동적으로 결정된다"라고 얘기할 수 있다.
2. this 바인딩
앞서 함수가 호출되는 상황에 따라 this가 가리키는 객체가 달라진다고 얘기하였다. 각각의 상황별로 this가 어떤 객체를 바라보게 되는지 살펴보도록 하자.
👉 전역 공간에서의 this
우선 전역 공간에서 this는 전역 객체를 가리킨다. 브라우저 환경에서는 window 객체를, Node.js 환경에서는 global 객체를 바라본다.
// 브라우저 환경
console.log(this === window) // true
// Node.js 환경
console.log(this === global) // true
함수를 실행하는 방법은 여러가지가 있지만, 일반적으로 함수가 호출되는 상황들은 크게 다음과 같이 나눌 수 있다.
- 메소드로서 호출
- 함수로서 호출
메소드는 자신을 호출한 대상 객체에 관한 동작을 수행한다는 관점으로 바라볼 수 있고, 함수로서의 호출은 함수 그 자체로 독립적인 기능을 수행한다는 관점으로 볼 수 있다.
👉 메소드로서 호출
메소드는 "객체의 프로퍼티에 할당된 함수"로 이해할 수 있다. 하지만, 함수를 객체의 프로퍼티에 할당한다고 해서 그 자체로 무조건 메소드가 되는 것이 아니라 객체의 메소드로서 호출할 경우에만 메소드로 동작하고 그렇지 않으면 함수로 동작한다. 그렇다면 "함수로서 호출"과 "메소드로서 호출"은 어떻게 구분할까? 간단하다. 함수가 호출될 때 점 표기법(.) 혹은 대괄호 표기법이 사용되었다면 메소드로서의 호출이라고 생각하면 된다.
const obj = {
outer: function() {
console.log(this)
}
obj.outer() // { outer: [Function: outer] }
obj['outer']() // { outer: [Function: outer] }
함수를 호출할 때 함수 앞에 점(.)이 있거나 대괄호 표기법과 함께 함수 이름 앞에 객체가 명시되어 있는 경우 해당 함수는 메소드로 호출한 것이다. this에는 호출 주체에 대한 정보가 담긴다. 따라서 어떤 함수를 메소드로서 호출한 경우 호출 주체는 함수명 앞의 객체가 되고 this는 자신이 속한 객체(해당 메소드를 가지고 있는 객체)에 바인딩 된다.
👉 함수로서 호출
메소드가 아닌 일반 함수로 호출되는 함수의 this는 전역 객체(브라우저에서는 window, Node.js에서는 global)에 바인딩 된다. 이는 어떤 함수를 함수로서 호출할 경우 this가 지정되지 않기 때문이다. this에는 호출 주체에 대한 정보가 담기는데 함수로서 호출하는 것은 메소드의 경우와 다르게 호출 주체를 명시하지 않고 실행하는 것이기 때문에 호출 주체에 대한 정보를 알 수 없는 것이다. 함수가 호출되고 실행 컨텍스트가 생성되어 this 바인딩에 대한 정보가 등록될 때 this가 지정되지 않은 경우 this에는 전역 객체가 저장된다. 따라서 함수에서의 this는 전역 객체를 가리킨다.
const func = function() {
console.log(this) // Object [global] { ... }
}
func()
함수로서 호출할 때 this가 전역 객체에 바인딩 되는 상황은 메소드의 내부함수에서의 this에도 마찬가지로 적용된다.
const obj = {
outer: function() {
console.log(this) // { outer: [Function: outer] }
const inner = function() {
console.log(this) // Object [global] { ... }
}
inner()
}
}
obj.outer()
위와 같이 obj.outer 메소드 안에서 선언된 내부함수 inner가 함수로서 호출될 경우 메소드 내부에서 정의하고 실행한 함수라고 할지라도 해당 함수에서의 this는 전역 객체를 가리킨다. 자바스크립트 언어의 개발자인 더글라스 크락포드는 "이것은 설계 단계의 결함으로 메소드가 내부함수를 사용하여 자신의 작업을 돕게 할 수 없다는 것을 의미한다"라고 말한다. 창시자도 인정한 명백한 설계상의 오류인 것이다. 이러한 문제를 해결하기 위해 call, apply, bind 등의 메소드를 활용해 함수를 호출할 때 명시적으로 this를 지정해주거나 화살표 함수를 사용하기도 한다.
3. 화살표 함수와 this
지금까지 this는 함수가 호출되는 방식에 따라 결정되며, 함수가 선언된 위치의 스코프나 컨텍스트는 this 바인딩에 직접적인 영향을 미치지 않는다는 것을 살펴보았다. 하지만, 화살표 함수는 그렇지 않다. 앞서 언급한 메소드의 내부함수에서의 this와 같이 함수 내부에서 this가 전역 객체를 바라보는 문제를 보완하고자 ES6에서는 this를 바인딩하지 않는 화살표 함수(Arrow function)를 새로 도입했다. 화살표 함수는 실행 컨텍스트를 생성할 때 this 바인딩 과정 자체가 빠지게 되어, 상위 스코프의 this를 그대로 활용할 수 있다. 쉽게 말해, 화살표 함수 내부에는 this가 없다고 생각하면 된다.
자바스크립트에서는 어떤 식별자(변수)를 찾을 때 현재 Lexical Environment의 environmentRecord(환경 레코드)에서 해당 식별자를 찾지 못하면 outerEnvironmentReference(외부 환경 참조)를 통해 현재 호출된 함수가 선언될 당시의 Lexical Environment를 참조한다. 다시 말해, 현재 환경에서 식별자를 찾지 못하면 점점 상위 환경으로 타고 타고 올라가며 이 과정은 원하는 리소스를 찾을 때까지 혹은 전역 스코프에 도달할 때까지 계속된다. (이렇게 식별자의 유효범위를 안에서부터 바깥으로 차례로 검색해 나가는 것을 "스코프 체인"이라 한다) 화살표 함수에서의 this 바인딩 방식도 이와 유사하다. 화살표 함수 내부에는 this 자체가 존재하지 않기 때문에 선언 시점의 Lexical Environment의 this를 참조한다. function으로 선언된 함수는 메소드로 호출되냐 함수로서 호출되냐에 따라 동적으로 this가 바인딩 되는 반면, 화살표 함수는 선언된 시점의 상위 스코프에서의 this를 참조하는 것이다.
const obj = {
outer: function() {
console.log(this) // { outer: [Function: outer] }
const inner = () => {
console.log(this) // { outer: [Function: outer] }
}
inner()
}
}
obj.outer()
앞서 살펴본 obj.outer 메소드의 내부함수 inner를 위와 같이 화살표 함수로 선언할 경우 함수로서 호출됨에도 불구하고 inner 함수 내부의 this가 전역 객체가 아닌, 선언 시점의 Lexical Environment의 this를 참조하여 obj 객체를 가리키는 것을 확인할 수 있다.
'JavaScript' 카테고리의 다른 글
JavaScript - Map , Set (2) | 2023.11.01 |
---|---|
JavaScript - 불변성(Immutability) (2) | 2023.10.23 |
JavaScript - 일급 객체(First Class Object) & 일급 함수(First Class Function) (0) | 2023.10.19 |
JavaScript - 전역(global)변수 , 지역(local)변수 (0) | 2022.02.04 |
JavaScript ES6 - 템플릿 리터럴 , 객체 리터럴 , 구조분해 할당 (0) | 2022.01.27 |