Study/JavaScript

코어 자바스크립트 - THIS

maino77 2022. 2. 2. 20:44

앞서 배웠던 실행 컨텍스트(Excution Context)에서 이 안에 3가지의 정보가 담긴다고 이야기했습니다.

VariableEnvironment, LexicalEnviroment, ThisBinding

이 세가지 중에서 앞의 2개는 설명했고 이번에는 ThisBinding에 대해 이야기해보겠습니다.

 

ThisBinding은 실행 컨텍스트가 생성될 때 실행됩니다.

그럼 활성화되는 때는 언제일까요? 바로 해당 함수가 호출될 때 입니다.

그러면 this는 함수가 호출될 때 결정된다고 말할 수 있겠습니다. 즉 함수를 호출한다고 해서 this를 예측할 수 있는 것이 아니라 함수를 어떤 식으로 호출했느냐에 따라 this는 얼마든지 달라질 수 있습니다.

그렇다면 어떻게 호출했느냐를 살펴봐야겠죠.

그래서 호출을 다음과 같이 나눠보았습니다. 

전역공간에서 this window(브라우저) / global (node.js)
함수 호출시 this ES5 : window(브라우저) / global (node.js)
ES6 : arrow function 도입해서 위 컨텍스트의 this를 가져다 씀
메서드 호출시 this 메서드 호출 주체 (메서드명 앞)
callback 호출시 this 기본적으로는 함수 내부에서와 동일
생성자함수 호출시 this 인스턴스 (새로 만든 객체 그 자체가 this)

 


 

전역공간에서 this는 전역 객체를 가리킵니다.

 


 

함수로 호출했을 때 전역 객체를 가리킵니다

// a()의 this는 전역 객체를 가리킨다.
function a() {
 console.log(this);
}
a();

//c()의 this는 전역객체를 가리킨다
function b() {
	function c() {
    	console.log(this);
    }
    c();
}
b();

[Plus. ES6에서는 arrow function이 있어 바로 위 컨텍스트에 있는 this를 가져다 쓴다. 하지만 ES5 환경에서 함수로 호출했을 때 this는 무조건 전역 객체를 가리킨다]

매서드(객체 프로퍼티에 할당된 함수)내의 함수가 this를 호출한 경우

var d = {
	e: function () {
    	function f() {
        	console.log(this);
        }
        f();
     }
 }
 d.e();

함수로서 호출했기 때문에 this이다. (호출형태 확인)

 


 

매서드로써 호출했을 때 this는 호출한 대상의 객체가 바인딩된다. 

var a = {
	b: function () {
    	console.log(this);
    }
}
a.b();

 

a.b(); 로 호출하고 있는데 이때 this는 a이다. 이처럼 점(.) 앞에 있는 것이 this가 됩니다.

 

var a = {
	b: {
    	c: function() [
        	console.log(this);
            }
       }
}
a.b.c();

 

이 경우에는 this가 a.b가 된다.

그런데 점은 대괄호 표기법으로 나타낼 수 있다.

obj.func();
obj['func']();

person.info.getName();
person.info['getName']();
person['info'].getName();
person['info']['getName']();

대괄호 표기법으로 나타내더라도 obj, person.info가 this이다.

앞서 메소드 안에 내부함수가 있을 때 this가 전역객체를 참조하는 것을 확인할 수 있었습니다. 그런데 이것을 그렇지 않게 하는 방법이 있습니다. 

var a = 10;
var obj = {
	a: 20,
    b: function() {
       console.log(this.a); // 20
       
       function c() {
       console.log(this.a); // 10
       }
       c();
     }
   }
obj.b();

obj.b()를 했을 때 20이 나온다. this가 obj이기 때문이다. 반면에 c의 this.a는 this가 전역객체임으로 10이 나온다.

그렇다면 함수 c의 this.a가 20이 나오려면 어떻게 해야할까요? 바로 스코프 체인을 이용합니다.

var a = 10;
var obj = {
	a: 20,
    b: function() {
       var self = this;
       console.log(this.a); // 20
       
       function c() {
       console.log(self.a); // 20
       }
       c();
     }
   }
obj.b();

이렇게 하면 c함수는 LexicalEnvironment에서 self를 찾고 없으니까 outerEnvironmentReference를 타고 외부 b 함수의 LexicalEnvironment에서 self를 찾아 가져옵니다.

이를 통해 메서드에서와 동일한 this를 그대로 활용할 수 있게 됩니다.

이외에도 call, apply로도 가능합니다. 그러나 ES6가 등장하면서 this를 바인딩을 하지 않는 arrow function을 사용하면서 이런 방법을 쓸 필요성이 없어졌습니다.

var a = 10;
var obj = {
	a: 20,
    b: function() {
         console.log(this.a); // 20
       
       function c() => {
         console.log(this.a); // 20
       }
       c();
     }
   }
obj.b();

this를 바인딩하지 않기 때문에 스코프 체인으로 this에 바로 접근할 수 있기 때문입니다.

 


 

콜백 호출 시 기본적으로는 함수 내부에서와 동일합니다.

