티스토리 뷰

반응형

얼마전 본 면접에서 this에 대한 질문을 받았었는데 전역에 선언된 화살표 함수 내에서 this가 undefined 값을 가진다고 완전히 잘못된 대답을 하고는 집에 돌아와서 심히 자괴감을 느낀터라 이번 기회에 정확하게 정리를 해야겠다고 결심했다.

 

이 글은 [코어 자바스크립트 (정재남, 위키북스)] 책을 참고하여 제 생각과 함께 정리한 글입니다.

https://wikibook.co.kr/corejs/

 

코어 자바스크립트: 핵심 개념과 동작 원리로 이해하는 자바스크립트 프로그래밍

자바스크립트의 근간을 이루는 핵심 이론들을 정확하게 이해하는 것을 목표로 합니다! 최근 웹 개발 진영은 빠르게 발전하고 있으며, 그 중심에는 자바스크립트가 있다고 해도 결코 과언이 아닙니다. ECMAScript2015 시대인 현재에 이르러서도 ES5에서 통용되던 자바스크립트의 핵심 이론은 여전히 유효하며 매우 중요합니다. 《코어 자바스크립트》는 자바스크립트의 근간을 이루는 핵심 이론들을 정확하게 이해하는 것을 목표로 합니다. 기본 이론들 중 ES6에서도

wikibook.co.kr

 

메소드로서 호출한 함수 내부의 this, 함수로서 호출한 함수 내부의 this

일반적으로 this 값은 함수 호출의 주체가 누구냐에 따라 결정된다. 다음 코드를 보자.

function func() {
    console.log(this);
}

var obj = {
	method: func
}
obj.method();
func();

obj.method()는 메소드로서 func()는 함수로서 호출되었다.

함수를 실행하는 방법 중 메소드 vs 함수 두 가지가 가장 일반적인데 둘의 유일한 차이는 독립성에 있다. 함수는 자체로 독립적인 기능을 수행하는 반면, 메서드는 자신을 호출한 객체에 관한 동작을 수행한다.

결과는 다음과 같다.

메소드로서 호출한 함수와 함수로서 호출한 함수의 this값 차이

메소드로서 호출된 함수는 호출의 주체인 객체 obj를 반환하고 함수로서 호출된 함수는 this값이 따로 지정되지 않아 스코프체인에 있는 this값을 찾아 올라가다가 가장 상위 객체인 전역객체(Window)를 반환하게 된다.

일반적으로 메소드로서 호출된 함수에서의 this는 obj.method() 처럼 점(.) 앞에 표기된 객체를 가리킨다고 보면 된다.

 

그렇다면 이것을 화살표 함수로 바꿔보면 어떻게 될까?

화살표 함수에서의 this값

화살표 함수로 바꾸면 메소드로 호출하든 함수로 호출하든 모두 전역객체를 반환한다. 어떻게 된것일까?

 

일반적으로 기존 함수의 this 값은 실행 컨택스트가 생성될 때, 즉 함수를 호출할 때 동적으로 그 값이 바인딩된다. 그러나 화살표 함수 내에서의 this는 값이 따로 바인딩되지 않고 정적으로 결정된다. 때문에 스코프체인상 가장 가까운 this를 반환한다. 즉 화살표 함수 내부에는 this가 아예 없다. 그렇기 때문에 obj.method()를 호출하더라도 근처에 지정된 this값이 없으므로 스코프체인의 최상위 객체인 Window를 반환하게 되는것이다.

 

이것만 보면 화살표 함수가 논리상 더 이상해 보인다. 일반적인 생각으로는 메소드로 호출한 함수 내부의 this 값은 해당 객체가 반환되는것이 타당하기 때문이다. 이 때문에 MDN의 arrow function 문서에도 "이 함수 표현은 메소드 함수가 아닌 곳에 가장 적합" 하다고 되어있다.

그러면 화살표 함수의 this는 어떻게 써야 합리적일까?

 

화살표 함수(arrow function) 내부의 this

화살표 함수는 주로 어떤 인스턴스의 콜백 함수로 쓰일 때 그 진가를 발휘한다. 일반 함수에서 콜백 함수는 해당 콜백 함수 실행의 제어권을 가지는 함수가 별도로 this를 바인딩하지 않는 한 전역객체를 반환한다. 그러나 화살표 함수는 해당 함수가 정의되는 시점에 이미 주변 스코프의 this를 할당받았기 때문에 콜백을 포함하고 있던 객체를 정확히 반환한다.

말이 조금 복잡하니 일단 다음 코드를 보자.

일반 함수로 콜백을 정의한 경우

