일반함수와 화살표함수의 차이점을 설명할 때는 'this'라는 것을 빼먹으면 안된다. 하지만 좀 막연하게 알고 있다는 생각에 다시 모던자바스크립트 책과 여러 정보들을 천천히 정독했고, 이를 기반으로 블로그에 정리해 보려고 한다.
//일반함수
function foo() {
};
//화살표 함수
const foo = () => {
};
화살표 함수는 function 키워드 대신 화살표(=>)를 사용하여 보다 간결하게 함수를 선언할 수 있다.
하지만 단순히 함수를 짧게 쓰기 위한 용도로만 사용되지 않고, 몇 가지 독특하고 유용한 기능을 제공한다.
const Foo = () => {};
new Foo(); //TypeError: Foo is not a constructor
const Foo = () => {};
Foo.hasOwnProperty('prototype'); //False
'use strict'
function normal(a, a) { return a + a; }
//SyntaxError: Duplicate parameter name not allowed in this context
const arrow = (a, a) => a + a;
//SyntaxError: Duplicate parameter name not allowed in this context
자바스크립트의 경우 함수 호출 방식에 의해 this에 바인딩될 값이 동적으로 결정된다.
함수를 선언할 때 this에 바인딩할 객체가 정적으로 결정되는 것이 아니고, 함수를 호출할 때 함수가 어떻게 호출되었는지에 따라 this에 바인딩할 객체가 동적으로 결정된다.
함수 실행시에는 전역(window) 객체를 가리킨다.
메소드 실행시에는 메소드를 소유하고 있는 객체를 가리킨다.
생성자 실행시에는 새롭게 만들어진 객체를 가리킨다.
function Prefixer(prefix) {
this.prefix = prefix;
}
Prefixer.prototype.prefixArray = function (arr) {
// (A)
return arr.map(function (x) {
return this.prefix + ' ' + x; // (B)
});
};
var pre = new Prefixer('Hi');
console.log(pre.prefixArray(['Lee', 'Kim']));
(A) 지점에서의 this는 생성자 함수 Prefixer가 생성한 객체, 즉 생성자 함수의 인스턴스(위 예제의 경우 pre)이다.
(B) 지점에서 사용한 this는 아마도 생성자 함수 Prefixer가 생성한 객체(위 예제의 경우 pre)일 것으로 기대하였겠지만, 이곳에서 this는 전역 객체 window를 가리킨다. 이는 생성자 함수와 객체의 메소드를 제외한 모든 함수(내부 함수, 콜백 함수 포함) 내부의 this는 전역 객체를 가리키기 때문이다.
function Prefixer(prefix) {
this.prefix = prefix;
}
Prefixer.prototype.prefixArray = function (arr) {
// this는 상위 스코프인 prefixArray 메소드 내의 this를 가리킨다.
return arr.map(x => `${this.prefix} ${x}`);
};
const pre = new Prefixer('Hi');
console.log(pre.prefixArray(['Lee', 'Kim']));
window.x = 1;
const normal = function () { return this.x; };
const arrow = () => this.x;
console.log(normal.call({ x: 10 })); // 10
console.log(arrow.call({ x: 10 })); // 1
내가 생각하는 가장 중요한 포인트
- 화살표 함수는 콜백함수 내부에서 this가 전역 객체를 가리키는 문제를 해결하기 위한 대안이다.
- 콜백 함수 내부의 this가 외부 함수의 this와 다르기 때문에 발생하는 문제를 해결하기 위해 설계된 것
// Bad
const person = {
name: 'Lee',
sayHi: () => console.log(`Hi ${this.name}`)
};
person.sayHi(); // Hi undefined
메소드로 정의한 화살표 함수 내부의 this는 메소드를 소유한 객체, 즉 메소드를 호출한 객체를 가리키지 않고 상위 컨택스트인 전역 객체 window를 가리킨다. 그러므로 화살표 함수로 메소드를 정의하는 것은 피해야 한다.
// Good
const person = {
name: 'Lee',
sayHi() { // === sayHi: function() {
console.log(`Hi ${this.name}`);
}
};
person.sayHi(); // Hi Lee
메소드를 위한 단축 표기법인 ES6의 축약 메소드 표현을 사용하면 해결이 가능하니 알아두자.
// Bad
const person = {
name: 'Lee',
};
Object.prototype.sayHi = () => console.log(`Hi ${this.name}`);
person.sayHi(); // Hi undefined
화살표 함수로 객체의 메소드를 정의하였을 때와 같은 문제가 발생한다. 따라서 prototype에 메소드를 할당하는 경우, 일반 함수를 할당한다.
// Good
const person = {
name: 'Lee',
};
Object.prototype.sayHi = function() {
console.log(`Hi ${this.name}`);
};
person.sayHi(); // Hi Lee
화살표 함수는 생성자 함수로 사용할 수 없다. 생성자 함수는 prototype 프로퍼티를 가지며 prototype 프로퍼티가 가리키는 프로토타입 객체의 constructor를 사용한다. 하지만 화살표 함수는 prototype 프로퍼티를 가지고 있지 않다.
const Foo = () => {};
// 화살표 함수는 prototype 프로퍼티가 없다
console.log(Foo.hasOwnProperty('prototype')); // false
const foo = new Foo(); // TypeError: Foo is not a constructor
addEventListener 함수의 콜백 함수를 화살표 함수로 정의하면 this가 상위 컨택스트인 전역 객체 window를 가리킨다.
// Bad
var button = document.getElementById('myButton');
button.addEventListener('click', () => {
console.log(this === window); // => true
this.innerHTML = 'Clicked button';
});
addEventListener 함수의 콜백 함수 내에서 this를 사용하는 경우, function 키워드로 정의한 일반 함수를 사용하여야 한다. 일반 함수로 정의된 addEventListener 함수의 콜백 함수 내부의 this는 이벤트 리스너에 바인딩된 요소(currentTarget)를 가리킨다.
// Good
var button = document.getElementById('myButton');
button.addEventListener('click', function() {
console.log(this === button); // => true
this.innerHTML = 'Clicked button';
});
일반함수와 화살표 함수의 차이점에 대해 공부를 하다 보면 this에 대해 꽤 depth있는 이해가 필요하다.
다음 포스팅에는 this에 대해 정리해 보도록 하겠다.
참고자료
https://poiemaweb.com/es6-arrow-function
https://ko.javascript.info/arrow-functions#ref-268
모던 자바스크립트 서적