티스토리 뷰

TIL/Javascript

this

버터호두 2021. 7. 13. 21:50

자바스크립트는 너무 길다. 자스로 하겠다...


✨ 자바스크립트의 this

대부분의 객체지향 언어this = 클래스로 생성한 인스턴스 객체

🔎 객체지향 언어?
: (...)

클래스에서만 사용할 수 있기 때문에, 혼란의 여지가 거의 없다. 그러나 자스의 this는 어디서든 사용 가능하다. 그래서 다음과 같은 경우가 자주 일어나는데, 정확한 작동 방식을 이해하지 않으면 디버깅이 힘들다.
- 원인 파악 어렵다
- 예상과 다르게 엉뚱한 대상을 바라본다

함수객체(메서드)의 구분이 느슨한 자스.
this는 실질적으로 이 둘을 구분하는 거의 유일한 기능이다.

상황별로 this는 어떻게 달라질까? 왜 그럴까? 어떻게 추적할까?


상황마다 다른 this

기본적으로 실행 컨텍스트가 생성될 때 함께 결정된다.
함수를 호출할 때 실행 컨텍스트를 생성 = 함수를 호출할때 this가 결정

1) 전역 공간에서의 this는 전역 객체

전역 컨텍스트를 생성하는 주체 => 바로 전역 객체
전역 객체는 자스 런타임 환경에 따라 다른 이름과 정보를 가지고 있다.

[브라우저 환경]
전역객체 = window

[Node.js]
전역객체 = global


전역변수를 선언하면 자스 엔진은 이를 전역객체의 프로퍼티로도 할당한다. 변수이면서 객체의 프로퍼티 이기도 한셈.

그런데 전역변수에 값을 할당하고 그 값을 콘솔에 찍어보면 값이 나온다. 이는 자스의 모든 변수는 특정 객체의 프로퍼티로 동작하기 때문이다. 특정 객체는 바로 실행 컨텍스트의 lexicalEnvironment이다! 실행 컨텍스트는 변수를 수집해서 LE의 프로퍼티로 저장한다. 이후 어떤 변수를 호출하면 LE를 조회해서 일치하는 프로퍼티가 있을 경우 그 값을 반환한다. 전역 컨텍스트의 경우 LE는 전역 객체를 그대로 참조한다.

전역변수를 선언하면 자동으로 이를 전역객체의 프로퍼티로도 할당한다.
=> 전역변수를 선언하면 자스 엔진은 이를 전역객체의 프로퍼티로 할당한다.

그렇다면 변수 자체를 콘솔에 찍을 땐 왜 나올까? 변수에 접근하고자 하면 스코프 체인에서 해당 변수를 검색하다가 가장 마지막에 도달하는 전역 스코프의LE, 즉 전역객체에서 해당 프로퍼티(변수명과 같음)를 발견해서 그 값을 반환하기 때문! 원리는 이렇지만, 단순하게 window.이 생략된 것이라고 여겨도 무방하다.

때문에, window객체의 프로퍼티를 할당하더라도 var를 이용해 변수를 선언하는 것과 같다. 대개는!
그렇다면 예외는?
삭제 명령에 대해 그렇다. delete!
var 변수 선언시 delete window.변수 또는 delete 변수의 경우 false이고 변수 및 값이 유지된다.
그러나 window.변수 식으로 전역객체의 프로퍼티를 할당할 경우 위와 같은 명령어에 true를 반환, 해당 프로퍼티는 삭제된다.

자스의 빈틈이라기 보다, 의도치않게 삭제하는 것을 방지하는 차원에서 나름의 방어 전략이라고 해석된다.
전역변수를 선언하면 자스 엔진이 자동으로 전역객체의 프로퍼티로 할당하면서 추가적으로 해당 프로퍼티의 configurable 속성 (변경 및 삭제 가능성)을 false로 정의하는 것!

2) 메서드로서 호출할 때 또는 메서드 내부에서의 this
함수 vs. 메서드
함수를 실행하는 방법
일반적으론 1) 함수로서 호출하는 경우 2)메서드로서 호출하는 경우
함수 & 메서드 : 미리 정의한 동작을 수행하는 코드 뭉치
이 둘을 구분하는 유일한 차이는 독립성에 있음
함수는 그 자체로 독립적인 기능을 수행하나, 메서드는 자신을 호출한 대상 객체에 관한 동작을 수행한다.
자스는 상황별로 this 키워드에 다른 값을 부여하게 함으로써 이를 구현하였다.
메서드 = 객체의 프로퍼티에 할당된 함수? (세모)
어떤 함수를 객체의 프로퍼티에 할당한다고 해서 그 자체로서 무조건 메서드가 되는 것이 아니다.
객체의 메서드로서 호출할 경우에만 메서드로 동작하고, 그렇지 않으면 함수로 동작한다.

메서드로의 호출? 함수 앞에 점이 있는지 여부만으로 구분 가능!
정말이다... 또는 대괄호 표기법도 ok

