[JS] this

Pakxe·2023년 8월 15일
0

JavaScript

목록 보기
14/16
post-thumbnail

this - 정리

자바스크립트에서 this는 어디서나 사용할 수 있다. 이 this는 상황에 따라 가리키는 대상이 달라지는데, 왜 이걸 가리키는지 알기 어려운 경우도 있고, 예상과는 다른 걸 가리키기도 한다. 이런 경우에 원인을 파악하지 못한다면 오류를 해결하기 어렵다.

함수, 객체(메서드)의 구분이 느슨한 자바스크립트에서는 this가 이 둘을 구분하는 거의 유일한 기능이다. 이 장에서는 상황별로 this가 어떻게 달라지는지, 왜 그렇게 되는지, 예상과 다른걸 가리킬 때 그 원인을 알아보는 방법에 대해서 공부해보자.

1. 상황에 따라 달라지는 this

자바스크립트에서 this는 실행 컨텍스트가 생성될 때 함께 결정된다. 실행 컨텍스트는 함수를 호출할 때 생성되므로, this는 함수를 호출할 때 결정된다고 할 수 있다. 함수를 어떤 방식으로 호출하느냐에 따라 값이 달라지는 것이다.

이제부턴 여러 상황과 각 상황별로 this가 어떤 값을 보는지 알아보고, 원인도 알아보자.

전역 공간에서의 this

전역 공간에서의 this = 전역 객체

개념상 전역 컨텍스트를 생성하는 주체가 전역 객체이기 때문이다. 전역 객체는 자바스크립트 런타임 환경에 따라 다른 이름과 정보를 가지고 있다.

  • 브라우저 환경: window
  • Node.js: global

전역 공간에서만 발생하는 특이한 성질이 하나 있다.

자바스크립트의 모든 변수는 특정 객체의 프로퍼티로서 동작한다. 사용자가 var 연산자를 이용해 변수를 선헌해도 실제 자바스크립트 엔진은 어떤 특정 객체의 프로퍼티로 인식한다. 여기서 특정 객체 = 실행 컨텍스트의 LexicalEnvironment이다. 실행 컨텍스트는 변수를 수집해서 LE의 프로퍼티로 저장한다. 이후 어떤 변수를 호출하면 LE를 조회해서 일치하는 프로퍼티가 있을 경우 그 값을 반환한다. 전역 컨텍스트의 경우 LE는 전역 객체를 그대로 참조한다.

전역변수를 선언하면 자바스크립트 엔진은 이를 전역객체의 프로퍼티로 할당한다. 이것이 window.a, this.a가 1로 출력되는 이유이다. 그렇다면 a를 직접호출하는 경우는 어떻게 출력하는 것일까?
이는 변수 a에 접근하고자하면 스코프 체인에서 a를 검색하다가 가장 마지막에 도달하는 전역 스코프의LE = 전역객체에서 해당 프로퍼티 a를 발견해서 그 값을 반환하기 때문이다. 그냥 단순하게 window. 이 생략된 것이라고 생각해도 된다.

그러면 전역 변수 선언, 전역 객체의 프로퍼티 할당 이 두 방법이 똑같은가? 댓츠논노 그렇지않다.. 둘의 차이는 ‘삭제’에서 드러난다.

  • 전역 객체의 프로퍼티로 할당한 경우: 삭제 가능
  • 전역 변수로 선언한 경우: 삭제 불가능

전역 변수를 선언하면 자바스크립트 엔진이 이를 자동으로 전역 객체의 프로퍼티로 할당하면서 + 해당 프로퍼티의 configuration 속성(변경 및 삭제 가능성)을 false로 정의하는 것이다.

이처럼 var로 선언한 전역변수와 전역객체의 프로퍼티는 호이스팅 여부 및 configurable 여부에서 차이를 보인다.

메서드로서 호출할 때 그 메서드 내부에서의 this

어떤 함수를 실행하는 방법은 여러가지가 있는데, 가장 일반적인 방법 두 가지는

  • 함수로서 호출
  • 메서드로서 호출

이다.

프로그래밍 언어에서 함수와 메서드 = 미리 정의한 동작을 수행하는 코드뭉치, 이 둘을 구분하는 유일한 차이는 독립성에 있다.

함수는 그 자체로 독립적인 기능을 수행하지만,
메서드는 자신을 호출한 대상 객체에 관한 동작을 수행한다.

자바스크립트는 상황별로 this키워드에 다른 값을 부여하게 함으로써 이를 구현했다.

자바스크립트의 메서드는 ‘객체의 프로퍼티에 할당된 함수’로 이해되지만, 이건 반은 맞고 반은 틀리다.
어떤 함수를 객체의 프로퍼티에 할당한다고 해서 그 자체로서 무조건 메서드가 되는 것이 아니라 객체의 메서드로서 호출할 경우에만 메서드로 동작하고, 그렇지 않으면 함수로 동작한다.