Person 생성자 함수 내에서 setTimeout으로 비동기 콜백함수를 실행하도록 하였다. 1초 뒤에 전역객체(Window)가 찍히고 p.age를 확인해보면 기존값 그대로 0이 찍힌다. 반면에 아래에 콜백을 화살표 함수로 정의한 경우에는 생각한대로 동작한다.

화살표 함수로 콜백을 정의한 경우

그런데 여기서 나는 이상하다는 생각이 들었다. 위에 메소드로서 호출된 함수와 함수로서 호출된 함수의 차이를 알아볼 때, 일반 함수의 경우에도 지정된 this값이 없을 경우 스코프체인을 따라 올라가면서 찾는다고 했었다. 그렇다면 화살표 함수와 어떤 차이가 있는것일까?

혼자 곰곰히 생각해본 결과 this 값을 바인딩하는 "시점"에 차이가 있기 때문이라고 결론내렸다. 이 결론의 근거는 call, apply, bind 메소드로 알 수 있다.

 

call / apply / bind 메소드로 알아보는 this

일반 함수 내부의 this값은 함수가 실행될 때, 즉 실행 컨택스트가 생성될 때 동적으로 바인딩 된다. 반면에 화살표 함수 내부의 this값은 함수가 정의(?)될 때, 즉 실행되기 전에 스코프 체인에 있는 this값을 넣어주고 이 후 실행될 때에는 별도로 this를 바인딩 하지 않는다. 즉 정적인 값이다.

실제로 MDN 화살표 함수 설명을 보면 다음과 같이 써있다.

화살표 함수 표현(arrow function expression)은 function 표현에 비해 구문이 짧고  자신의 this, arguments, super 또는 new.target을 바인딩 하지 않습니다.

화살표 함수는 this 외에도 arguments, super 등의 값을 바인딩 하지 않는다. 또한 일반 함수와는 다르게 생성자 함수로 쓸 수 없기 때문에 이와 관련된 prototype 프로퍼티를 포함하지 않아서 더 가볍다.

 

화살표 함수에 대한 설명은 이쯤 하고 아래 코드를 보자.

var 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
func.apply({ y: 1 }, [7, 8, 9]);	// {y: 1} 7 8 9

call과 apply 메소드는 직접 원하는 객체를 this로 바인딩 하여 함수를 호출할 수 있도록 해준다. 위의 코드를 보면 일반적인 함수 호출의 경우 this값으로 전역객체(Window)를 반환했지만 call과 apply의 경우 첫 번째 인자에 객체를 넘겨주면 해당 객체가 this값으로 할당되어 반환되는 것을 볼 수 있다. call과 apply의 차이는 첫 번째 이후의 인자들을 넘기는 방법에 차이가 있으며 나머지는 동일하게 작동한다.

객체의 메소드에 다른 객체를 할당하여 호출하는 모습

bind의 경우 this로 지정할 객체를 넘기는 것은 동일하나 적용한 함수를 곧바로 실행하는 것이 아니라 this값이 지정된 새로운 함수를 반환하기만 한다. 또한 매개변수 일부분도 함께 미리 넘길 수 있다. 이렇게 반환받은 함수를 나머지 매개변수와 함께 실행시켜주면 이전에 지정한 값들을 기억하고 있다가 실행된다. 아래를 보자.

어떠한 객체의 메소드로 정의되어 있는 함수라도 call, apply, bind를 적용하면 원하는 객체를 this로 지정할 수 있다.

변수 bindFunc에는 { x: 1000 } 객체가 this로 바인딩되고 매개변수 a=7로 할당된 함수가 넘겨진다. 그 함수에 나머지 매개변수 b(8)를 넘겨 호출해주면 해당 함수가 실행된다.

 

일반 함수는 this를 동적으로 바인딩하기 때문에 변경이 가능하다. 그러면 화살표 함수의 경우를 살펴보자.

 

화살표 함수에 다른 객체 대입을 시도하지만 모두 실패

일반 함수의 경우와 같이 화살표 함수 내에서 obj 객체를 this값으로 갖게 하기 위해서 일반 함수로 한번 감싼뒤 화살표 함수 자체를 리턴해주었다. 해당 함수 내부의 this값은 실행하기 전 이미 obj로 할당되었고, 이 후 실행 컨택스트가 생성되더라도 따로 this 바인딩을 거치지 않기 때문에 call / apply / bind 를 이용해서 다른 객체를 할당해도 계속 동일한 객체만 바라본다.

 

이것을 통해 화살표 함수가 일반 함수와 다른 this 값을 갖는 과정을 대략이나마 알게 되었다. 그러나 아직은 복잡하게 작성된 코드나 화살표 함수를 비롯한 최신 문법으로 작성된 코드를 보면 한 눈에 this값을 알아차리거나 로직을 알기가 쉽지 않다. 이를 타개하는 방법은 수 많은 경험과 훈련 뿐인 것 같다.

반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함