이에 대해 이야기하기 전에 call, apply, bind 메소드가 어떻게 작동하는지 잠깐 보고 가겠습니다.

// 대괄호는 생략가능
// 대괄호가 없으면 무조건 와야하는 필수 매개변수
func.call(thisArg[, arg1 [arg2[, ...]]])
func.apply(thisArg, [argsArray])
func.bind(tisArg[, arg1[, arg2[, ...]]])

 

이제 다시 콜백함수 호출시 어떻게 나타내는지 알아보죠

var callback = function () {
	console.dir(this);
    };
var obj = {
	a: 1,
	b: function(cb) {
    	cb();
     }
  };
obj.b(callback);

b 함수를 실행하는 데 callback함수를 넘겨주었습니다. cb라는 변수가 callback이 된 것이죠. 그런 다음 cb();로 callback함수를 실행시키고 있습니다. 그 결과 console.log로 this가 찍힐 겁니다.

this로 나오는 것은 바로 전역객체입니다. 함수로 호출했기 때문이죠.

 

var callback = function () {
	console.dir(this);
    };
var obj = {
	a: 1,
	b: function(cb) {
    	cb.call(this);
     }
  };
obj.b(callback);

이때는 call을 사용했음으로 this는 obj가 된다.

즉 콜백함수 자체로 this가 결정되는 것이 아니라 콜백함수를 넘겨받은 대상이 어떤 식으로 처리하느냐에 따라 달라지게 됩니다. 

기본은 전역객체이지만 call, apply 같이 껴있으면 지정한 this의 값으로 바뀐다.

 

다른 예시도 살펴보겠습니다.

var callback = function() {
	console.dir(this); //전역객체(window)
};
var obj = {
	a: 1
};
setTimeout(callback, 100); //0.1초

 

setTimeout은 this를 별도로 처리하지 않기 때문에 this가 전역객체가 나온다. 이 상황에서 우리가 원하는 값으로 this를 지정하려면 bind를 사용하면 됩니다.

var callback = function() {
	console.dir(this); // obj
};
var obj = {
	a: 1
};
setTimeout(callback.bind(obj), 100); //0.1초

이렇게 하면 console.dir의 this가 obj로 나오게 됩니다.

 

이번에는 이벤트 핸들러를 살펴보겠습니다.

document.body.innerHTML += '<div id="a">클릭하세요</div>';

document.getElementById('a').addEventListener(
	'click',
    function() {
    	console.dir(this);
    }
);

여기서 this는 html dom 엘리먼트가 나옵니다.

addEventListener라는 함수가 콜백함수를 처리할 때 this는 이벤트가 발생한 그 타겟대상 엘리먼트로 하도록 정의가 되어있기 때문입니다.

이런 경우에도 this를 obj로 바꾸고 싶다면 bind를 사용하면 됩니다.

document.body.innerHTML += '<div id="a">클릭하세요</div>';

document.getElementById('a').addEventListener(
	'click',
    function() {
    	console.dir(this);
    }.bind(obj)
);

이렇게 하면 콜백함수를 넘겨줄 때 this를 obj로 하도록 명시적으로 바인딩했기 때문에 this가 obj로 나오게 됩니다.

 

이제 전체적으로 정리를 해보겠습니다.

  • 기본적으로는 함수의 this와 같습니다.
  • 제어권을 가진 함수가 콜백의 this를 지정해둔 경우도 있습니다(addEventListener가 대표적)
  • 이 경우에도 개발자가 this를 바인딩해서 콜백을 넘기면 그에 따르게 된다.

 


 

생성자함수(new) 호출시 this는 새로 만들 인스턴트 객체 그 자체가 곧 this가 됩니다.

function Person(n, a) {
	this.name = n;
	this.age = a;
}
var roy = Person('나', 20);
console.log(window.name, window.age); // 나 20

roy의 this는 전역객체를 가리키게 된다.

new 연산자 없이 그냥 person 함수를 호출할 경우에는 roy에는 아무것도 담기지 않고 전역객체의 name, age 프로퍼티에 값이 할당되어서 '나 20'이 출력됩니다

 

그런데 new를 넣어서 호출하게 되면 이렇게 됩니다.

function Person(n, a) {
	this.name = n;
	this.age = a;
}
var roy = new Person('나', 20);
console.log(roy); // Person {name: '나', age: 20}

새로 생성될 Person의 인스턴스 객체 자신이 this가 되기 때문에 roy의 name, age가 되면서 나, 20이 담긴채로 roy가 되면서 Person {name: '나', age: 20} 가 나오게 됩니다.

 


지금까지 전체 this에 대해 알아보았습니다.

전역공간에서 this window(브라우저) / global (node.js)
함수 호출시 this ES5 : window(브라우저) / global (node.js)
ES6 : arrow function 도입해서 위 컨텍스트의 this를 가져다 씀
메서드 호출시 this 메서드 호출 주체 (메서드명 앞)
callback 호출시 this 기본적으로는 함수 내부에서와 동일
생성자함수 호출시 this 인스턴스 (새로 만든 객체 그 자체가 this)

 

이 표가 이제는 이해가 되었으리라 생각합니다.

728x90