자바스크립트 this 키워드 이해하기
프로그래머가 되고 싶다면, 어떤 분야를 선택하든 자바스크립트는 필연적으로 마주하게 됩니다. 자바스크립트는 초라한 시작에서 벗어나 진정한 강자로 진화했습니다. 이제 프론트엔드, 백엔드, 모바일, 게임, 데스크톱 앱, 심지어 머신러닝까지 모든 영역을 아우릅니다.
자바스크립트 마스터가 되어 분야를 확장하기 전에, 반드시 기초를 다져야 합니다. this 키워드는 학습 목록의 최우선 순위에 있어야 합니다.
this란 무엇인가?
간단히 말해, this는 포인터입니다. 자바스크립트 코드에서 함수나 메서드를 호출한 객체가 무엇인지 참조하는 방식입니다. 쉽게 말해, this는 점(.) 연산자 왼쪽에 있는 것입니다.

this에는 두 가지 주요 용도가 있습니다. 예제와 함께 살펴보겠습니다.
this로 객체 생성하기 (생성자 함수)
function Book(title, author, genre) {
this.title = title;
this.author = author;
this.genre = genre;
}
const book1 = new Book("Lord Of The Rings", "J.R.R. Tolkien", "Fantasy");
const book2 = new Book("Tools Of Titans", "Tim Ferriss", "Self-help");
console.log(book1);
console.log(book2);
위 코드는 다음과 같은 출력을 생성합니다:

여기서 우리는 제목, 저자, 장르를 매개변수로 받는 북(Book) 객체를 만드는 생성자 함수를 정의했습니다.
생성자 함수는 주어진 이름(여기서는 Book)의 객체를 생성할 수 있는 객체 "청사진" 함수입니다.
필요한 매개변수(함수를 호출할 때는 인수(arguments)라고 부릅니다. 정의할 때는 매개변수(parameters)라고 합니다)를 전달하여 myBook 객체를 생성했습니다.
마지막으로 myBook을 로그로 출력하면, 해당 속성들을 가진 객체가 반환됩니다. 이렇게 하면 새로 생성되는 각 북 객체마다 전달된 값이 할당됩니다.
this로 속성 접근하기:
const girl = {
name: "Sarah",
age: 26,
introduce() {
console.log(
"Hi! " + "I'm " + this.name + " and I'm " + this.age + " years old."
);
},
};

여기서 name과 age 속성, 그리고 introduce 함수를 가진 객체를 girl 변수에 할당했습니다.
이 함수는 객체의 속성을 this.원하는_속성 형태로 접근하여 소개 문장을 출력합니다.
자바스크립트에서 this와 스코프
기본적으로 전역 스코프에서 사용될 때, 즉 정의된 함수나 객체 내부에서 사용되지 않을 때, this는 전역 객체를 가리킵니다. 브라우저 환경에서 전역 객체는 window입니다.
자바스크립트에서 원시 데이터(문자열, 숫자, 불리언 등), 객체, 함수를 선언하면 모두 전역(window) 객체에 속하게 됩니다.
"노출된 상태"로 작성된 모든 것은 마치 앞에 window.가 붙은 것처럼 읽을 수 있습니다. 다음 코드 스니펫을 살펴보겠습니다:
function returnThis() {
return this;
}
먼저 전역 스코프에서 this 값을 반환하는 returnThis 함수를 정의했습니다. 그리고 returnThis()로 호출합니다.
전역 스코프에 있으므로 window.returnThis()로 볼 수 있으며, 이는 점(.) 연산자 왼쪽에 있는 것을 반환합니다.

객체 함수, 즉 메서드
이제 같은 함수를 내부에 가진 새 객체를 정의하고(이를 객체 메서드라고 합니다) 다시 호출하면, 해당 객체(중괄호로 표시)가 반환됩니다.
const object = {
returnThis() {
return this;
},
};
object.returnThis();

this의 함정과 세 가지 해결책
함수가 객체에 직접 연결되지 않은 경우, 함수 내부의 this는 전역 객체를 가리키게 됩니다.
const weirdBehaviorObject = {
directlyTiedFunction() {
return this;
},
directlyTiedOuterFunction() {
return function indirectNestedFunction() {
return this;
};
},
};
브라우저에서 실행해 결과를 확인해 보겠습니다:

객체에 this를 반환하는 함수 하나와, 내부 함수를 반환하는 외부 함수(그 내부 함수가 다시 this를 반환)를 정의했습니다.
indirectNestedFunction을 호출하려면 먼저 외부 함수를 호출하여 내부 함수를 반환받고(() 한 번), 다시 내부 함수를 호출(() 한 번 더)해야 합니다.
이때 window 객체가 반환됩니다. 이는 자바스크립트의 신비로운 동작 중 하나로, 예기치 않은 결과를 초래합니다.
다행히 이 문제를 해결할 방법은 세 가지 있습니다. 첫 번째는 클로저를 이용하는 것(ES6 이전 방식), 두 번째는 화살표 함수 사용, 세 번째는 bind 메서드 사용(call, apply와 밀접한 관련)입니다.
#1 클로저로 this 해결하기
클로저는 지속적인 메모리 저장소로, 함수가 실행을 마쳐도 변수가 사라지지 않게 합니다. 외부 함수의 변수를 참조하는 중첩 함수가 있을 때 자바스크립트 엔진이 생성하며, 외부 함수가 실행을 마친 후에도 해당 변수에 접근할 수 있게 합니다. 우리 this 예제에 적용하면 다음과 같습니다:
const objectWithClosure = {
closureFunction() {
const self = this;
return function enclosedFunction() {
return self;
};
},
};