1줄에서 func 변수에 익명함수를 할당했다.
4줄에서 func 호출시 this로 전역객체 window가 출력된다.
6줄에서 obj 변수에 객체를 할당하는데, method 프로퍼티에 만들었던 func 함수를 할당한다.
9줄에서 obj.method 호출하니, 이번 this는 obj이다.

obj의 method프로퍼티에 할당한 값과 func 변수에 할당한 값은 모두 1줄에서 선언한 함수를 참조한다. 즉, 원래의 익명함수는 그대로인데 이를 변수에 담아 호출한 경우와 obj 객체의 프로퍼티에 할당해서 호출한 경우에 this가 달라지는 것이다.

그렇다면

  • 함수로서 호출
  • 메서드로서 호출

을 어떻게 구분할까? ⇒ 함수 앞에 점이 있는지 여부만으로 간단하게 구분할 수 있다. (대괄호 표기법도 메서드로서 호출임)
요약: 어떤 함수를 호출할 때 함수명(프로퍼티명)앞에 객체가 명시되어 있는 경우 메서드, 그외 함수 호출

[ 메서드 내부에서의 this ]

this에는 호출한 주체에 대한 정보가 담긴다.
어떤 함수를 메서드로서 호출하는 경우 호출 주체는 바로 함수명(프로퍼티명) 앞의 객체이다.
점 표기법인 경우 마지막 점 앞에 명시된 객체 = this

함수로서 호출할 때 그 함수 내부에서의 this

[ 함수 내부에서의 this ]

함수로서 호출한 경우에는 this가 지정되지 않는다. 함수로서 호출하는 것은 호출 주체(객체지향 언어에서의 객체)를 명시하지 않고 개발자가 코드에 직접 관여해서 실행한 것이기 때문에 호출 주체의 정보를 알 수 없다. this에는 호출 주체에 대한 정보가 담기므로 함수로서 호출시 this가 지정되지 않는 것.

2장에서 실행 컨텍스트를 활성화할 당시에 this가 지정되지 않은 경우 this는 전역 객체를 바라본다고 했다. 따라서 함수의 this는 전역 객체를 가리킨다. (누군가는 이걸 설계오류라고 한다.. 왜일까?)

[ 메서드의 내부함수에서의 this ]

이 this가 예측하기 제일 어렵다. 앞에서 말한 설계상의 오류로 인해 실제 동작과 다르게 예측되는 것.
this라는 단어 자체가 주는 느낌 그대로 코드를 보면 예상과 다른 결과가 나온다.

하지만 앞에서 배운 것처럼 내부함수도 함수로 호출한건지, 메서드로 호출한건지만 파악하면 this의 값을 정확하게 예측할 수 있다.

아래 예제의 콘솔에서 this가 무엇을 가리킬지 예상해보자.

정답은 (1): obj1, (2): 전역객체 = Window, (3): obj2 이다.

1줄: 객체를 생성, outer라는 프로퍼티가 있으며, 여기에 익명함수가 연결된다. 이렇게 생성한 객체를 변수 obj1에 할당한다.
15줄: obj1.outer를 호출한다.
2줄: obj1.outer의 실행 컨텍스트가 생성되면서 호이스팅하고, 스코프 체인 정보를 수집하고 this를 바인딩한다. 이 함수는 호출할 때 함수명인 outer 앞에 점이 있었으므로 메서드로서 호출한것이다. 따라서 this에는 obj1이 바인딩된다.
3줄: obj1 객체 정보가 출력된다.
4줄: 호이스팅된 변수 innerFunc에는 outer 스코프 내에서만 접근할 수 있는 지역변수이다. 이 지역 변수에 익명 함수를 할당한다.
7줄: innerFunc호출
4줄: innerFunc 함수의 실행 컨텍스트가 생성되면서 호이스팅, 스코프 체인 수집, this 바인딩 등을 수행한다. 이 함수는 함수로서의 호출(점이없어서)이므로 this가 지정되지 않았고, 따라서 자동으로 스코프 체인상의 최상위 객체인 전역객체=window가 바인딩된다.
5줄: window 객체 정보가 출력된다.
9줄: 호이스팅된 변수 obj2 역시 outer 스코프 내에서만 접근할 수 있는 지역변수다. 여기에는 다시 객체를 할당하는데, innerMethod라는 프로퍼티가 있으며, 여기에는 앞서 정의된 변수 innerFunc와 연결된 익명 함수가 연결된다.
12줄: obj2.innerMethod를 호출
9줄: obj2.innerMethod 함수의 실행 컨텍스트 생성. 호출시 앞에 점이 있었으므로 메서드로서 호출이다. 따라서 this에는 마지막 점 앞의 객체인 obj2가 바인딩된다.
10줄: obj2의 객체 정보가 출력된다.

같은 함수 innerFunc임에도 불구하고 호출구문에 의해 this가 결정된다.

[ 메서드의 내부 함수에서의 this를 우회하는 방법 ]

이렇게하면 this에 대한 구분은 잘 할 수 있지만, this라는 단어가 주는 인상과는 달라졌다.
호출 주체가 없을 때는 자동으로 전역객체를 바인딩하지 않고 호출 당시 주변 환경의 this를 그대로 상속받아 사용할 수 있으면 좋겠다. 이게 자연스럽기도 하고, 스코프 체인과의 일관성을 지키는 설득력있는 방식이다.

