ES6 이전 - 사용 목적에 따라 함수가 명확히 구분되지 않음. (일반 함수 & 생성자 함수로서 호출 가능. 즉, callable 이면서 constructor)
함수가 명확히 구분되지 않다는 것은 성능 상 문제가 있음. 객체에 바인딩된 함수가 constructor
이라는 것은 불필요한 prototype
객체를 생성하기 때문.
ES6 - 사용 목적에 따라 함수를 세 가지 종류로 명확히 구분.
constructor
, prototype
프로퍼티 존재, arguments
가짐.)non-constructor
)function
키워드 대신 화살표(=>
)를 사용하여 정의된 함수. (non-constructor
)// ES6 이전
var foo = function () {
return 1;
}
// 일반 함수로서 호출
foo(); // 1
// 생성자 함수로서 호출
new foo(); // foo {}
// 메서드로서 호출
var obj = { foo: foo }
obj.foo; // 1
non-constructor
. (prototype
프로퍼티 없고, 프로토타입도 생성하지 않음)non-constructor
[[HomeObject]]
를 가짐super
키워드를 사용할 수 있음super
를 추가하고 의미적으로 맞지 않는 constructor
를 제거const obj = {
x: 1,
foo() { return this.x; }, // foo는 메서드 (ES6)
bar: function() { return this.x; } // 메서드가 아닌 일반 함수
};
obj.foo(); // 1
obj.bar(); // 1
new obj.foo(); // TypeError: obj.foo is not a constructor (생성자 함수로서 호출 불가)
new obj.bar(); // bar {}
obj.foo.hasOwnProperty('prototype'); // false (constructor가 아닌 메서드라 prototype 프로퍼티가 없음)
obj.bar.hasOwnProperty('prototype'); // true
// 표준 빌드인 객체
String.prototype.toUpperCase.prototype; // undefined
Array.prototype.map.prototype; // undefined
const base = {
name: 'Lee',
sayHi() { return `hi, i'm ${this.name}.` }
};
const derived = {
__proto__: base,
// super는 sayHi의 [[HomeObject]]의 프로토타입인 base.prototype을 가리킴
sayHi() { return `${super.sayHi()}. how u doing? } // hi i'm Lee. how u doing?
};
derived.sayHi(); // hi i'm Lee. how u doing?
function
키워드 대신 화살표(=>
)를 사용하여 기존의 함수 정의 방식보다 간략하게 정의한 함수. 콜백 함수 내부에서this
가 전역 객체를 가리키는 문제를 해결하기 위한 대안으로 유용함.
// 1) 함수 정의 - 함수 선언문으로 정의할 수 없고, 함수 표현식으로 정의해야 함
const multiply = (x, y) => x * y;
multiply(2, 3); // 6
// 2) 매개변수 선언
const arrow = (x, y) => {};
const arrow = x => {};
const arrow = () => {};
// 3) 함수 몸체 정의
const power = x => { return x ** 2; };
const arrow = () => { return const x = 1; };
const create = (id, create) => { return { id, content }; };
// 함수 몸체가 하나로 구성되는 표현식인 문일 때, {} 생략 가능하며 자동 반환됨
const power = x => x ** 2;
const arrow = () => const x = 1; // SyntaxError: Unexpected token 'const'
const create = (id, content) => ({ id, content }); // 객체 반환하는 경우는 소괄호로 감싸주기
create(1, 'js'); // { id: 1, content: 'js' }
// 즉시 실행 함수로 사용
const person = (name => ({
sayHi() { return `hi. my name is ${name}.`; }
}))('Lee');
console.log(person.sayHi()); // hi. my name is Lee.
// 고차함수에 인수로 전달 가능
[1, 2, 3].map(function (v) { // ES5
return v * 2;
});
[1, 2, 3].map((v) => v * 2); // ES6
// 1) 화살표 함수는 인스턴스를 생성할 수 없는 non-constructor.
const foo = () => {};
// 생성자 함수로서 호출 불가
new Foo(); // TypeError: Foo is not a constructor
// prototype 프로퍼티가 없고 프로토타입도 생성하지 않음
Foo.hasOwnProperty('prototype'); // false
-------------------------------------------------------------------------------
// 2) 함수 자체의 this, arguments, super, new.target 바인딩을 갖지 않음.
// 화살표 함수 내에서 위의 키워드들을 참조한다면, 상위 스코프의 것을 참조.
-------------------------------------------------------------------------------
// 3) this
// 함수를 호출할 때, 함수가 어떻게 호출되었는지에 따라 this에 바인딩할 객체가 동적으로 결정됨.
class Prefixer {
constructor(prefix) {
this.prefix = prefix;
}
add(arr) {
return arr.map(function (item) {
// 여기서 콜백함수를 일반함수로서 호출하는데, map은 strict mode가 암묵적으로 적용되어 this는 undefined가 바인딩됨.
return this.prefix + item; // TypeError: cannot read prroperty 'prefix' of undefined
});
}
}
const prefixer = new Prefixer('-');
console.log(prefixer.add(['first', 'second']));
// ES6 이전 해결책
add(arr) {
const that = this; // this를 일단 회피시킴
return arr.map(function (item) {
return that.prefix + item; // this 대신 that을 참조
})
}
// ES5 해결책
add(arr) {
return arr.map(function (item) {
return this.prefix + item;
}, this); // this에 바인딩된 값이 콜백 함수 내부의 this에 바인딩됨
}
// Function.prototype.bind 메서드 사용
add(arr) {
return arr.map(function (item) {
return this.prefix + item;
}.bind(this)); // this에 바인딩된 값이 콜백함수 내부의 this에 바인딩됨
}
// * 화살표 함수로 해결 (함수 자체의 this 바인딩을 갖지 않음. 따라서 상위 스코프의 this를 그대로 참조 = lexical this)
add(arr) {
return arr.map((item) => this.prefix + item);
}
// 화살표 함수를 제외한 모든 함수에는 this 바인딩이 반드시 존재.
// 만약 화살표 함수가 전역 함수라면, 화살표 함수의 this는 전역 객체를 가리킴.
const foo = () => console.log(this);
foo(); // window
// 화살표 함수가 this 바인딩을 갖지 않기 때문에 call, apply, bind를 호출할 수 없다는 의미가 아니라 이를 사용해 this를 교체할 수 없고, 언제나 상위 스코프의 this 바인딩을 참조.
const person {
name: 'Lee',
// 따라서 메서드를 화살표 함수로 정의하는 것은 피해야 한다.
sayHi: () => console.log(`Hi ${this.name}`)
// 축약 표현 메서드를 활용하는 것이 좋다. (클래스 필드에서도)
sayHi() { console.log(`Hi ${this.name}`) }
};
person.sayHi(); // Hi
-------------------------------------------------------------------------------
// 4) super
// 화살표 함수는 함수 자체의 super 바인딩을 갖지 않음 (화살표 함수 내부에서 super를 참조하면 상위 스코프의 super를 참조)
-------------------------------------------------------------------------------
// 5) arguments
// 화살표 함수는 함수 자체의 arguments 바인딩을 갖지 않음 (상위 스코프의 arguments를 참조)
// 상위 스코프의 arguments 객체를 참조할 수는 있지만 상위 함수에게 전달된 인수 목록을 참조하므로 그닥 도움되지 않음. (=> Rest 파라미터를 사용하기)
함수에 전달된 인수들의 목록을 배열로 전달받는다.
매겨변수 이름 앞에 세개의 점...
을 붙여서 정의한 매개변수.
function foo(...rest) {
console.log(rest);
}
foo(1, 2, 3); // [1, 2, 3]
// 일반 매개변수와 함께 사용
function foo(param, ...rest) { // 선언된 매개변수에 할당된 인수를 제외한 나머지 인수들로 배열된 구성이므로, 반드시 마지막 파라미터여야 하며, 단 하나만 선언 가능
console.log(param, rest);
}
foo(1, 2, 3); // 1, [2, 3]
foo.length; // 1 (함수 객체의 length 프로퍼티에 영향을 주지 않음)
// ES6에서는 rest 파라미터를 이용해 가변 인자 함수의 인수 목록을 배열로 직접 전달 받을 수 있다.
function sum(...args) {
return args.reduce((pre, cur) => pre + cur, 0);
}
console.log(sum(1, 2, 3)); // 6
기존에 함수 내에서 수행하던 인수 체크 및 초기화를 간소화할 수 있다.
매개변수 기본값은 매개변수에 인수를 전달하지 않은 경우와 undefined를 전달한 경우에만 유효.
function sum(x, y) {
x = x || 0;
y = y || 0;
return x + y;
}
// ES6
function sum(x = 0, y = 0) {
return x + y;
}