콜백 함수, 함수 내 화살표 함수의 this를 공부하다가 헷갈려서 검색하여 정리해 보았습니다.
보고나면 이해가 되는데 시간이 지나 헷갈리면 자주 보도록 해야겠습니다.
명시적 바인딩까지 포함하여 정리 다시 한 최신 🔗링크
자바스크립트 this에 대해서 간단하게만 보는 것이 목적이면 이 글을 읽으면 됩니다.
1. this와 실행 문맥

⚪ 자바스크립트 엔진을 프로그램이 실행을 하면 모든 실행 가능한 코드를 평가해서 ✔ '실행 문맥' 이라는 것을 만듦.
⚪ 이 때 실행 가능한 코드란 전역코드, 함수 코드, eval 코드가 있음. 각각의 실행 가능한 코드별로 실행 문맥이 하나씩
만들어짐.
⚪ 실행 문맥은 렉시컬 환경 컴포넌트, 디스 바인딩 컴포넌트 등 실제 실행에 필요한 컴포넌트들로 이루어져 있는데, 여기에 있는 ✔디스 바인딩 컴포넌트에 우리가 알고 있는 'this'에 대한 정보가 담기게 되는 것.

⚪ 먼저, 프로그램이 실행이 되면 자바스크립트는 전역 코드를 평가해서 전역 실행 문맥을 만듦. ✔함수가 실행되면 전역코드의 실행을 잠깐 멈추고 또 다시 실행 문맥을 만듦(= 함수 실행 문맥). 이 때 디스 바인딩 컴포넌트의 값이 결정됨.
2. this 바인딩의 종류
⚪ 일반적으로 ✔this는 호출 당시의 함수를 포함하고 있는 객체에 바인딩이 됨.
⚪ 함수가 객체에 의해서 호출되지 않는 상황에서는 this가 어떻게 바인딩이 될까? 혹은 생성자 함수에서는 this를 어떻게 이해하면 좋을까? 각 실행 문맥에서 this를 바인딩하는 데에는 일정한 규칙들이 존재함.

⚪✔ this는 4가지 규칙에 의해서 바인딩 됨.
⚪ 함수가 호출되는 상황에 따른 규칙들임.
⚪ ✔규칙들 사이에는 우선순위가 존재함.
1. 기본 바인딩
[브라우저 환경]

⚪ 함수가 단독 실행하게 되면 this는 전역 객체에 바인딩이 됨.
⚪ 브라우저 환경에서는 전역 객체인 window 환경에 바인딩이 됨.(이 때 ✔"use strict"; 엄격모드를 쓰면 전역 객체가 기본 바인딩 대상에서 아예 제외 되어버려 this가 바인딩될 객체가 없기 때문에 undefined를 출력함.)
[노드 환경]

⚪ node의 전역 객체인 global에 바인딩 됨.
⚪ 함수 코드 안에서가 아니라 전역 코드상에서 ths를 콘솔에 출력해보면 빈 객체가 나옴. 이 때 빈 객체는 사실 모듈객체에 있는 exports 객체와 완벽하게 동일한 객체임. 이를 통해서 node.js 환경에서 this가 2가지 방향을 바인딩 된다는 것을 알 수 있음.
console.log(this === module.exports); // true
function asdf() {
console.log(this === global); // true
console.log(this === globalThis); // true
console.log(this); // Object [global]
}
2. 암시적 바인딩

⚪ 일반적으로 배우게 되는 바인딩. " . " (점) 앞의 메서드를 호출한 객체에 바인딩 됨.
⚪ 암시적 바인딩이 되는 경우에서 함수를 사용할 때는 조심해야 되는 경우가 있음.
조심해야 되는 경우 예시 1

⚪ getName을 쓴 객체인 obj에 바인딩이 되어 beuccol이 출력될 것 같지만..... 아니다!

⚪ 자바스크립트에서 객체를 할당한 변수는 해당 객체에 대한 참조값을 저장함.
⚪ obj.getName 프로퍼티에는 getName 함수에 대한 참조가 들어있는 것. 그리고 참조값을 함수에 인수로 넘기게 되면 함수는 이 참조값을 복제해서 사용하게 됨. 따라서 같은 객체를 참조하는 또 다른 변수를 만들어서 함수 안에서 사용하게 되는 것. 마찬가지로 showReturnValue에서 obj.getName을 전달하게 되면 동일한 함수를 참조하는 또 다른 레퍼런스가 콜백 변수에 저장되어서 사용되는 것
근데 왜? 바인딩이 안 된 결과가 나오는걸까?
그 이유는 바로 . (점)연산 때문이다.

⚪ 점 연산이나 대괄호 연산을 통해서 객체의 프로퍼티에 접근하면 참조 타입이라고 하는 특별하 타입을 반환해줌.
참조 타입은 자바스크립트 명세서에서만 사용되는 타입인데,

