최근 글쓴이는 우아한테크러닝 3기 교육을 받고있습니다. 이전에 사용했던 Javascript 스펙들을 다시보게 되면서 배운 점들을 시리즈 글로 올려볼까 합니다. 글에 오류가 있거나 궁금한 점이 있다면 언제든 댓글로 알려주세요!
이전 글에서도 this의 사용법이 다양하다고 언급했습니다. 대체로, this는 호출 당시의 상황을 보고 this가 가리키는 대상을 정하게 됩니다. 이렇게 상황에 따라 달라지는 this의 결정 방식을 실행 컨텍스트라고 합니다.
const person = {
name: '박성우',
getName() {
return this.name;
}
}
console.log(person.getName());
위 예시에서, person
객체는 getName
이란 함수를 가지고 있는데, 이때 반환값으로 this
를 사용하게 됩니다. Java와 같이 객체지향에 익숙한 사람은 '이때 this
는 당연히 person
본인을 말하는 거겠지?' 라고 생각하며 코드를 이해하고, 실제로 출력되는 값도 생각한 결과와 동일하게 나오게 됩니다.
const man = person.getName;
console.log(man());
다만, 다음과 같이 man
으로 호출하게 된다면 예상했던 name
값이 출력되지 않습니다. 위에서 언급했듯this
는 현재 호출시 상황을 보고 대상을 정하는데, 첫번째 예시의 경우 this
는 person.getName()
으로 호출되었기 때문에 person
을 this
의 대상으로 정한 것입니다. 그러나 두번째 예시는 그저 man()
으로 호출되었음으로, this
는 가리킬 대상을 찾지 못해 결국 기본적인 사용방식인 전역객체(브라우저에서는 window
객체)를 가리키게 됩니다.
🤔
man
을 선언할 때person.getName
으로 값을 지정했기 때문에 그때부터this
가 결정될 것 같다고 생각할 수 있지만, 이전 글에서도 말했듯 현제man
은 변수이고 함수도 값이기 때문에 함수가 호출되지 않고 값으로 저장되었음으로 그 상황에서는this
가 호출되지 않은 상태입니다.
그런데, 저런 상황이 실제로 많이 일어날까요? 코딩에서 많이 사용하지 않은 기법이라고 생각하겠지만 저희는 은근 저런 상황을 많이 마주치게 됩니다. 이벤트 시스템을 예로 들어볼까요? 만약 버튼을 클릭할 때 실행할 함수를 만들어야 한다면, 함수를 명시하기 위해 콜백함수를 사용하게 됩니다. 콜백함수에서 this
를 사용하게 된다면? 그때는 실제 콜백함수가 호출하는 환경에 따라 this
가 가리키는 대상이 바뀌게됩니다.
이렇게 this
의 대상이 계속 바뀌게 되는 것을 방지하기 위해, Javascript 스펙에 이를 고정하는 함수를 제공하는데, 그중 한 함수가 bind
함수입니다.
button.addEventListener('click', person.getName.bind(person));
다음과 같이 적게 된다면, person.getName
이 실행될 때 마다 항상 this
를 person
으로 해석하라고 지정할 수 있습니다. 이 외에도 call
, apply
등을 사용해 this
를 지정할 수 있습니다.
this
화살표 함수(Arrow function)는 ES6에서부터 추가된 스펙입니다. 기존 함수에 비해 더욱 간단하게 함수를 표현할 수 있는 특징이 있습니다.
const foo = function(x) {
};
const bar = (x) => {
};
위와 같이 foo
와 bar
는 같은 기능을 하는 함수입니다.
실행 컨텍스트를 이야기하다 갑자기 화살표 함수를 이야기하는 이유는, 화살표 함수 안에서 this는 항상 상위 scope의 this를 가리킵니다. 이를 Lexical this라고 하는데, 이 스펙을 활용한다면 위에서 사용한 person
객체를 다음과 같이 수정할 수 있습니다.
const person = {
name: '박성우',
getName: () => {
return this.name;
}
}
console.log(person.getName()); // 박성우
const man = person.getName;
console.log(man()); // 박성우
두 방식 다 this
가 person
을 가리키도록 고정되었기 때문에 의도대로 정상 작동합니다.
💡 이전 React에서 클래스 컴포넌트에서 이벤트 콜백함수를 추가할 때
this.onClickButton = this.onClickButton.bind(this);
와 같이 처리하는 것도 실행 컨텍스트와 관련이 있습니다. 다만 이는 화살표 함수로 선언한다면 위와 같이 처리할 필요가 없습니다.
function foo(x) {
return function bar() {
return x;
}
}
const f = foo(10);
console.log(f()); // 10
위 콜백함수는 bar
에서 foo
의 매개변수인 x
를 다루게 되는데, 일반적인 프로그래밍 언어에서는 이해가 안되는 로직입니다. f
라는 변수를 만들 때 변수 x
는 foo
스코프가 끝나면 소멸될탠데, 이후 f
변수를 사용할 때 x
가 살아남아 리턴된다는 것은 신기한 일 일것입니다. 이는 콜백함수로 함수를 리턴할 때 반환된 함수가 사용하는 외부 스코프를 저장하는데, 이를 클로저라고 합니다.
클로저를 적절하게 사용할 수 있는 예시는 객체 안 값을 보호할 때 사용할 수 있습니다.
const person = {
age: 10,
}
person.age = 500;
만약 다음과 같이 person
객체를 만들었다고 하면, 그 안 age
는 정말 마음대로 바꿀 수 있습니다. 코드를 짠 의도처럼 실제 나이를 의미하는 값이라고 하면 터구니없는 500이라는 값을 넣는다고 해도 문제가 되지 않는다는 것입니다. 그러면 어떻게 해서 객체의 값을 제한할 수 있을까요?
function makePerson() {
let age = 10;
return {
getAge() {
return age;
},
setAge(x) {
age = x > 1 && x < 130 ? x : age;
}
}
}
const p = makePerson()
console.log(p); // {getAge: (fun), setAge: (fun)}
p.setAge(150);
console.log(p.getAge()); // 10
p.setAge(30);
console.log(p.getAge()); // 30
위와 같이 객체를 만드는 함수 makePerson
을 만든다면, age
값을 보호할 수 있습니다. 객체의 getAge
를 통해 age
값을 가져올 수 있고, setAge
를 통해 age
값을 설정할 수 있습니다. 특히나, setAge
의 경우에는 받은 값이 특정 범위를 넘어가면 받은 값을 age
로 설정하지 않고 기존 값을 유지하는 기능을 넣을 수 있습니다. 그리고 클로저 덕분에 setAge
로 age
값을 수정해도 age
값을 같이 공유하기 때문에 getAge
를 했을 때 같은 age
값을 가져올 수 있는 것입니다.
💡 클로저를 보면, 함수형 프로그래밍의 커링(currying)을 생각하게 되는데 두 개념은 서로 같은 개념으로 생각할 수 있습니다. 클로저라는 개념을 활용한 것이 커링이고, 커링을 하기 위한 수단으로 클로저를 사용한것이죠.
다음에는 Modern Javascript의 꽃인 비동기에 대해 다뤄보겠습니다.