JavaScript에는 다양한 함수의 표현식이 존재한다. 일반적인 함수의 표현식, 화살표 함수, 객체 안에서의 함수, 클래스 안에서의 함수 등 다양한 함수의 표현식이 존재한다. 평소에 표현식에 따른 차이가 존재한다고 알고 있기는 했지만, 이번 기회에 정확히 표현식에 따른 차이점을 정리해보려고 한다.
ES6 이전까지 자바스크립트의 함수는 별다른 구분 없이 다양한 목적으로 사용되었다.
자바스크립트의 함수의 함수는 다음 세가지로 호출할 수 있다.
const a = function() {
return 'a';
};
// 일반적인 함수
a();
// 생성자 함수로서
new a();
// 메서드
const obj = { a: a };
obj.a();
위의 코드처럼 ES6 이전의 모든 함수는 일반적인 함수로서 호출할 수 있는 것은 물론 생성자 함수로서도 호출할 수 있었다. 즉, 모든 함수는 callable
이면서 constructor
이다.
이로 인해, 함수의 호출 방식에 특별한 제약이 없고 생성자 함수로 호출하지 않아도 프로토타입 객체를 생성한다.
이를 해결하기 위헤 ES6에서는 함수를 사용 목적에 따라 다음의 세 가지로 구분했다.
ES6의 함수 | constructor | prototype | super | arguments |
---|---|---|---|---|
일반 함수 | O | O | X | O |
메서드 | X | X | O | O |
화살표 함수 | X | X | X | X |
ES6에서는 축약 표현으로 정의된 함수
를 의미한다.
const obj = {
a: 1,
foo() {
return this.a;
},
bar: function() { return this.x; }
}
위의 코드에서 축약된 표현으로 정의된 foo()
는 메서드, 그렇지 않은 bar()
는 일반 함수이다.
ES6에서 정의한 메서드는 인스턴스를 생성할 수 없는 non-constructor이다. 인스턴스를 생성할 수 없으므로, prototype 프로퍼티가 없고, 프로토타입도 생성하지 않는다.
obj.foo.hasOwnProperty('prototype'); // false
obj.bar.hasOwnProperty('prototype'); // true
ES6에서의 메서드는 자신을 바인딩한 객체를 가리키는 내부 슬롯 [[HomeObject]]을 갖는다. 따라서, super
키워드를 사용할 수 있다.
super 참조는 내부 슬롯 [[HomeObject]]를 사용하여 상위 클래스의 메서드를 참조한다.
화살표 함수(arrow function)
1. 화살표 함수는 인스턴스를 생성할 수 없는 non-constructor이다.
const arror() = () => {};
new arror(); // TypeError: fun is not a constructor
arror.hasOwnProperty('prototype'); // false
위의 코드에서 확인할 수 있듯이 화살표 함수는 인스턴스를 생성할 수 없으므로 prototype 프로퍼티가 없고 프로토타입도 생성하지 않는다.
2. 중복된 매개변수 이름을 사용할 수 없다.
// SyntaxError: Duplicate parameter name not allowed in this context
const arrow = (a, a) => a + a;
일반 함수와는 달리 화살표 함수에서는 중복된 매개변수 이름을 사용하면 에러가 발생한다.
3. 함수 자체의 this, arguments, super, new.target 바인딩을 갖지 않는다.
화살표 함수와 일반 함수가 가장 구별되는 특징은 바로 this
이다.
화살표 함수의 this는 일반 함수의 this와 다르게 동작하는데, 이것은 콜백 함수 내부의 this와 외부 함수의 this가 다르다는 문제를 해결한다.
this바인딩은 함수의 호출 방식, 즉 함수가 어떻게 호출되었는지에 따라 동적으로 결정된다.
먼저, 아래의 코드를 살펴보자.
const value = 1;
const obj = {
value: 100,
foo() {
console.log('this: ', this); // { value: 100, foo: f }
setTimeout(function () {
console.log('callback's this: ', this); // windw
console.log('callback's this.value: ', this.value); // 1
}, 100);
}
}
obj.foo();
위의 코드에서 setTimeout의 콜백함수로 일반 함수가 호출되었다. 이 경우 콜백 함수 내부에는 전역 객체가 바인딩된다.
이처럼 일반 함수로 호출된 함수 내부의 this에는 전역 객체가 바인딩 된다.
중첩 함수 또는 콜백 함수는 보통 외부 함수를 돕는 헬퍼 함수의 역할을 주로 한다. 하지만 위의 예시처럼 this에 전역 객체가 바인딩된다면 헬퍼 함수로서 동작하기가 힘들다.
따라서, 중첩 함수나 콜백 함수의 this 바인딩과 상위 메서드의 this 바인딩을 일치시켜야 한다.
this 바인딩을 일치시키기 위한 방법은 다음과 같다.
1. this 바인딩을 위한 변수 선언
const value = 1;
const obj = {
value: 100,
foo() {
// this 바인딩을 that에 할당한다.
const that = this;
// 콜백 함수에서 this 대신 that을 참조한다.
setTimeout(function () {
console.log(that.value); // 100
}, 100);
}
}
obj.foo();
2. 메서드 활용
JavasScript에서는 this를 명시적으로 바인딩할 수 있는 apply
, call
, bind
메서드를 제공한다.
const value = 1;
const obj = {
value: 100,
foo() {
// 콜백 함수에 this를 바인딩한다.
setTimeout(function () {
console.log(this.value); // 100
}.bind(this), 100);
}
};
obj.foo();
3. 화살표 함수
const value = 1;
const obj = {
value: 100,
foo() {
// 화살표 함수 내부의 this는 상위 스코프의 this를 가리킨다.
setTimeout(() => { console.log(this.value), 100); // 100
}
};
obj.foo();
화살표 함수는 함수 자체의 this 바인딩을 갖지않고, 상위 스코프의 this를 참조한다. =lexical scope
이처럼, this 바인딩의 문제를 해결하기 위해 화살표 함수를 사용하면 가장 간단히 문제를 해결할 수 있다.
하지만 화살표 함수가 상위 스코프의 this를 참조한다는 점이 문제를 일으킬 때도 있다. 아래의 경우를 보자.
const obj = {
value: 100,
fun: () => console.log(this.value), // undefined
};
obj.fun();
위의 코드의 경우 fun 프로퍼티에 할당된 화살표 함수 내부의 this는 메서드를 호출한 객체인 obj를 가리키지 않고 상위 스코프인 전역의 this가 가리키는 전역 객체를 가리키게 된다.
따라서 화살표 함수로 메서드를 정의하는 것은 바람직하지않다.
메서드를 정의할 때는 아래의 경우처럼 ES6 메서드 축약 표현으로 정의한 ES6 메서드를 사용하는 것이 좋다.
const obj = {
value: 100,
fun() {
console.log(this.value), // 100
}
};
this와 마찬가지로 super, arguments 키워드 또한 함수 자체의 super, arguments 바인딩도 갖지 않고, 상위 스코프의 super, arguments를 참조한다.