어떤 함수를 호출할 때 그 함수 이름앞에 객체가 명시돼 있는 경우에는 메서드로 호출한 것이고 그렇지 않는 모든 경우에는 함수로 호출한 것

<메서드 내부에서의 this>
this는 호출한 주체에 대한 정보가 담긴다. 어떤 함수를 메서드로서 호출하는 경우, 호출 주체는 바로 함수명(프로퍼티명)앞의 객체다! 점 표기법의 경우 마지막 점 앞에 명시된 객체가 곧 this가 되는것

<함수로서 호출할 때 그 함수내부에서의 this>
<함수 내부에서의 this>
어떤 함수를 함수로서 호출할 경우엔 this가 지정되지 않는다. this는 호출한 주체에 대한 정보가 담긴다!!!
함수로서 호출하는 것은 호출주체(객체지향언어에서의 객체)를 명시하지 않고 개발자가 코드에 직접 관여해서 실행한 것이기 때문에. 호출 주체의 정보를 알 수 없는 것이다. 실행 컨텍스트를 활성화할 당시에 this가 지정되지 않은 경우 this는 전역객체를 바라본다. 다라서, 함수에서의 this = 전역 객체 이다.
더글라스 크락포드는 이를 명백한 설계상의 오류라고 지적하는데, 왤까?

<메서드 내부함수에서의 this>
메서드 내부에서 정의하고 실행한 함수에서의 this는 혼란을 준다. 설계상의 오류로 인해 실제 동작과 다르게 예측한다. 우리는 이미 어떤 함수를 메서드로서 호출할 때와 함수로서 호출할 때 this가 무엇을 가리키는지를 알고 있다. 내부함수 역시 이를 함수로서 호출했는지 메서드로서 호출했는지만 파악하면 this의 값을 정확히 맞출 수 있다.

(예시)
같은 함수임에도 호출 방식이 다르면, 함수로서 호출하여 바인딩되는 this와 메서드로서 호출하여 바인딩 되는 this의 대상이 서로 다르다. 그러니까, this바인딩에 관해서는 함수를 실행하는 당시의 주변 환경(메서드 내부 or 함수 내부)는 중요하지 않고, 오직 해당 함수를 호출하는 구문 앞에 점 또는 대괄호 표기 유무가 관건인 것.

<메서드의 내부함수에서의 this를 우회하는 방법>
호출 주체가 없을때는 자동으로 전역객체를 바인딩하지 않고 호출 당시 주변 환경의 this를 그대로 상속받아 사용할 수 있다 면 좋겠다. 그게 훨씬 자연스럽고 자스 설꼐상 이렇게 동작하는게 스코프 체인과의 일관성을 지키는 설득력 있는 방식이다. 변수를 검색하면 우선 가장 가까운 스코프의 LE를 찾고, 없으면 상위 스코프를 탐색하듯이 this 역시 현재 컨텍스트에 바인딩된 대상이 없으면 직전 컨텍스트의 this를 바라보도록 말이다.
하지만 사용자 입장에서는 어색하거나 설득력이 없더라도 그자체를 언어의 특성으로 적응할 수 밖에. 아쉽게도 ES5까지는 자체적으로 내부함수에 this를 상속할 방법이 없지만, 우회할 방법이 없진 않다. 대표적이 방법이 변수를 활용하는 것.
var self= this;
그저 상위 스코프의 this를 저장해서 내부함수에서 활용하려는 수단

this를 바인딩하지 않는 함수?
ES6에서는 함수 내부에서 this가 전역객체를 바라보는 문제를 보완하고자 arrow function을 새로 도입하였다!
화살표 함수는 실행 컨텍스트를 생성할 때 this 바인딩 과정 자체가 빠지게 되어, 상위 스코프 this를 그대로 활용할 수 있다. 이 밖에도 call, apply 등의 메서드를 활용해 함수를 호출할 때, 명시적으로 this를 지정하는 방법이 있다.

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

setTimeout함수, forEach 메서드의 경우 그 내부에서 콜백함수를 호출할 때 대상이될 this를 지정하지 않는다.
addEventListener 메서드는 콜백함수를 호출할 때 자신의 this를 상속하도록 정의돼 있다.

때문에 콜백 함수에서는 this는 무조건 이거, 라고 정의할 수 없다.
콜백 함수의 제어권으 ㄹ가지는 함수(메서드)가 콜백 함수에서 this를 무엇으로 할지 결정하며, 특별히 정의하지 않은 경우에는 기본적으로 함수와 마찬가지로 전역객체를 바라본다.