변수를 검색하면 우선 가장 가까운 스코프의LE를 찾고 없으면 상위 스코프를 탐색하듯이, this 역시 현재 컨텍스트에 바인딩된 대상이 없으면 직전 컨텍스트의 this를 바라보도록 하는 것이다.

(생략 p.75)

[ this를 바인딩하지 않는 함수 ]

ES6에서는 함수 내부에서 this가 전역객체를 바라보는 문제를 해결하고자, this를 바인딩하지 않는 화살표함수를 만들었다.
화살표 함수는 실행 컨텍스트를 생성할 때 this 바인딩 과정 자체가 빠지게 되어, 상위 스코프의 this를 그대로 활용할 수 있다. 내부함수를 화살표 함수로 바꾸면 위의 우회법이 불필요해진다.

이 밖에도 call, apply 등의 메서드를 활용해 함수 호출시 명시적으로 this를 지정하는 방법이 있다.(추후소개)

콜백 함수 호출 시 그 함수 내부에서의 this

콜백 함수의 정의와 동작 원리 등은 다음장에서 공부한다.
여기선 this가 어떤 값을 참조하는지만 간단히 확인하고 넘어가자.

함수 A의 제어권을 다른 함수(또는 메서드) B에게 넘겨주는 경우 함수 A를 콜백 함수라고 한다.
이때 함수 A는 함수 B의 내부 로직에 따라 실행되며, this 역시 함수 B 내부 로직에서 정한 규칙에 따라 값이 결정된다. 콜백 함수도 함수이기 때문에 기본적으로 this가 전역 객체를 참조하지만, 제어권을 받은 함수에서 콜백 함수에 별도로 this가 될 대상을 지정한 경우에는 그 대상을 참조하게 된다.

아래는 대표적인 콜백 함수이다. 어떤 값이 출력되는지 예측해보자.

(?)잘 모르겠다 p.77

생성자 함수 내부에서의 this

생성자 함수는 어떤 공통된 성질을 지니는 객체들을 생성하는 데 사용하는 함수이다.
객체지향 언어에서는 생성자를 클래스, 클래스를 통해 만든 객체를 인스턴스라고 한다.(클래스는 7장에서..)

인간을 객체라고 하면, 각 사람들은 인간 클래스의 인스턴스인 것. 각 인스턴스들은 공통점도 있지만 차이도 있을 수 있다. 프로그래밍적으로 생성자는 구체적인 인스턴스를 만들기 위한 틀이다. 이 틀에는 해당 클래스의 공통 속성들이 미리 준비돼있고, 여기에 구체적인 인스턴스의 개성을 더해 개별 인스턴스를 만드는 것.

자바스크립트는 함수에 생성자로서의 역할을 함께 부여했다. new 명령어와 함께 함수를 호출하면 해당 함수가 생성자로서 동작한다. 그리고 어떤 함수가 생성자 함수로서 호출된 경우 내부에서의 this는 곧 새로 만들 구체적인 인스턴스 자신을 가리킨다.

생성자 함수를 호출(new + 함수)하면 우선 생성자의 prototype 프로퍼티를 참조하는 proto라는 프로퍼티가 있는 객체(인스턴스)를 만들고, 미리 준비된 공통 속성 및 개성을 해당 객체=this에 부여한다. 이렇게해서 구체적인 인스턴스가 만들어진다.

명시적으로 this를 바인딩하는 방법

규칙을 깨고 this에 별도의 대상을 바인딩하는 방법도 있다.

( 지금 공부할 필요 없을 것 같음 )

call 메서드

apply 메서드

call / apply 메서드의 활용

bind 메서드

화살표 함수의 예외사항

ES6에 새롭게 도입된 화살표 함수는 실행 컨텍스트 생성 시 this를 바인딩하는 과정이 제외됐다.
⇒ 이 함수 내부에는 this가 아예 없으며, 접근하고자하면 스코프체인상 가장 가까운 this에 접근한다.

별도의 인자로 this를 받는 경우(콜백 함수 내에서의 this)

3. 정리

다음 규칙은 명시적 this 바인딩이 없는 한 늘 성립한다.
원리를 바탕으로 다양한 상황에서 this가 무엇일지 예측하는 연습을 해보자.

  • 전역 공간에서의 this = 전역객체(브라우저: window, Node.js: global)
  • 메서드로서 호출 = 메서드 호출 주체 = 메서드 명 앞의 객체
  • 함수로서 호출 = 전역 객체, 메서드의 내부 함수에서도 같다.
  • 콜백 함수 내부에서의 this = 해당 콜백함수의 제어권을 넘겨받은 함수가 정의한대로., 정의하지 않으면 전역객체
  • 생성자 함수에서의 this = 생성될 인스턴스
profile
내가 꿈을 이루면 나는 또 누군가의 꿈이 된다.

0개의 댓글