TIL DAY.45 [코어 자바스크립트] this

Dan·2020년 11월 28일
0

평소에 자바스크립트를 공부하면서 제일 혼란스러웠던게 this라는 개념이였다. 분명 쓰고는 있지만 내가 맞게 쓰고 있는건지 항상 의문이 들었다. 그래서 이번에는 평소 혼란스러웠던 this라는 개념에 대해서 좀 더 명확하게 알아보는 시간을 갖도록 하겠다.

다른 대부분의 객체지향 언어에서 this는 클래스로 생성한 인스턴스 객체를 의미한다고 한다. 클래스에서만 사용할 수 있기 때문에 혼란의 여지가 많지 않다. 하지만 자바스크립트에서의 this는 어디서든 사용할 수 있기 때문에 굉장히 혼란스러웠다. 특히 this로 인해 생기는 오류들을 해결할려면 원인을 추적해서 수정해야하는데, 정확한 작동 방식을 이해하지 못한다면 문제를 해결할 수 없다. 그래서 상황에 따라 this를 어떻게 사용해야하는지 부터 파악해보겠다.

상황에 따라 달라지는 this

자바스크립트의 this는 실행 컨텍스트가 생성될 때 함께 된다 그리고 실행 컨텍스트는 함수가 호출 될 때 생성되므로, this는 함수를 호출할 때 결정된다고 할 수 있다. 지금부터 다양한 상황에서 this가 어떤 값을 바라보게 되는지 살펴보면서 원인을 파악해보겠다.

전역 공간에서의 this

전역 공간에서 this는 전역 객체를 가리킨다 그 이유는 전역 컨텍스트를 생성하는 주체가 바로 전역 객체이기 때문이다. 브라우저 환경에서 전역개체는 window, Node.js환경에서는 global이다.

var a = 1;
console.log(a);  //1
console.log(window.a); // 1
console.log(this.a); //1

위의 예제를 살펴보면 전역공간에서 선언한 변수 a에 1을 할당했을 뿐인데 window.a랑 this.a 모두 1이 출력되는 것을 볼 수 있다. 전역 공간에서 전역객체인 window랑 this가 같은 값을 출력하는건 당연한 일이지만 그 값이 1인 것은 예상밖일수도 있다. 값이 1인 이유는 자바스크립트의 모든 변수는 실은 특정 객체의 프로퍼티로서 동작하기 때문이다. 즉 사용자가 var연산자를 이용해 변수를 선언하더라도 실제 자바스크립트 엔진은 어떤 특정 객체의 프로퍼티로 인식한다는 것이다. 여기서 특정 객체는 실행 컨텍스트에서 다뤘던 Lexcial Environment이다. 실행 컨텍스트는 변수를 수집해서 L.E의 프로퍼티에 저장되고 어떤 변수를 호출하면 L.E를 조회해서 일치하는 프로퍼티 값을 반환한다.

그냥 쉽게 생각하면 단순하게 (window.)이 생략된 것이라고 여겨도 무방하다. 그럼 이를 적용한 예제를 통해 이해를 좀 더해보도록 하자.

var a = 1;
window.b = 2;
console.log(a, window.a , this.a); //1 1 1
console.log(b, window.b , this.b); //2 2 2

window.a = 3;
b= 4;
console.log(a, window.a, this.a); //3 3 3
cosnole.log(b, window.b, this.b); //4 4 4

메서드로서 호출할 때 그 메서드 내부에서의 this

함수 vs. 메서드

함수를 실행하는 방법 중 가장 일반적인 방법은 함수로 호출하는 경우와 메서드로 호출하는 경우 두 가지가 존재한다. 프로그래밍 언어에서 함수와 메서드는 미리 정의한 동작을 수행하는 코드 뭉치로, 이 둘을 구분하는 유일한 방법은 독립성 뿐이다.
함수는 그 자체로 독립적인 기능을 수행하는 반면, 메서드는 자신을 호출한 대상 객체에 관한 동작을 수행한다.
그렇다면 '함수로서 호출' 과 '메서드로서 호출'을 어떻게 구분할까?
보통 함수 앞에 점(.) 이나 대괄호가 존재할 경우 메서드로서 호출 된것이고 점이 없으면 함수로서 호출된 것이라고 볼 수 있다.

메서드 내부에서의 this

this에는 호출한 주쳉에 대한 정보가 담긴다, 어떤 함수를 메서드로서 호출하는 경우 호출 주체는 바로 함수명 앞의 객체가 된다. 점 표기법의 경우 마지막 점 앞에 명시된 객체가 곧 this가 된다.

var obj = {
	
    methodA: function () {console.log(this);},
    inner  : {
    		methodB: function() {console.log(this);}
    }
};
obj.methodA();			// {methodA: f, inner:{...}}  (===obj)
obj.inner.methodB(); 	// {methodB: f}				(===obj.inner)

함수로서 호출할 때 그 함수 내부에서의 this

함수 내부에서의 this

어떤 함수를 함수로서 호출할 경우에는 this가 지정되지 않는다. this에는 호출한 주체에 대한 정보가 담긴다고 했는데 함수로서 호출하는 것은 호출 주체를 명시하지 않고 개발자가 코드에 직접 관여해서 실행하는 것이기 때문에 호출 주체의 정보를 알 수 없는 것이다.

