ES6 이전의 모든 함수는 일반 함수로서 호출할 수 있는 것을 물론 생성자 함수로서 호출할 수 있다.
var foo = function () {
return 1;
};
// 일반적인 함수로서 호출
foo(); // -> 1
// 생성자 함수로서 호출
new foo(); // -> foo {}
// 메서드로서 호출
var obj = { foo: foo };
obj.foo(); // -> 1
var foo = function () {
return 1;
};
// 일반적인 함수로서 호출
foo(); // -> 1
// 생성자 함수로서 호출
new foo(); // -> foo {}
// 메서드로서 호출
var obj = { foo: foo };
obj.foo(); // -> 1
이는 실수를 유발시킬 수도 있고, 성능 면에서도 손해이다.
객체에 바인딩된 함수도 일반 함수로서 호출할 수 있는 것은 물론 생성자 함수로서 호출할 수도 있다.
// 프로퍼티 f에 바인딩된 함수는 callable이며 constructor다.
var obj = {
x: 10,
f: function () {
return this.x;
},
};
// 프로퍼티 f에 바인딩된 함수를 메서드로서 호출
console.log(obj.f()); // 10
// 프로퍼티 f에 바인딩된 함수를 일반 함수로서 호출
var bar = obj.f;
console.log(bar()); // undefined
// 프로퍼티 f에 바인딩된 함수를 생성자 함수로서 호출
console.log(new obj.f()); // f {}
// 프로퍼티 f에 바인딩된 함수는 callable이며 constructor다.
var obj = {
x: 10,
f: function () {
return this.x;
},
};
// 프로퍼티 f에 바인딩된 함수를 메서드로서 호출
console.log(obj.f()); // 10
// 프로퍼티 f에 바인딩된 함수를 일반 함수로서 호출
var bar = obj.f;
console.log(bar()); // undefined
// 프로퍼티 f에 바인딩된 함수를 생성자 함수로서 호출
console.log(new obj.f()); // f {}
위와 같은 문제를 해결하기 위해 ES6에서는 함수를 사용 목적에 따라 세 가지 종류(일반 함수
, 메서드
, 화살표 함수
)로 명확히 구분했다.
🌟 메서드
ES6이전에는, 메서드는 일반적으로 객체에 바인딩된 함수를 일컫는 의미로 사용된다.
ES6이후에는 , 메서드는 메서드 축약 표현으로 정의된 함수만을 의미한다.
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
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
ES6 사양에서 메서드는 인스턴스를 생성할 수 없는 non-constructor
이다.
따라서, 생성자 함수로서 호출할 수 없다.
new obj.foo(); // -> TypeError: obj.foo is not a constructor
new obj.bar(); // -> bar {}
new obj.foo(); // -> TypeError: obj.foo is not a constructor
new obj.bar(); // -> bar {}
ES6 메서드는 자신을 바인딩한 객체를 가리키는 내부 슬롯 [[HomeObject]]
를 갖는다.
super 참조는 내부 슬롯 [[HomeObject]]
를 사용한다.
const base = {
name: "Lee",
sayHi() {
return `Hi! ${this.name}`;
},
};
const derived = {
__proto__: base,
// sayHi는 ES6 메서드다. ES6 메서드는 [[HomeObject]]를 갖는다.
// sayHi의 [[HomeObject]]는 sayHi가 바인딩된 객체인 derived를 가리키고
// super는 sayHi의 [[HomeObject]]의 프로토타입인 base를 가리킨다.
sayHi() {
return `${super.sayHi()}. how are you doing?`;
},
};
console.log(derived.sayHi()); // Hi! Lee. how are you doing?
const base = {
name: "Lee",
sayHi() {
return `Hi! ${this.name}`;
},
};
const derived = {
__proto__: base,
// sayHi는 ES6 메서드다. ES6 메서드는 [[HomeObject]]를 갖는다.
// sayHi의 [[HomeObject]]는 sayHi가 바인딩된 객체인 derived를 가리키고
// super는 sayHi의 [[HomeObject]]의 프로토타입인 base를 가리킨다.
sayHi() {
return `${super.sayHi()}. how are you doing?`;
},
};
console.log(derived.sayHi()); // Hi! Lee. how are you doing?
ES6 메서드가 아닌 함수는 super
키워드를 사용할 수 없다. 내부 슬롯 [[HomeObject]]
를 갖지 않기 때문이다.
const derived = {
__proto__: base,
// sayHi는 ES6 메서드가 아니다.
// 따라서 sayHi는 [[HomeObject]]를 갖지 않으므로 super 키워드를 사용할 수 없다.
sayHi: function () {
// SyntaxError: 'super' keyword unexpected here
return `${super.sayHi()}. how are you doing?`;
},
};
const derived = {
__proto__: base,
// sayHi는 ES6 메서드가 아니다.
// 따라서 sayHi는 [[HomeObject]]를 갖지 않으므로 super 키워드를 사용할 수 없다.
sayHi: function () {
// SyntaxError: 'super' keyword unexpected here
return `${super.sayHi()}. how are you doing?`;
},
};
🌟 화살표 함수
화살표 함수는 표현뿐만 아니라, 내부 동작도 기존의 함수보다 간략하다.
특히, 화살표 함수는 콜백 함수 내부에서 this
가 전역 객체를 가리키는 문제를 해결하기 위한 방안으로 유용하다.
화살표 함수와 일반 함수의 차이
-
화살표 함수는 인스턴스를 생성할 수 없는
non-constructor
이다. -
중복된 매게변수 이름을 선언할 수 없다.
-
화살표 함수는 함수 자체의
this
,arguments
,super
,new.target
바인딩을 갖지 않는다.
- 따라서 화살표 함수 내부에서,
this
,arguments
,super
,new.target
을 참조하면, 스코프 체인을 통해 상위 스코프의this
,arguments
,super
,new.target
을 참조한다.
⚙️ this
화살표 함수가 일반 함수와 구별되는 가장 큰 특징은 this
이다.
화살표 함수는 다른 함수의 인수로 전달되어 콜백 함수
로 사용되는 경우가 많다.
콜백 함수 내부의 this
문제 → 즉, 콜백 함수 내부의 this
와 외부 함수의 this
가 다르기 때문에 발생하는 문제를 해결하기 위해 설계되었다.
this
는 함수가 어떻게 호출되었는지에 따라 this
에 바인딩할 객체가 동적으로 결정된다. → 22장 this
이때 주의할 것이, 일반 함수로서 호출되는 콜백 함수의 경우다. 고차 함수의 인수로 전달되어 고차 함수 내부에서 호출되는 콜백 함수도 중첩 함수라 할 수 있다.
class Prefixer {
constructor(prefix) {
this.prefix = prefix;
}
add(arr) {
// add 메서드는 인수로 전달된 배열 arr을 순회하며 배열의 모든 요소에 prefix를 추가한다.
// ①
return arr.map(function (item) {
// ②
return this.prefix + item;
// -> TypeError: Cannot read property 'prefix' of undefined
});
}
}
const prefixer = new Prefixer("-webkit-");
console.log(prefixer.add(["transition", "user-select"]));
class Prefixer {
constructor(prefix) {
this.prefix = prefix;
}
add(arr) {
// add 메서드는 인수로 전달된 배열 arr을 순회하며 배열의 모든 요소에 prefix를 추가한다.
// ①
return arr.map(function (item) {
// ②
return this.prefix + item;
// -> TypeError: Cannot read property 'prefix' of undefined
});
}
}
const prefixer = new Prefixer("-webkit-");
console.log(prefixer.add(["transition", "user-select"]));
프로토타입 메서드 내부인 ①
에서는, this
는 메서드를 호출한 객체(prefixer)를 가리킨다.
그렇지만, ②
에서는, this
는 undefined
를 가리킨다. 이는 Array.prototype.map
메서드가 콜백 함수를 일반함수
로서 호출하기 때문이다.
일반 함수로서 호출되는 모든 함수 내부의 this
는 전역 객체를 가리킨다.
그렇지만, 클래스 내부의 모든 코드에는 strict mode
가 적용된다. 이에 따라, this에
는 전역 객체가 아닌, undefined
가 바인딩된다.
이를 해결하는 방법이 존재하지만, 복잡하고 직관적이지 않기 때문에, 화살표 함수가 등장했다.
class Prefixer {
constructor(prefix) {
this.prefix = prefix;
}
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']
class Prefixer {
constructor(prefix) {
this.prefix = prefix;
}
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
라고 한다.
화살표 함수는 this
바인딩을 갖지 않기 때문에, Function.prototype.call
, Function.prototype.apply
, Function.prototype.bind
메서드를 호출하 수 없는 것은 아니지만, 언제나 상위 스코프의 this
바인딩을 참조한다.
🌟 Rest 파라미터
Rest
파라미터는 매게변수 이름 앞에 새개의 점 ...
을 붙여서 정의한 매게변수를 의미한다.
Rest
파라미터는 함수에 전달된 인수들의 목록을 배열로 전달받는다.
function foo(...rest) {
// 매개변수 rest는 인수들의 목록을 배열로 전달받는 Rest 파라미터다.
console.log(rest);
// [ 1, 2, 3, 4, 5 ]
}
foo(1, 2, 3, 4, 5);
function foo(...rest) {
// 매개변수 rest는 인수들의 목록을 배열로 전달받는 Rest 파라미터다.
console.log(rest);
// [ 1, 2, 3, 4, 5 ]
}
foo(1, 2, 3, 4, 5);
일반 매게변수와 Rest
파라미터는 함께 사용할 수 있다.
function foo(param, ...rest) {
console.log(param); // 1
console.log(rest); // [ 2, 3, 4, 5 ]
}
foo(1, 2, 3, 4, 5);
function bar(param1, param2, ...rest) {
console.log(param1); // 1
console.log(param2); // 2
console.log(rest); // [ 3, 4, 5 ]
}
bar(1, 2, 3, 4, 5);
function foo(param, ...rest) {
console.log(param); // 1
console.log(rest); // [ 2, 3, 4, 5 ]
}
foo(1, 2, 3, 4, 5);
function bar(param1, param2, ...rest) {
console.log(param1); // 1
console.log(param2); // 2
console.log(rest); // [ 3, 4, 5 ]
}
bar(1, 2, 3, 4, 5);
이는 순차적으로 할당되기 때문에, Rest
파라미터는 항상 마지막 파라미터여야 한다.
🌟 매게 변수 기본값
ES6
에서는 매게변수 기본값을 사용해, 함수 내에서 수행하던 인수 체크 및 초기화를 간소화 할 수 있다.
function sum(x, y) {
// 인수가 전달되지 않아 매개변수의 값이 undefined인 경우 기본값을 할당한다.
x = x || 0;
y = y || 0;
return x + y;
}
console.log(sum(1, 2)); // 3
console.log(sum(1)); // 1
function sum(x = 0, y = 0) {
return x + y;
}
console.log(sum(1, 2)); // 3
console.log(sum(1)); // 1
function sum(x, y) {
// 인수가 전달되지 않아 매개변수의 값이 undefined인 경우 기본값을 할당한다.
x = x || 0;
y = y || 0;
return x + y;
}
console.log(sum(1, 2)); // 3
console.log(sum(1)); // 1
function sum(x = 0, y = 0) {
return x + y;
}
console.log(sum(1, 2)); // 3
console.log(sum(1)); // 1