closureFunction 내부에서 self 변수에 this 값을 저장했습니다. closureFunction은 포함 객체에 직접 연결되어 있으므로 this는 해당 객체를 가리킵니다. 일반적으로 enclosedFunction에서 this는 window 객체를 가리키지만, 클로저를 통해 외부 함수의 this에 접근함으로써 호출 객체를 값으로 보존할 수 있습니다.
#2 화살표 함수로 this 해결하기
두 번째 해결책은 ES6와 함께 등장한 최고의 기능 중 하나인 화살표 함수를 사용하는 것입니다.
화살표 함수는 함수를 작성하는 더 간결한 문법을 제공합니다. 더불어 자신이 포함된 함수(한 레벨 상위 스코프)에서 this를 찾습니다.
따라서 객체를 다음과 같이 정의하면:
const arrowFunctionObject = {
outerFunc() {
return () => this;
},
};

화살표 함수가 window가 아닌 실제 객체를 반환하는 것을 볼 수 있습니다.
이는 화살표 함수가 자신을 호출한 outerFunc(여기서 this는 arrowFunctionObject를 가리킴)까지 한 레벨 올라갔기 때문입니다. 즉, 화살표 함수에는 일종의 내장된 클로저가 있습니다.
중첩 화살표 함수
자바스크립트답게 또 다른 함정이 있습니다. 기술 면접 외에서는 거의 마주치지 않을 상황이지만, 알면 유용합니다.
일반 함수 안에 화살표 함수를 중첩하고, 그 안에 또 다른 함수를 중첩하면 어떻게 될까요? 다음과 같습니다:
const arrowNestedInRegularFunctionObject = {
outerFunction() {
return function regularFunction() {
return () => this;
};
},
};

객체에 outerFunction이 있고, 호출 시 내부 regularFunction을 반환합니다.
regularFunction을 호출하면 가장 안쪽의 화살표 함수를 반환합니다.
이제 이를 호출하면 window 객체가 반환됩니다.
화살표 함수는 오직 한 레벨만 상위 스코프를 보기 때문입니다. 이를 포함하는 regularFunction이 객체에 직접 연결되지 않았으므로 window가 반환됩니다.
하지만 화살표 함수 안에 화살표 함수를 중첩하고, 그걸 다시 일반 함수 안에 넣으면 예상대로 실제 포함 객체가 반환됩니다.
const nestedArrowFunctionObject = {
outerFunction() {
return () => {
return () => this;
};
},
};

이 경우 안쪽 화살표 함수는 바깥쪽 화살표 함수를 바라봅니다. 바깥쪽도 화살표 함수이므로 한 레벨 더 올라갑니다. 일반 함수인 outerFunction은 호출 객체에 직접 연결되어 있으므로, 결과적으로 객체가 this로 반환됩니다.
#3 bind 메서드로 this 해결하기
마지막 세 번째 해결책은 bind 메서드 사용입니다. bind는 this가 무엇을 가리켜야 할지 명시적으로 선언하는 데 사용됩니다. 인수로 객체를 받아 함수의 this 키워드에 바인딩합니다.
const arrowNestedInRegularFunctionObject = {
outerFunction() {
return function regularFunction() {
return () => this;
}.bind(arrowNestedInRegularFunctionObject);
},
};

이 예제에서 일반 함수(보통 this로 window를 반환)에 bind를 체이닝했습니다. this로 참조하고 싶은 객체를 인수로 전달하여 문제를 해결했습니다.
자바스크립트를 이해하면 this는 어렵지 않습니다
꽤 깊이 파고들었습니다. 그런데 왜 this 때문에 이토록 번거롭고 혼란스러울까요?
이유는 자바스크립트의 다른 모든 것과 달리 this가 동적 스코프(dynamic scope)를 가지기 때문입니다. 즉, 자바스크립트 엔진이 코드 실행 시점(runtime)에 어디서, 누가 호출했는지를 보고 this가 무엇을 나타내는지 결정합니다.
반면 자바스크립트의 다른 모든 것은 렉시컬 스코프(lexical scope)를 가지며, 변수가 정의된 위치(코드 작성 시점)에서 값이 결정됩니다.
this 덕분에 우리는 객체 "청사진"을 한 번만 만들고, 북 예제에서 본 것처럼 서로 다른 속성 값을 가진 객체를 동적으로 생성할 수 있습니다.
this.원하는_속성 형태로 객체의 속성을 노출하고 조작하는 메서드도 만들 수 있습니다.
자바스크립트의 기묘한 동작을 우회하고, 그 행동을 확신 있게 제어하며, 예상한 결과를 얻을 수 있습니다.
이제 자바스크립트에서 this뿐만 아니라 몇 가지 핵심 개념을 이해했으니, 실험하고 연습하며 자바스크립트 마스터를 향해 더 자신 있게 나아갈 수 있습니다.
당신은 this를 이해했습니다!