JavaScript (JS)에는 함수를 정의하는 다양한 방식이 있습니다. ES6 이후로 등장한 화살표 함수(arrow function)는 그 전의 function
키워드로 정의하는 방식과는 몇 가지 중요한 차이점을 지니고 있습니다.
function
사용법ES6 이전에는 함수를 정의하기 위해 function
키워드를 사용했습니다. function
키워드로 생성한 함수는 인스턴스를 생성하며 내부에 Function.prototype을 빈 객체로 생성합니다.
function foo1() {
console.log(arguments);
}
const foo2 = () => {
console.log(arguments);
};
console.log(foo1.prototype);
// {constructor: ƒ}
console.log(foo2.prototype);
// undefined
또한 function을 생성자 함수로 사용해 새로운 객체를 생성할 수도 있습니다. 이 경우 호출 시 앞에 new 를 붙이지 않으면 일반 함수로 작동합니다.
function Person(name) {
this.name = name;
this.sayHello = function () {
console.log(`${this.name} says 'Hello World!'`);
};
}
const person1 = new Person("AA");
const person2 = new Person("Ben Brode");
const person3 = Person("CC");
person1.sayHello(); // AA says 'Hello World!'
person2.sayHello(); // Ben Brode says 'Hello World!'
person3.sayHello(); // Uncaught TypeError TypeError: Cannot read properties of undefined (reading 'sayHello')
VSCode에서는 위와 같은 suggestion이 뜹니다. Quick Fix를 통해 친절하게 ES6부터 등장한 Class 생성자로 변환할 수 있습니다.
class Person {
constructor(name) {
this.name = name;
this.sayHello = function () {
console.log(`${this.name} says 'Hello World!'`);
};
}
}
일반 함수의 this는 함수 호출 위치에 따라 달라집니다.
var foo = function () {
console.dir(this);
};
// 1. 함수 호출
foo(); // window
// window.foo();
// 2. 메소드 호출
var obj = { foo: foo };
obj.foo(); // obj
// 3. 생성자 함수 호출
var instance = new foo(); // instance
// 4. apply/call/bind 호출
var bar = { name: 'bar' };
foo.call(bar); // bar
foo.apply(bar); // bar
foo.bind(bar)(); // bar
이에 따라 콜백 함수에서 this가 의도치 않게 바인딩될 수도 있습니다.
const obj1 = {
name: 'ObjectWithoutCalloutFunction',
showName: function() {
console.log(this.name);
}
};
obj1.showName(); // 'ObjectWithoutCalloutFunction',
const obj2 = {
name: 'ObjectWithCalloutFunction',
showName: function() {
setTimeout(function() {
console.log(this.name);
}, 1000);
}
};
obj2.showName(); // undefined(Timeout 함수의 this.name)
위의 예시의 경우, this는 setTimeout 함수의 스코프에 속한 this를 사용합니다.
이렇게 유동적인 this 문제점을 해결하기 위해 bind 메소드를 사용할 수 있습니다. 또는 var self = this 와 같이 추가 변수에 의도하는 this를 할당을 해도 됩니다.
// Solution 1 : this 바인딩
const obj3 = {
name: "ObjectBinded",
showName: function () {
setTimeout(
function () {
console.log(this.name);
}.bind(this),
1000
);
},
};
// Solution 2 : self = this
const obj4 = {
name: "ObjectSelf",
showName: function () {
const self = this;
setTimeout(function () {
console.log(self.name);
}, 1000);
},
};
ES6에서 새롭게 추가된 화살표 함수는 이러한 문제를 해결하기 위해 등장했습니다. 화살표 함수는 this
바인딩을 하지 않고 언제나 상위 스코프의 this
를 참조합니다. 이를 lexical this라고 합니다.
const obj4 = {
name: "ObjectWithArrowFunction",
showName: function () {
setTimeout(() => {
console.log(this.name);
}, 1000);
},
};
생성될 때의 상위 스코프의 this
를 그대로 사용하므로, this
바인딩 문제를 신경 쓸 필요가 없습니다.
다만 이벤트 리스너의 콜백 함수에 화살표 함수를 전달하는 경우 this가 언제나 상위 객체인 window를 호출함에 주의합시다.
// HTML 요소 생성
const button = document.getElementById('button');
button.textContent = '버튼';
// 화살표 함수를 이벤트 핸들러로 등록
button.addEventListener('click', () => {
console.log(this); // this는 window 객체를 가리킴
console.log(this.textContent); // this는 window 객체를 가리키므로 undefined (textContent는 window 객체에 없음)
});
간결한 문법: 화살표 함수는 간결한 문법을 제공합니다. 특히 단일 표현식을 반환하는 경우 중괄호와 return
키워드도 생략할 수 있습니다.
const add = (a, b) => a + b;
암묵적 반환: 중괄호가 없는 경우 표현식의 결과가 자동으로 반환됩니다.
const square = x => x * x;
this
바인딩: 화살표 함수는 고유의 this
를 가지지 않기 때문에 언제나 상위 스코프의 this
를 반환합니다.