
대부분의 객체지향 언어에서 this 는 클래스로 생성한 인스턴스 객체를 의미합니다. 하지만 Javascript에서 this 는 어디서든 사용할 수 있죠. 상황에 따라 this 가 바라보는 대상이 달라지기 때문에 정확한 작동 방식을 이해하지 못하면 굉장히 혼란스럽습니다.
오늘은 이 this 가 어떤 상황에서 무엇을 바라보는지 알아봅시다.
이 글에서는 this 아래 두 개념을 이해하고 있다는 것을 전제로 설명을 진행합니다.
자바스크립트에서 this 는 실행 컨텍스트가 생성될 때 함께 결정됩니다.
실행 컨텍스트가 생성되면 아래와 같은 정보들이 담기게 됩니다. 이 중 ThisBinding에 this에 대한 정보가 담기게 됩니다.
// 현재 컨텍스트 내의 식별자 정보 & 외부 환경 정보
// 지금은 몰라도 됩니다 :D
- VariableEnvironment
- LexicalEnvironment
// this 식별자가 바라봐야 할 대상 객체
- ThisBinding
그렇다면 실행 컨텍스트는 언제 생성될까요? 다음 3가지 상황에서 생성됩니다.
eval() 함수가 호출될 때단, eval() 함수는 왠만해서는 사용하지 않으니, 이 글에서는 1, 2번 상황만을 가정하겠습니다.
위의 2가지 내용을 종합하면 this는 전역 객체가 생성될 때 혹은 함수가 실행될 때 결정된다는 것을 알 수 있습니다.
⇒ this는 전역 객체가 생성될 때, 함수가 호출될 때 결정된다.
하지만 여기서 끝이 아닙니다. this가 변할 수도 있기 때문이죠. JS 엔진이 알아서 변경하기도 하고, 개발자가 명시적으로 변경할 수도 있습니다.
JS 엔진이 변경하는 경우 - 엄격 모드가 아닐 때
엄격 모드가 활성화 되어 있지 않다면 this 값이 undefined 혹은 null 일 때, JS 엔진이 autoboxing을 수행하여 this 에 전역 객체를 바인딩합니다.
// 비엄격 모드
function nonStrictFunc() {
return this;
}
// 함수 단위의 엄격 모드 설정
function strictFunc() {
"use strict";
return this;
}
console.log(nonStrictFunc()); // Window or Global
console.log(strictFunc()); // undefined
개발자가 명시적으로 변경하는 경우 - bind(), call(), apply()
개발자가 명시적으로 this를 바인딩하는 방법도 있습니다.
function foo(bar) {
return [this, bar];
}
const myThis = "my this";
const myParam = "my param";
// func.call(thisArg[, arg1[, arg2[, ...]]])
console.log(foo.call(myThis, myParam));
// func.apply(thisArg, [argsArray])
console.log(foo.apply(myThis, [myParam]));
// func.bind(thisArg[, arg1[, arg2[, ...]]])
const boundFunc = foo.bind(myThis);
console.log(boundFunc(myParam));
// result
// [ [String: 'my this'], 'my param' ]
// [ [String: 'my this'], 'my param' ]
// [ [String: 'my this'], 'my param' ]
위 내용을 종합하면 this 는 다음과 같은 순간에 결정된다는 것을 알 수 있습니다.
this 값을 변경할 때이제 this가 언제 결정되는지 알았으니 this 가 어떻게 결정되는지 각 상황별로 알아보겠습니다.
this 는 호출 주체에 대한 정보가 담깁니다.
이 대원칙을 바탕으로 this 를 호출하는 상황은 5개로 정리할 수 있습니다. 그리고 이 5가지 규칙은 늘 성립합니다. (this 를 별도로 바인딩 해주지 않는다면)
this 는 window(브라우저 환경) 또는 module.exports(Node.js 환경)를 참조합니다.this 는 undefined 로 바인딩 됩니다.this 는 메서드 호출 주체(메서드명 앞의 객체)를 참조합니다.undefined를 참조합니다.위 5가지 규칙을 하나씩 살펴봅시다.
브라우저 환경에서 전역 공간의 this는 전역 객체인 window 를 참조합니다.
// 브라우저 환경
console.log(this); // Window
console.log(window); // Window
console.log(this === window); // true
Node.js 환경에서 전역 공간의 this는 module.exports 를 참조합니다. this, module.exports, exports 는 모두 동일한 객체입니다.
// Node.js 환경
console.log(this, module.exports, exports); // {} {} {}
console.log(this === module.exports); // true
console.log(this === exports); // true
console.log(module.exports === exports); // true
함수를 호출할 때, 함수 내부에 작성한 this는 undefined 가 바인딩됩니다. 때문에 엄격 모드로 실행하면 this 값이 undefined 그대로 나오지만 엄격 모드로 실행하지 않으면 autoboxing이 수행되어 전역 객체(Window, Global)이 바인딩 됩니다.
// 엄격 모드가 적용되지 않은 함수
function foo() {
console.log(this);
}
// 엄격 모드가 적용된 함수
function bar() {
"use strict";
console.log(this);
}
foo();
bar();
// 브라우저 환경에서의 결과
// Window
// undefined
// Node.js 환경에서의 결과
// Global
// undefined
함수를 메서드로서 호출할 때, 함수 내부에 작성한 this는 메서드 호출 주체(메서드명 앞의 객체)를 참조합니다.
// 함수로서 호출
const func = function(x) {
console.log(this, x);
};
func(1); // Window {...} 1
// 메서드로서 호출
const obj = {
method: func
}
obj.method(2); // { method: f } 1
이 경우 콜백 함수의 제어권을 넘겨받은 함수에 정의된대로 this가 바인딩 됩니다.
// addEventListener 함수는 콜백 함수의 this를 event.currentTarget으로 바인딩 하도록 정의되어 있습니다.
const $form = document.querySelector('#form');
$form.addEventListener('click', function (event) {
console.log(event.currentTarget);
console.log(this);
console.log(this === currentTarget);
})
// 브라우저 환경 실행 결과
// <form>...</form>
// <form>...</form>
// true
해당 함수에 this를 무엇으로 바인딩할지 정의되어있지 않다면, undefined가 바인딩됩니다. 때문에 엄격 모드로 실행하지 않는다면 JS 엔진이 this를 전역 객체로 다시 바인딩합니다.
// forEach 함수는 콜백 함수의 this 바인딩을 별도로 정의해두고 있지 않습니다.
const foo = [1, 2, 3];
// 엄격 모드
foo.forEach(function (el) {
"use strict";
console.log(el, this);
});
// 브라우저 환경 실행 결과
// 1 undefined
// 2 undefined
// 3 undefined
// 비엄격 모드
foo.forEach(function (el) {
console.log(el, this);
});
// 브라우저 환경 실행 결과
// 1 Window
// 2 Window
// 3 Window
new 명령어를 사용해 생성자 함수를 호출하면 this는 새롭게 생성될 인스턴스 자신이 할당됩니다.
const Cat = function (name, age) {
this.name = name;
this.age = age;
};
const choco = new Cat("쪼꼬", 5);
const nabi = new Cat("나비", 3);
console.log(choco); // Cat {name: '쪼꼬', age: 5}
console.log(nabi); // Cat {name: '나비', age: 3}
this에 undefined, null 이 할당되어 있다면 JS 엔진에 의해 전역 객체로 바인딩 됩니다.bind(), call(), apply() 메서드를 사용해 this를 명시적으로 바인딩 할 수 있습니다.