메서드의 내부함수에서의 this

메서드 내부에서 정의하고 실행한 함수에서 this는 가장 자주 혼란을 느끼는 지점 중 하나이다. 그러나 이미 어떤 함수를 메서드로서 호출할 때와 함수로서 호출할 때 this가 무엇을 가르키는지를 알고 있다. 내부 함수 역시 이를 함수로서 호출했는지 메서드로서 호출했는지만 파악하면 this의 값을 정확히 맞출 수 있다. 아래의 예시를 통해 this에 대해 명확하게 파악해보도록 하자.

var obj1 = {
	outer : function () {
    	console.log(this); // obj1
        var innerFunc = function() {
        	console.log(this);   // 1. 전역객체(window) 2.obj2
         }
        innerFunc();
        
        var obj2 = {
        		innerMethod :  innerFunc
        }
        obj2.innerMethod();
        }
 };
 obj1.outer();

위으 코드의 흐름을 생각해보며 this 값을 어떻게 파악했는지 알아보겠다.
1 . obj1 이라는 객체를 생성하고 outer이라는 프로퍼티를 obj1.outer();로 실행 시켜준다.
2. obj1.outer() 로 실행시켰고 outer앞에 점(.) 이 붙어있으므로 메소드로서 함수를 호출한것이므로 outer의 this의 값은 obj1이다.
3. 그 다음 innerFunc()을 통해 innerFunc이라는 함수를 호출하는데 여기서 함수를 호출할 때 앞에 점(.)이 없은 것으로 보아 함수로서 호출 된 것이므로 this가 지정되지 않게 된다.
4. this는 자동으로 스코프 체인상의 최상위 객체인 전역객체(window)가 바인딩 된다.
5. 마지막으로 obj2.innerMethod를 통해 innerFunc을 다시 호출해서 console을 찍게되는데 여기서 innerMethod 앞에 점(.)이 있는 것으로 보아 메소드로 함수를 호출 한것으로 this의 값은 obj2가 된다.

메소드의 내부 함수에서의 this를 우회하는 방법

ES6에서는 함수 내부에서 this가 전역객체를 바라보는 문제를 보완하고자, this를 바인딩하지 않는 화살표 함수를 새로 도입했다. 화살표 함수는 실행 컨텍스트를 생성할 때 this 바인딩 과정 자체가 빠지게 되어, 상위 스코프의 this를 그대로 활용할 수 있게 되었다. 내부함수를 화살표 함수로 바꾸면 3-1-3-3절의 '우회법'이 불필요해진다.

var obj = {
	outer: function() {
    	console.log(this); // (1) {outer:f}
        var innerFunc = () => {
        		console.log(this); //(2) {outer: f}
        };
        innerFunc();
      }
}
obj.outer();

콜백 함수 호출 시 그 함수 내부에서의 this

setTimeout(function() {console.log(this); }. 300);  //(1)

[1,2,3,4,5].forEach(function (x) {   // (2)
		console.log(this, x);
 });
 
 document.body.innerHTML += '<button id = "a"> 클릭 </button>;
 docunment.body.querySelector('#a')
 	.addEventListener('click', function(e) {  //(3)
    		console.log(this, e);
     });

(1)의 setTimeout 함수와 (2)의 forEach 메서드는 그 내부에서 콜백 함수를 호출할 때 대상이 될 this를 지정하지 않았다. 따라서 콜백 함수 내부에서의 this는 전역개체를 참조한다.

(3)의 addEventListner 메서드는 콜백 함수를 호출할 때 자신의 this를 상속하도록 정의돼어 있다, 그러므로 메서드명의 점(.) 앞부분이 곧 this가 된다.

생성자 함수 내부에서의 this

프로그래밍적으로 '생성자'는 구체적인 인스턴스를 만들기 위한 일종의 틀이라고 정의한다. 이 틀에는 해당 클래스의 공통 속성들이 미리 준비돼 있고, 여기에 구체적인 인스턴스의 개성을 더해 개별 인스턴스를 만들 수 있다. 자바스크립트 함수 같은 경우는 new 명령어와 함께 함수를 호출하면 해당 함수가 생성자로서 동작하게 되며 생성자로서 함수가 호출된 경우 내부에서의 this는 곧 새로 만들 구체적인 인스턴스 자신이 된다.

var Cat = function ( name, age) {
		this.bark = '야옹';
        this.name = name;
        this.age  = age;
 };
var choco = new Cat('초코', 7);
var nabi = new Cat('나비', 5);
console.log(choco, nabi);

//결과
Cat { bark: '야옹' , name: '초코', age: 7}
Cat { bark: '야옹' , name: '나비', age: 5}

위의 예제 결과를 보면 new 명령어를 사용해 Cat함수를 호출해서 변수 choco, nabi로 할당하고 출력해보니 cat클래스의 인스턴스 객체가 각각 출력되는것을 볼 수 있다. 즉 6번째 줄에서 실행한 생성자 함수 내부에서의 this는 choco의 인스턴스를 7번째의 this는 nabi 인스턴스를 가리킴을 알 수 있다.

profile
만들고 싶은게 많은 개발자

0개의 댓글