2020.4.29 TIL
뭘까요. this에 대해서는 블로그에서 몇 번(??) 언급했...고 정리했던 적이 있었던 것 같은데, 정리를 해도 명확하게 대답할 수 없었기 때문에 다시 한번 정리를 해 봅니다. this가 어떤 건지, 어떨 때 나오는지에 대해 쉽고 간단하게 (나는 쉽고 간단한 걸 좋아하니까) 시작해 봅시다. this를 정리하면서 ES6 기능인 arrow function, 화살표 함수에 대해서도 이야기를 합니다! 둘이 전혀 관련이 없을 것 같지만!!! 있답니다!!!
이름 그대로 번역하자면 "지금" 개발 용어로 말하자면 실행 문맥! 영어로는 execution context! 현재 실행 문맥이 뭔데..라고 말할 수 있다. 맞다. 나도 그랬다. 하지만 괜찮다. 읽고, 읽고, 읽다 보면 언젠가는 어렴풋이 알게 된다. 말 그대로 현재 실행하고 있는 문맥을 짚어 준다는 것이다. 지금, 어디에서 돌고 있는지, 그 바탕이 되는 곳을 this라고 한다. 호출자라고도 말하는데, 이 this를 누가 호출했느냐? (지금 사용하고 있는 곳이 어디인지?)이므로 똑같다.
정확히는 execution context의 한 부분이 this인 것이지만, this가 execution context의 인기(?) 지분을 거의 다 먹고 있는 것으로 봐서는, this는 execution context라고 생각해도 괜찮을 것이다.
함수를 생성하게 되면 고유의 스코프에서 자동으로 this가 생성이 됩니다. 왜냐하면, 함수는 자신만의 그라운드를 가지고 있기 때문이조. 그리고 함수가 실행되고, 끝날 때까지 this는 유효합니다.
이 this는 한 곳에 정착해 있지 않고, call time에 따라서 이리저리 옮겨다닙니다. 그래서 어디에 지정해 주었느냐? 보다는 어떻게 불렸는지가 더 중요합니다. 함수 안에 지정을 해 주었어도 함수를 변수에 할당하고, 그것을 호출한다면 결국엔 호출자가 함수가 아닌 전역으로 설정이 되기 때문입니다.
자, 여기서 this의 5가지 패턴이 나타납니다.
5개라고? 하지만 5개인 듯 5개 아닌 5개 같은 패턴이기 때문에 크게 걱정할 필요가 없어요.
alert(window === this) //true
this는 코드 실행이 될 때 전역(global)으로부터 시작합니다. window라는 큰 전역(global)은 객체입니다. 제일 처음의 this는 전역 객체인 window를 향하게 됩니다. window 뒤에는 아무것도 없는 제일 땅바닥(^^;)이기 때문입니다.
function foo() {
console.log(this === window);
};
foo(); //true
기본적으로 함수를 만들거나 이미 있는 함수를 사용하게 될 때 클로저가 아닌 이상 전역에 두게 됩니다. 그리고 전역인 window는 위에서 말했던 것처럼 "객체"이고, 그 객체의 메소드가 함수이기 때문에, 결과적으로 window.foo()로 실행이 되는 것이죠. 그렇기에, 함수 안에 있는 this도 foo의 바탕인 전역 window를 향하게 됩니다.
let obj = {
a: function() {
console.log(this === window);
console.log(this === obj);
}
}
obj.a()// false, true
객체의 메소드 함수는 어떨까요? 혹시, 위의 2개와 동일점을 찾으셨나요? 그렇습니다. window.foo()와 obj.a()는 똑같습니다. foo()를 실행시키면 foo의 바탕인 window가 this가 되고, a()를 실행시키면 a의 바탕인 obj가 this가 됩니다. 1, 2, 3번이 다 같다는 이야기이죠.
class Car {
constructor (name, color) {
this.name = name;
this.color = color;
}
}
let bungbung2 = new Car('BBang', 'black');
bungbung2; // {name: 'BBang', color: 'black'}
생성자 함수는 어떨까요? 요 부분은 조금 다릅니다. class에서 정의된 this는 class를 따르는 게 아닌, class로 선언한 인스턴스에게 this가 옮겨갑니다. 저같은 아직 배우는 중인 학생에게는 이 this의 쓰임이 제일 많이 보이고, 또, 연습으로 사용하게 되어서 그런지 굉장히 익숙합니다.
function foo() {
let a = 'apple';
console.log(this.a);
}
foo.apply({a: 'air'})
//air
마지막으로 call과 apply입니다. 요건 특수한 경우인데요, 어떠한 함수에 call과 apply 함수를 사용하게 된다면, 첫 번째 인자(객체)를 무조건 this가 따라가게 됩니다. 그렇기에 첫 번째 인자를 바꾸고 싶지 않다면 null을 사용하여 this의 이동을 막게 할 수 있습니다.
let foo = {
outFunc: function() { //메소드
function inFunc() { //메소드 안의 함수
console.log(this)
}
inFunc();
},
}
foo.outFunc(); //window
outFunc를 전역에서 실행을 하게 되면 outFunc의 바탕인 foo가 this가 되지만, outFunc 함수 안의 inFunc를 호출을 하게 되면, inFunc는 function invocation이 되기 때문에 this는 window가 됩니다.
저는 this를 window값이 아닌 foo로 하고 싶은데, 어떻게 해야 될까요? 이때 나타나는 것이 arrow function입니다.
let foo = {
outFunc: function() { //메소드
let inFunc = () => { //메소드 안의 함수
console.log(this === foo)
}
inFunc();
},
}
foo.outFunc();
(예시를 바꾸면 bind로도 충분히 가능합니다!)
화살표 함수는 ES6에 도입되었습니다. 함수를 조금 더 간결하게 만들어 줄 수 있는 특징이 있는데요, function이라고 쓸 필요 없이 화살표로 표시를 합니다. 매우매우매우매우 편합니다. 익명 함수를 쓸 때는 더 진가를 발휘하지요.
let foo = (x, y) => x + y;
foo(1, 2); //3
//익명 함수를 사용할 때
function foo(x) {
return function(y) {
return function(z) {
return x + y + z
}
}
}
let foo => x => y => z => x+y+z;
foo(1)(2)(3); //6
//놀랍게도 두 함수는 같은 값을 가지게 됩니다.
두 가지의 특징이 더 있는데요, 하나는 리턴 생략이 가능하다는 점과, 함수임에도 불구하고 실행 컨텍스트를 가지지 않는다는 점입니다.
그렇기 때문에 arrow function은 function scope가 아닌 block scope를 가지게 됩니다. 정말.. 정말 특이하죠? 이러한 특이점 때문에 메소드 안의 함수를 화살표 함수로 쓰게 되면, function scope를 가지지 않게 되고, 그렇게 되면 실행 컨텍스트가 만들어지지 않아서, 그 위의 것으로 상속이 됩니다.
이러한 화살표 함수와 일반 함수의 특성을 잘 살려서, 때에 맞는 함수를 사용하는 것이 중요합니다.
* 틀린 부분이 있으면 꼭 말씀해 주세요! 정확한 지식 전달이 저와 우리 모두를 살립니다 *