대부분의 객체 지향 언어에서 this는 클래스로 생성한 인스턴스 객체를 의미한다. 하지만 자바스크립트 this는 상황에 따라 바라보는 대상이 달라진다. 함수와 객체(메서드)의 구분이 느슨한 자바스크립트에서 this는 실질적으로 이 둘을 구분해준다.
전역 객체는 특수한 객체고 모든 객체는 이 객체의 프로퍼티다.
function func(){
console.log("print")
}
func() // "print"
window.func() // "print"
함수 func() window라고 하는 전역 객체의 메소드다.
(원리)
const a = 1;
console.log(a) // 1
console.log(window.a) // 1
console.log(this.a) // 1
자바스크립트의 모든 변수는 특정 객체의 프로퍼티다. 특정 객체란 바로 실행 컨텍스트의 LexicalEnvironment(이하 LE)다. 실행 컨텍스트는 변수를 수집해 LE에 저장한다. 이후 어떤 변수를 호출하면 LE를 조회하여 일치하는 프로퍼티가 있을 경우 그 값을 반환한다.
변수 a에 접근하고자 스코프 체인에서 a를 검색하다가 가장 마지막에 도달하는 전역 스코프의 LE, 즉 전역 객체에서 해당 프로퍼티 a 를 발견해서 그 값을 반환한 것이다.
this는 함수를 어떻게 호출하느냐에 따라 지칭하는 대상이 달라진다. 함수 내에서 호출 맥락(context)을 의미한다.
function func(){
if(window === this){
console.log(window === this) // true
}
}
func()
this는 함수 안에서 전역객체를 의미하는 window를 가리킨다.
함수와 메서드의 차이는 독립성에 있다. 함수는 독립적으로 기능을 수행! 하지만 메서드는 자신을 호출한 객체에 관해 동작한다.
const obj = {
func : function(){
if(obj === this){
console.log("this = obj") // this = obj
}
}
}
obj.func()
객체 안에서 함수가 메서드로 존재할 때 메서드 안 this는 그 객체를 가리킨다.(접근할 수 있다)
함수가 함수로 호출될 때 함수 내부에서 this는 지정되지 않는다. this는 호출한 주체의 정보가 있는데 함수를 함수로 호출하게 되면 개발자가 코드에 직접 관여했기 때문에 호출 주체 정보를 알 수 없다. 따라서 함수에서 this는 전역 객체를 가리킨다.
메서드 내부에서 정의하고 있는 함수에서 this 는 다르다.
const obj1 = {
outer: function() {
console.log(this) // 1번
const innerFunc = function() {
console.log(this) // 2번, 3번
}
innerFunc();
const obj2 = {
innerMethod: innerFunc
};
obj2.innerMethod()
}
}
obj1.outer()
정리하자면 객체 obj1에는 outer라는 프로퍼티 있고 그 프로퍼티 안에서 1번 console.log(this)를 호출한다. 그 안에는 innerFunc 함수와 obj2가 있는데 innerFunc에서 2번, 3번인 console.log(this)를 호출한다. obj2의 메서드로 innerMethod가 있고 innerFunc를 할당했다. 그리고 obj2.innerMethod()를 호출한다. 함수 외부에서 obj1.outer()를 했을 때 각 this의 값이 무엇인지 고민해보자! (내가)
outer라는 프로퍼티에 익명함수 연결한 객체 생성하고 변수 obj1에 할당 -> obj1.outer() 실행 -> 호이스팅 -> outer는 메서드로 호출된 것이므로 (1)의 this는 obj1에 바인딩 -> 호이스팅된 변수 innerFunc는 outer 스코프에서만 접근 가능한 지역변수 -> innerFunc() 호출 -> 이때 함수는 함수로서 호출된 것이므로 (2) this는 전역 객체를 가리킨다 -> 호이스팅된 obj2 역시 outer 스코프에서만 접근 가능, obj2에는 innerMethod가 있고 변수 innerFunc에 연결된 익명함수가 연결됨 -> obj2.innerMethod() 실행 -> 이때 함수는 메서드로 호출됨 그러므로 (3) this는 obj2다.
즉, this의 바인딩은 항수를 실행하는 당시의 주변 환경, 메서드 내부인지 함수 내부인지는 중요하지 않고 어떻게 호출되었는지가 관건이다.
const obj = {
outer: function() {
console.log(this)
const innerFunc1 = function() {
console.log(this)
}
innerFunc1()
let self = this;
const innerFunc2 = function() {
console.log(self)
}
innerFunc2()
}
}
obj.outer()
위 코드에서 innerFunc1 내부 this는 전역 객체를 가리킨다. 하지만 outer 스코프에서 self라는 변수에 this를 지정한 상태에서 innerFunc2 를 호출하면 self에 객체 obj2가 출력된다.
const obj = {
outer: function() {
console.log(this) // { obj: f}
const innerFunc = () => {
console.log(this) // { obj :f}
}
innerFunc()
}
}
obj.outer()
함수 내부에서 this가 전역 객체를 바라보는 문제를 보완하기 위해 화살표 함수에는 this가 바인딩 되지 않게 하였다. 화살표 함수는 실행 컨텍스트를 생성할 때 this 바인딩 과정이 빠져 상위 스코프에서 this를 그대로 사용할 수 있다.
setTimeout(function() {
console.log(this) // (1)
}, 300)
[1, 2, 3, 4, 5].forEach(function(x) {
console.log(this, x) // (2)
})
document.body.innerHTML += '<button id="a">클릭</button>'
document.body.querySelector("#a").addeventListener("click", function(e) {
console.log(this, e) // (3)
})
setTimeout과 forEach에서 this는 전역 객체다. 한편 (3)의 addeventListner 메서드는 콜백함수를 호출할 때 자신의 this를 상속한다.
new를 이용하여 생성자를 호출하면 빈 객체를 만들고 그 빈 객체 안에 this가 존재한다. this는 생성자를 통해 만들어진 객체를 가리킨다.
let funcThis = null;
function Func(){
funcThis = this;
}
const obj1 = Func();
if(funcThis === window){
console.log('window'); // window
}
const obj2 = new Func();
if(funcThis === obj2){
console.log("obj2"); //obj2
}
함수 Func()에서 지역 변수로 funcThis = this라고 정의했다. 그리고 객체 obj1에 Func()를 할당하고 obj 실행하면 Func() 함수 안의 fucnThis 는 let /const / var가 없기 때문에 전역변수인 null을 가리킨다. 즉, window를 가리킨다.
let obj = {}
let person = {}
function func(){
switch(this){
case obj:
console.log("obj")
break;
case person:
console.log("person")
break;
case window:
console.log("window")
break;
}
}
func() // window
func.apply(obj) // obj
func()을 호출하면 함수로서 호출했기 때문에 this는 전역객체를 가리킨다. 하지만 apply 메서드의 obj를 인자로 넣으면 this가 obj를 가리킨다.
심화
Function.prototype.call(thisArg[,arg1[, arg2], ...]]])
call 메서드는 호출 주체인 함수를 즉시 실행하는 명령이다. 첫 번째 인자를 this로 바인딩 이후 인자는 호출 함수의 매개변수다. call 매서드를 이용하면 객체를 this로 지정할 수 있다.
const func = function(a, b,c){
console.log(this, a, b, c)
}
func(1, 2, 3) // Window{...} 1 2 3
func.call({x:1},4,5,6) // {x:1} 4 5 6
call을 이용해 {x:1}을 this로 지정할 수 있다.
Function.prototype.apply(thisArg[, argsArray])
apply는 call과 동일한 기능이다. 하지만 두 번째 인자로 배열을 받아 그 배열의 요소들을 호출할 함수의 매개변수로 지정한다.
const func = function(a, b, c) {
console.log(this, a, b, c) // {x:1} 4 5 6
}
func.apply({ x: 1 }, [4, 5, 6])
const obj = {
a: 1,
method: function(x, y) {
console.log(this.a, x, y)
}
}
obj.method.apply({ a: 4 }, [5, 6]) //4 5 6
여기부터 더 공부하기
출처 : 생활코딩, 코어 자바스크립트