<생성자 함수 내부에서의 this>
*생성자 함수 : 어떤 공통된 성질을 지니는 객체들을 생성하는데 사용하는 함수
객체지향 언어에서는 생성자를 클래스, 클래스를 통해 만든 객체를 인스턴스라고 한다.
클래스 - 공통 속성들을 모아 인간 집합을 정의한 것
인스턴스 - 각 사람을 클래스에 속하는 인스턴스
각 인스턴스는 클래스의 공통 속성을 갖고 있으며, 저마다의 개성도 존재할 수 있다. 프로그래밍적으로 생성자는 구체적인 인스턴스를 만들기 위한 일종의 틀! 이 틀에는 해당 클래스의 공통 속성들이 미리 준비돼 있고, 여기에 인스턴스의 구체적 개성을 더해 개별 인스턴스를 만들 수 있다.

자스는 함수에 생성자로서의 역할을 함께 부여하였다. new 명령어와 함께 함수를 호출하면 해당 함수가 생성자로서 동작하게 된다. 어떤 함수가 생성자 함수로서 호출된 경우 내부에서의 this는 곧 새로 만들 구체적인 인스턴스 자신이 된다.
생성자 함수를 호출(new명령어와 함께 함수를 호출)하면 우선 생성자의 prototype 프로퍼티를 참조하는 __proto__라는 프로퍼티가 있는 객체(인스턴스)를 만들고, 미리 준비된 공통 속성 및 개성을 해당 객체(this)에 부여한다. 이렇게 해서 구체적인 인스턴스가 만들어진다.


명시적으로 this를 바인딩하는 방법?
상황별 바인딩을 살펴봤는데, 이러한 규칙을 깨고 this에 별도의 대상을 바인딩하는 방법도 있다.

- call 메서드
이는 메서드의 호출 주체인 함수를 즉시 실행하도록 하는 명령
call메서드의 첫번째 인자를 this로 바인딩하고, 이후의 인자들을 호출할 함수의 매개변수로 한다. 함수를 그냥 실행하면 this는 전역객체를 참조하지만 call 메서드를 이용하면 임의의 객체를 this로 지정할 수 있다.

메서드에 대해서도 마찬가지이다. 그냥 호출시 this는 객체를 참조하지만 call메서드를 적용하면 임의의 객체를 this로 지정할 수 있다.

- apply 메서드
기능적으로 call과 완전히 동일하다. call은 첫번째 인자를 제외한 나머지 모든 인자들을 호출할 함수의 매개변수로 지정하는 반면, apply는 두번째 인자를 배열로 받아 그 배열의 요소들을 호출할 함수의 매개변수로 지정한다. 그러니까 apply는 첫번째 인자를 제외한 나머지 인자가 많을때 유용한 메서드라는 것.

- 활용?
call과 apply를 사용하면 자스를 더욱 다채롭게!

<유사배열객체에 배열 메서드 적용>
객체에는 배열 메서드를 직접 적용할 수 없다. 그러나! key가 0 또는 양의 정수인 프로퍼티가 존재하고, length 프로퍼티의 값이 0또는 양의 정수인 객체, 즉 배열의 구조와 유사한 객체(유사배열객체, array-like object)는 call / apply를 활용하여 배열 메서드를 차용할 수 잇다.
slice : 객체를 배열 요소를 추출하는 메서드. 매개변수를 아무것도 넘기지 않을 경우엔 그냥 원본 배열의 얕은 복사본을 반환한다.

이부분은 좀더 보완 필요.

명시적 this 바인딩이 없는 한 늘 성립하는 규칙이다.
1. 전역공간에서 this는 전역객체를 참조한다.
2. 어떤 함수를 메서드로서 호출한 경우, this는 메서드 호출 주체(메서드명 앞의 객체)를 참조한다.
3. 어떤 함수를 함수로서 호출한 경우, this는 전역객체를 참조한다. 메서드의 내부함수에서도 같다.
4. 콜백함수 내부에서의 this는 해당 콜백 함수의 제어권을 넘겨받은 함수가 정의한 바에 따르며, 정의하지 않는 경우엔 전역객체를 참조한다.
5. 생성자 함수에서의 this는 생성될 인스턴스를 참조한다. (아리까리 한다.)

명시적 this 바인딩, 위 규칙에 부합하지 않는 경우엔 다음 내용을 바탕으로 this를 예측할 수 있다.
1. call, apply메서드는 this를 명시적으로 지정하면서, 함수 또는 메서드를 호출한다.
2. bind 메서드는 this 및 함수에 넘길 인수를 일부 지정해서 새로운 함수를 만든다.
3. 요소를 순회하면서 콜백 함수를 반복 호출하는 내용의 일부 메서드는 별도의 인자를 this로 받기도 한다.

'TIL > Javascript' 카테고리의 다른 글

클래스 vs. 프로토타입  (0) 2021.07.22
클로저  (0) 2021.07.17
[JS007] 변수명  (2) 2021.06.25
[JS006] Falsy, falsey 가 아닌 것은 Truthy  (0) 2021.06.25
[JS005] for문 계산  (0) 2021.06.25
댓글
최근에 올라온 글
최근에 달린 댓글
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
글 보관함