
ES6 이전의 모든 함수는 사용 목적에 따라 명확히 구분되지 않았습니다. 즉 일반 함수로서 호출할 수 있는 것은 물론 생성자 함수로서 호출할 수 있습니다.
var foo = function() {
return 1;
};
// 일반 함수 호출
foo(); // 1
// 생성자 함수로 호출
new foo(); // foo {}
// 메서드로 호출
var obj = { foo: foo }
obj.foo(); // 1
주의해야할 것은 객체에 바인딩된 함수도 일반 함수 호출, 생성자 함수로서 호출도 문법상 가능하다는 점입니다. 이는 불필요한 프로토파입 객체를 생성하며 실수를 유발할 가능성이 있고 성능에도 좋지 않습니다.
이러한 문제를 해결하기 위해 ES6에서는 함수를 사용 목적에 따라 아래의 세 가지 종류로 명확히 구분했습니다.
ES6 사양에서 메서드는 메서드 축약 표현으로 정의된 함수만을 의미합니다. 또한 인스턴스를 생성할 수 없는 non-constructor입니다.
const obj = {
x: 1,
// foo는 메서드
foo() { return this.x },
// bar에 바인딩된 함수는 메서드가 아닌 일반 함수
bar: function() { return this.x }
};
console.log(obj.foo()); // 1
console.log(obj.bar()); // 1
new obj.foo(); // TypeError 📌 메서드는 non-constructor
new obj.bar(); // bar {}
obj.foo.hasOwnProperty('prototype'); // false prototype 프로퍼티가 없다.
obj.foo.hasOwnProperty('prototype'); // true
ES6 메서드는 자신을 바인딩한 객체를 가리키는 내부 슬롯[[HomeObject]]를 갖습니다. 따라서 super 키워드를 사용할 수 있습니다.
const base = {
name: 'lee',
sayHi() {
return `Hi ${this.name}`;
}
};
const derived = {
__proto__: base,
// sayHi는 메서드, [[HomeObject]]를 갖는다
// sayHidml [[HomeObject]]는 sayHi가 바인딩된 객체인 derived를 가리킴
// super는 sayHi의 [[HomeObject]]의 프로토타입인 base를 가리킴
sayHi() {
return `${super.sayHi()}. how are you?`;
}
};
console.log(derived.sayHi()); // Hi lee. how are you?
// 📌 메서드가 아닌경우 //
const derived = {
__proto__: base,
sayHi: function() { // 메서드가 아니므로 [[HomeObject]]를 갖지 않음
return `${super.sayHi()}. how are you?`; // SyntaxError
}
};
메서드를 정의할 때 프로퍼티 값으로 익명 함수 표현식을 할당하는 방식은 사용하지 않는 것이 좋습니다.
화살표 함수는 function 키워드 대신 화살표를 사용하여 기존의 함수 정의 방식보다 간략하게 정의할 수 있습니다. 특히 콜백 함수 내부에서 this가 전역 객체를 가리키는 문제를 해결하기 위한 대안으로 유용합니다.
const multiply = (x, y) => x * y;
multiply(2, 3); // 6
const arrow = (x, y) => {...}; // 매개변수가 여러 개인 경우
const arrow = x => {...}; // 매개변수가 한 개인 경우
const arrow = () => {...}; // 매개변수가 없는 경우 소괄호 생략 불가
//📌 함수 몸체가 하나의 문으로 구성된 경우 중괄호 생략 가능
const power = x => x ** 2
power(2); // 4
//📌 함수 몸체 내부의 문이 표현식이 아닌 문이라면 에러 발생
const arrow = () => const x = 1; // SyntaxError
const arrow = () => { const x = 1 };
//📌 객체 리터럴을 반환하는 경우 소괄호로 감싸 주어야 함
const create = (id, content) => ({ id, content });
create(1, 'Javascript') // {id: 1, content: "Javascript"}
//📌 함수 몸체가 여러 개의 문으로 구성된다면 중괄호 생략 불가, 반환값 명시적으로 반환
const sum = (a, b) => {
const result = a + b;
return result;
};
//📌 화살표 함수도 즉시 실행 함수 사용가능
const person = (name => ({
sayHi() { return `Hi my name is ${name}`; }
}))('lee');
console.log(person.sayHi()); // Hi my name is lee
//📌 고차 함수에 인수로 전달할 수 있음
[1, 2, 3].map(v => v * 2); // [2, 4, 6]
const Foo = () => {};
new Foo(); // TypeError
Foo.hasOwnProperty('prototype'); // false
const arrow = (a, a) => a + a; // SyntaxError
화살표 함수의 this는 일반 함수의 this와 다르게 동작하며 이로 인해 화살표 함수는 다른 함수의 인수로 전달되어 콜백 함수로 사용되는 경우가 많습니다.
이때 주의할 것은 일반 함수로서 호출되는 콜백 함수의 경우입니다.
class Prefixer {
constructor(prefix) {
this.prefix = prefix;
}
//add 메서드는 인수로 전달된 배열 arr을 순회하며 배열의 모든 요소에 prefix를 추가
add(arr) {
return arr.map(function (item) {
return this.prefix + item; // TypeError: cannot read 'prefix'
});
}
}
const prefixer = new Prefixer('-webkit-');
console.log(prefixer.add(['transition', 'user-select']));
위의 예제에서 Array.prototype.map 메서드가 콜백 함수를 일반 함수로 호출하기 때문에 this는 undefined를 가리키고 외부 함수 add의 this가 서로 다른 값을 가리키고 있기 때문에 TypeError가 발생합니다.
class Prefixer {
constructor(prefix) {
this.prefix = prefix;
}
// 📌 화살표 함수로 this 문제 해결
add(arr) {
return arr.map(item => this.prefix + item);
}
}
const prefixer = new Prefixer('-webkit-');
console.log(prefixer.add(['transition', 'user-select']));
// ['-webkit-transition', '-webkit-user-select']
화살표 함수는 함수 자체의 this 바인딩을 갖지 않습니다. 따라서 화살표 함수 내부에서 this를 참조하면 상위 스코프의 this를 그대로 참조합니다. 이를 lexical this라 합니다.
const counter = {
num: 1,
// 📌 메서드를 화살표 함수로 정의하는 것은 피해야 한다.
increase: () => ++this.num // 📌 this는 전역 객체를 가리킴
};
console.log(counter.increase()); // NaN
화살표 함수는 Function.prototype.call/apply/bind 메서드를 사용해도 화살표 함수 내부의 this를 교체할 수 없습니다.
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
메서드를 정의할 때나, 프로토파입 객체의 프로퍼티에 화살표 함수를 할당하면 원하는 객체를 가리키지 않고 상위 객체나 전역 객체를 가리키게 되므로 피해야 합니다.
화살표 함수는 함수 자체의 super 바인딩을 갖지 않습니다. this와 마찬가지로 상위 스코프의 super를 참조합니다.
class Base {
constructor(name) {
this.name = name;
}
sayHi() {
return `Hi ${this.name}`;
}
}
class Derived extends Base {
// 📌 화살표 함수의 super는 상위 스코프인 constructor의 super를 가리킨다.,
sayHi= () => `${super.sayHi()} how are you?`;
}
const derived = new Derived('lee');
console.log(derived.sayHi()); // Hi lee how are you?
화살표 함수는 함수 자체의 arguments 바인딩을 갖지 않습니다. this와 마찬가지로 상위 스코프의 arguments를 참조합니다.
(function () {
//📌화살표 함수 foo의 arguments는 상위 스코프인 즉시 실행 함수의 arguments를 가리킴
const foo = () => console.log(arguments);
// [Arguments] { '0': 1, '1': 2 }
foo(3, 4);
}(1, 2));
const foo = () => console.log(arguments);
// 📌 전역의 arguments를 가리킴, 전역에는 arguments 객체가 존재하지 않음
foo(1, 2); // ReferenceError
Rest 파라미터(나머지 매개변수)는 매개변수 이름 앞에 세개의 점 ...을 붙여서 정의한 매개변수를 의미합니다. Rest 파라미터는 함수에 전달된 인수의 목록을 배열로 전달받습니다.
function foo(...rest) {
console.log(rest); // [1, 2, 3, 4, 5]
}
foo(1, 2, 3, 4, 5);
// 📌 일반 매개변수와 함께 사용할 수 있다.
// 📌 Rest 파라미터는 반드시 마지막에 와야한다.
// 📌 여러개의 일반 매개변수와 *단 하나의 Rest 파라미터를 사용할 수 있다.
function foo(param, ...rest) {
console.log(param); // 1
console.log(rest); // [2, 3, 4, 5]
}
foo(1, 2, 3, 4, 5);
// 📌 함수 정의 시 선언한 매개변수 개수를 나타내는 length 프로퍼티에 영향을 주지 않는다.
function foo(...rest){}
console.log(foo.length); // 0
function baz(x, y, ...rest){}
console.log(baz.length); // 2
ES5에서는 유사 배열 객체로 반환해주는 arguments 객체를 활용하여 인수를 전달받았습니다. 하지만 배열 메서드를 사용하기 위해 Function.prototype.call 이나 apply 메서드를 사용해 배열로 변환해야 하는 번거로움이 있었습니다.
ES6에서는 Rest 파라미터를 사용해 인수 목록을 배열로 직접 전달받을 수 있었고 이로인해 배열로 변환하는 번거로움을 피할 수 있었습니다.
화살표 함수는 arguments 객체를 갖지 않으므로 반드시 Rest 파라미터를 사용해야 합니다.
함수를 호출할 때 매개변수의 개수만큼 인수를 전달하는 것이 바람직하지만 그렇지 않은 경우에도 에러가 발생하지 않습니다. 하지만 의도치 않은 결과가 나올 수 있으므로 매개변수에 기본값을 할당할 필요가 있습니다.
function sum(x, y) {
return x + y; // y = undefined
}
console.log(sum(1)); // NaN
function sum(x, y) {
x = x || 0;
y = y || 0;
return x + y;
}
// 위의 표현과 같다.
function sum(x = 0, y = 0) {
return x + y;
}
console.log(sum(1, 2))<; // 3
console.log(sum(1)); // 1
function foo(...rest = []) { // 📌 Rest 파라미터에는 기본값을 지정할 수 없다.
console.log(rest);
} // SyntaxError
//📌 매개변수 기본값은 함수 정의 시 선언한 매개변수 개수를 나타내는
//📌 함수 객체의 length 프로퍼티와 arguments 객체에 아무런 영향을 주지 않는다.
function sum(x, y = 0) {
console.log(arguments);
}
console.log(sum.length); // 1
sum(1); // Arguments { '0': 1 }
sum(1, 2); // Arguments { '0': 1, '1': 2 }