프로퍼티뿐만 아니라 프로퍼티를 가지고 있는 객체와 strict 모드인지 아닌지에 대한 여부를 같이 가지고 있는 일종의 하나의 타입임.
⚪ 예를 들어 엄격모드에서 obj.getName에 접근을 하면 다음과 같은 참조 타입을 obj.getName : (obj.getName.true) 우리가 얻게 됨.
⚪ 이러한 참조 타입에 바로 괄호를 붙여서 호출하게 되면 그 함수 안에 있는 this가 참조 타입에 있는 객체를 찾아서 바인딩이 됨. 이러한 방식의 바인딩을 우리가 암시적 바인딩이라고 부르는 것임.

⚪ 근데 ✔점 연산이나 대괄호 연산을 제외한 다른 연산들은 참조 타입이 아닌 해당 프로퍼티의 값만 전달하게 됨.
따라서 점 연산을 통해 우리가 참조 타입을 어떻게 얻어냈다고 하더라도 다른 변수에 할당을 하는 순간 프로퍼티의 값 혹은 그 프로퍼티가 참조하고 있는 참조값만 남게 됨.
⚪ ✔ 따라서 점 연산을 통해 얻은 값을 바로 함수로 호출하지 않고서는 암시적 바인딩을 기대할 순 없음.
⚪ 마찬가지로 ✔인수로 전달된 콜백의 참조도 객체에 대한 어떠한 정보도 포함하지 않기 때문에 함수로 단독 호출한 것과 같이 동작하게 됨.
3. 명시적 바인딩 => call, apply, bind는 생략....
짧게 적어두자면, 명시적 바인딩을 사용하면, 함수 내부에서 참조하는 this 객체를 고정할 수 있음.
하지만, ES6에 등장한 화살표 함수는 Lexical this를 따라가기 때문에 명시적 바인딩이 먹히지 않는다.(🔗this와 명시적 바인딩)
4. new 바인딩

⚪ 자바스크립트 함수를 또 다른 방식으로 호출할 수도 있음.
자바스크립트 함수를 new연산자와 함께 호출하게 되면 생성자함수로서의 함수가 실행할 수 있게 됨.
이 과정에서 this는 새로 생성된 객체에 바인딩 됨.
이 과정을 아주 간단하게 표현하자면 이런 모양이 됨.

⚪ 이 과정을 통해 프로퍼티가 정해진 객체를 반환하는 생성자의 역할을 수행함.
⚪ 이렇게 new 연산자로 함수를 호출할 때 ths가 바인딩 되는 규칙을 new 바인딩이라고 함.
3. 바인딩의 우선순위

4. This is Arrow Function (화살표 함수)
🎈 ES6부터 화살표 함수가 추가됨.

위에서 (암시적 바인딩에서) 배운 바로는 위 코드의 this는 전역 객체를 가리켜야 한다. 근데 아니다!!
⚪ ✔화살표 함수의 가장 큰 특징은 상위 실행 문맥을 유지한다는 점!!!
⚪ Lexical Scope와 관계없이 호출 당시에 의존하는 기존의 바인딩 규칙은 화살표 함수 안에서의 this에게 전혀 의미가 없음.
⚪ 화살표 함수 안에서의 this는 ✔상위 실행 문맥(상위 스코프) 그 스코프에 해당하는 실행 문맥상의 this바인딩 컴포넌트를 참조함.
⚪ ✔쉽게 말해서 상위 스코프의 this를 가리킨다.
⚪ 따라서 위 코드 안에서는 화살표 함수 안에 있는 this가 showNameInSec 함수 실행 문맥에 있는 this와 같은 것임.

⚪ 이러한 this의 동작하는 모습을 보고 있으면 마치 외부 렉시컬 환경, 그러니까 상위 스코프에서의 변수를 참조하는 모습과 비슷한 면이 있음. 이런 특징을 갖는 this를 어휘적 this. 혹은 똑같은 말인데 렉시컬 this 라고도 부름.
아래 추가 예시 코드 참고
/**
* two depth test
*/
const x = 123;
const twoDepth = {
x: 1,
xx: 2,
xxx: 3,
getX() {
console.log(this.x);
},
nestedObj: {
x: 4,
getXX() {
console.log(this.xx);
},
getXXX: () => {
console.log(this.xxx);
},
superNestedObj: {
x: 5,
getX() {
console.log(this.x);
},
getArrowX: () => {
console.log(this.x);
},
getXInSec() {
queueMicrotask(() => console.log(this.x));
},
},
},
};
twoDepth.getX(); // 1
twoDepth.nestedObj.getXX(); // undefined
twoDepth.nestedObj.getXXX(); // undefined
twoDepth.nestedObj.superNestedObj.getX(); // 5
twoDepth.nestedObj.superNestedObj.getArrowX(); // undefined
twoDepth.nestedObj.superNestedObj.getXInSec(); // 5
5. 출처
우아한테크
https://www.youtube.com/watch?v=7RiMu2DQrb4&t=222s