요즘 바닐라 자바스크립트로 디자인패턴에 대해서 공부하던 중 일반함수와 화살표함수(arrow function)이 가르키는 this가 다르다는 것을 알게 되었습니다. 예전부터 궁금했던 내용이었는데, 정리하는 시간을 가져보았습니다.
1. 일반함수
this는 자신이 종속된 객체를 나타낸다.
function normalFunction() {
this.name = "화살표 함수";
return {
name: "일반함수",
call: function() {
console.log(this.name);
}
};
}
const nfunc = new normalFunction();
nfunc.call();
위의 예제의 경우, this는 return하는 객체를 나타낸다.
{
name: "일반함수",
call: function() {
console.log(this.name);
}
}
2. 화살표 함수
this는 자신이 종속된 인스턴스를 나타낸다.
function arrowFunction() {
this.name = "화살표 함수";
return {
name: "일반함수",
call: () => {
console.log(this.name);
}
};
}
const aFunc = new arrowFunction();
aFunc.call();
위의 예제의 경우, this는 생성된 인스턴스 (new arrowFunction())의 생성자가 가르키는 arrowFunction 자체를 가르킨다.
function arrowFunction() {
this.name = "화살표 함수";
return {
name: "일반함수",
call: () => {
console.log(this.name);
}
};
}
일반 함수의 경우는 생성자로 new키워드와 함께 생성하였기 때문에 내부에 this가 생성이 되게 된다.
그렇다면 왜 이렇게 동작하는가?
함수 vs 화살표 함수
이번에는 일반 함수와 화살표 함수의 두가지 다른 특징을 알아보자
1. 화살표 함수는 hoisting이 불가능하다.
-
자바스크립트는 다른 언어가 다르게 변수와 함수에 대해 정의하기 전에 실행이 가능하다.
-
자바스크립트에서는 아래의 코드가 실행 가능하다.
console.log(func()) function func() { return 100 }
- 분명 실행 단계 `console.log(func())가 정의되기 전임에도 불구하고 100을 리턴함을 알 수 있다.
- 자바스크립트는 아래와 같이 자체적으로 정의부분을 실행 코드 위로 올려준다
function func() {
return 100
}
console.log(func())
- 이것을 호이스팅(hoisting) 이라고 부른다.
- hoistf라는 단어는 줄이나 다른 도구로 물체를 올려준다는 뜻을 가지고 있다.
- 즉, 정의 코드을 실행 코드 위로 올려준다는 뜻이다.
Hoisting is a JavaScript mechanism where variables and function declarations are moved to the top of their scope before code execution.
Understanding Hoisting in JavaScript
더 간단하게 보면 아래와 같다
console.log(x)
var x = 100;
- 이 코드는 error를 출력하는 것이 아니라 undefined를 출력한다
- 내부적으로 아래와 같이 동작하기 때문이다.
var x;
console.log(x)
x = 100;
- 호이스팅이 적용되어 정의된 부분이 실행코드 위로 올라가게 되고 에러 대신 undefined를 호출하게 된다.
그럼 hoisting이 화살표 함수와 무슨 관련이 있을까
아래의 코드를 보자
console.log(x())
var x = () =>{
return 100;
}
위의 코드는 에러를 낼 것이다. 방금까지만 해도 호이스팅해서 undefined를 호출할 것이라 하지 않았는가?
사실 화살표 함수는 호이스팅이 불가능하다. 호출 시, 함수를 참조값 (x)를 호출한 것이 아니라 함수를 출력 시켰다(x()). console.log(x)로 예상한 대로 undefined가 출력되었을 것이다. 화살표함수의 x는 undefined가 되지만 함수로 초기화가 되지 않기 때문이다. 만약 x가 function x() 로 정의가 되었다면 함수로 초기화가 되면서 실행이 되었을 것이다.
2. 화살표 함수는 부모의 this를 받아온다.
- 자바스크립트에서 일반 함수를 사용할 때 binding이라는 단어를 들어보았을 것이다.
- 화살표 함수는 binding이 필요없다
- 바로 부모의 this를 받아올 수 있기 때문이다.
아래의 코드를 보자
function startGame() {
this.live = 0;
console.log(this);
this.addLives = function() {
console.log(this);
this.oneUp = setTimeout(function() {
console.log(this);
}, 1000);
};
}
var game = new startGame();
game.addLives();
위의 코드에서 this는 window 객체를 의미한다. setTimeout은 global scope에 정의 되어 있기 때문에 함수의 this를 사용할 수 없다. 그래서 아래와 같이 함수내의 this를 setTimeout 함수안으로 묶어줘야한다 (binding).
function startGame() {
this.live = 0;
var that = this
this.addLives = function() {
this.oneUp = setTimeout(function() {
console.log(that);
}, 1000);
};
}
var game = new startGame();
game.addLives();
그러면 함수의 this가 잘 binding 됨을 알 수 있다.
여기서 화살표 함수를 사용한다면 binding이 필요없다. startGame() 함수 내부 scope의 this를 내려받기 때문이다.
아래의 코드를 보자
function startGame() {
this.live = 0;
console.log(this)
this.addLives = () => {
console.log(this);
this.oneUp = setTimeout(() => {
console.log(this);
}, 1000);
};
}
var game = new startGame();
game.addLives();
addLives 함수가 먼저 startGame()가 가진 this를 끌어오고, addLives가 끌어온 부모의 this를 oneUp이 사용하는 것을 볼 수 있다.
정리
자바스크립트의 일반함수와 화살표 함수는 일반적으로 혼용이 가능하다.
특히, 화살표 함수는 아래와 같은 특성들을 가진다.
- 화살표 함수는 호이스팅 될 수 없다.
- 화살표 함수는 부모의 this를 바인딩 할 수 있다.
위와 같은 특성 때문에 아래와 같은 상황에는 사용을 하면 안된다.
- 객체의 메소드에서 사용 시
- 콜백 함수에서 컨텍스트가 계속 변할 때
위의 경우를 제외하고는 콜백함수를 쓴다면 코드를 경제적으로 사용할 수 있을 것이다.
참조