26장 es6 함수의 추가 기능
함수의 구분
es6 이전
→ 동일한 함수여도 다양한 형태로 호출이 가능했다.
var foo = function () { return 1; }; foo(); // 일반적인 함수 호출 // 1 new foo(); //생성자 함수로서 호출 //foo {} var foo = {foo: foo}; obj.foo(); // 1
es6 이전의 함수는 사용목적에 따라 명확히 구분되지 않는다. 즉, es6이전의 모든 함수는 일반 함수로서 호출할 수 있는 것은 물론 생성자 함수로서 호출할 수 있다.
→ 모든 함수가 callable이면서 constructor 이다.
- es6 이전에 메서드라고 부르던 객체에 바인딩 된 함수도 callable이며 constructor여서 생성자 함수로 호출 가능
var = obj = { x: 10, f: function () {return this.x}; } //프로퍼티f에 바인딩된 함수를 메서드로서 호출 console.log(obj.f()) //10 //일반 함수로서 호출 var bar = obj.f; console.log(bar()); //undefined //생성자 함수로서 호출 console.log(new obj.f()) //f {}
이렇게 객체에 바인딩된 함수를 생성자 함수로 호출하는 것은 문법상으로도 성능 면에서도 문제가 있다.
객체에 바인딩된 함수가 constructor라는 것은 prototype프로퍼티를 가지며 프로토타입 객체도 생성한다는 것을 의미하기 때문!
함수에 전달되는 콜백함수도 constructor이기 때문에 프로토타입 객체를 생성한다.
이처럼 es6 이전의 함수는 호출 방식에 특별한 제약이 없어서 생성자 함수로 호출하지 않아도 프로토타입 객체를 생성한다.
→ 이러한 문제를 해결하기 위해 es6에서는 함수를 사용 목적에 따라 구분하였다.
es 함수의 구분 | constructor | prototype | super | arguments |
일반함수 | o | o | x | o |
메서드 | x | x | o | o |
메서드
es6이전: 메서드에 대한 명확한 명의가 없이 객체에 바인딩된 함수를 일컫는 말로 사용
es6: 메서드는 메서드 축약 표현으로 정의된 함수만을 의미
→ es6에서 정의한 메서드는 인스턴스를 생성할 수 없는 non-constructor이다.
→ 인스턴스를 생성할 수 없으므로 prototype 프로퍼티도 없고 프로토타입도 생성하지 않는다.
const obj = { x: 1, foo() {return this.x; }, //foo는 메서드 bar: function() {return this.x} //bar에 바인딩된 함수는 메서드가 아닌 일반 함수 } new obj.foo() //TypeError new obj.bar() //bar {} obj.foo.hasOwnProperty('prototype'); // false obj.bar.hasOwnProperty('prototype'); //true
- es6 메서드는 자신을 바인딩한 객체를 가리키는 내부슬롯[[HomeObject]]를 갖고 super 참조는 내부 슬롯[[HomeObject]]를 사용하여 수퍼 클래스 메서드를 참조.
→ [[HomeObject]]를 갖는 es6 메서드는 super 키워드를 사용할 수 있다.
const base = { name: 'Lee' sayHi() { return `Hi! ${this.name}`; } }; const derived = { __proto__ : base, //sayHi는 es6 메서드. es6메서드는 [[HomeObject]]를 갖는다. //sayHi의 [[HomeObject]]는 바인딩 된 객체인 derived를 가리키고 //super는 sayHi의 [[HomeObject]]의 프로토타입인 base를 가리킨다. sayHi() { return `${super.sayHi()}. how are you doing?`; } } console.log(derived.sayHi()); //Hi! Lee. how are you doing?
따라서 es6 메서드가 아닌 함수는 [[HomeObject]] 를 갖지 않기 때문에 super를 사용할 수 없다.
화살표 함수
function 키워드 대신 화살표를 사용하여 간략하게 함수를 정의
화살표 함수 정의
- 함수 정의
함수선언문 x 함수 표현식으로 정의해야한다.
- 매개변수 선언
-매개 변수가 여러 개인 경우 소괄호 안에 선언
-한 개인 경우 소괄호 생략 가능
-매개 변수가 없는 경우 소괄호 생략 불가능
- 함수 몸체 정의
함수 몸체가 하나의 문이라면 중괄호 생략 가능
하지만 표현식이 아닌 문은 반환할 수 없기 때문에 에러가 발생

화살표 함수와 일반 함수의 차이점
- 화살표 함수는 인스턴스를 생성할 수 없는 non-constructor이다.
const Foo = () => {}; new Foo()// TypeError: Foo is not a constructor Foo.hasOwnProperty('prototype') //false
따라서 prototype 프로퍼티가 없고 프로토타입도 생성하지 않는다.
- 중복된 매개변수 이름을 선언할 수 없다.
일반 함수는 중복된 매개변수를 선언해도 에러 발생x(단, strict mode일 경우 에러 발생)
화살표 함수는 중복된 매개변수 이름을 선언하면 에러 발생
//일반 함수 function normal(a, a) {return a + a}; console.log(normal(1, 2)) // 3; //use strict; function normal(a, a) {return a + a}; //SyntaxError //화살표 함수 const arrow = (a, a) => a + a; //SyntaxError
- 화살표 함수는 함수 자체의 this, arguments, super, new.target 바인딩을 갖지 않는다.
따라서 화살표 함수 내부에서 this, arguments, super, new.target 를 참조하면 스코프 체인을 통해 화살표 함수가 아닌 상위 스코프의 this, arguments, super, new.target 를 참조한다.
this
화살표 함수의 this는 일반 함수의 this와 다르게 동작한다.
이는 콜백 함수 내부의 this가 외부 함수의 this와 다르기 때문에 발생하는 문제를 해결하기 위해 의도적으로 설계한 것이다.

기대하는 결과는 [’-webkit-transition’, ‘-webkit-user-select’]다. 하지만 TypeError가 발생하다.
그 이유는 다음과 같다.
1.에서 this는 메서드를 호출한 객체(prefixer)를 가리킨다.
map 의 인수로 전달한 콜백함수는 일반함수로서 호출하는 것이기 때문에 전달한 콜백함수 의 내부인 2에서 this는 undefined를 가리킨다.
→ 일반함수로 호출되는 모든 함수의 내부의 this는 전역 객체를 가리키는데 class 내부의 코드는 암묵적으로 stirct mode가 적용되어 undefined가 바인딩된다.
이러한 문제를 해결하기 위해 화살표 함수의 this는 다르게 동작한다.
es6 이전에는 다음과 같은 방법으로 문제를 해결했다.
- this를 일단 회피시킨 후 콜백 함수 내부에 적용

- map의 두 번째 인수로 add 메서드를 호출한 prefixer객체를 가리키는 this 전달

- bind 메서드를 사용하여 add 메서드를 호출한 prefixer 객체를 가리키는 this를 바인딩

es6의 화살표 함수를 사용하면 간단하게 해결 할 수 있다.

화살표 함수는 함수 자체의 this 바인딩을 갖지 않는다. 따라서 화살표 함수 내부에서 this를 참조하면 상위 스코프의 this를 그대로 참조하고 이것을 lexical this라고 한다.
→ 렉시컬 스코프와 같이 화살표 함수의 this는 함수가 정의된 위치에 의해 결정
- 화살표 함수가 중첩된 경우

- 화살표 함수가 전역함수인 경우

- 화살표 함수가 프로퍼티에 할당된 경우

화살표 함수는 함수 자체에 this 바인딩을 갖지 않기 때문에 call, apply, bind 메서드를 사용해도 내부의 this를 교체할 수 없다.

call, apply, bind 메서드를 호출할 수 없는 것이 아니라 화살표 함수의 this를 교체할 수 없고 항상 상위 스코프의 this를 참조한다는 것
es6 메서드가 아닌 일반적인 의미의 메서드를 화살표 함수로 정의하는 것은 피해야 한다.
//Bad const person = { name: 'Lee' sayHi: () => console.log(`Hi ${this.name}`) } //프로퍼티로 할당된 화살표 함수 내부의 this는 전역 객체를 가리킨다 //전역 객체 window에는 빌트인 프로퍼티인 name이 존재 person.sayHi() //Hi
//Good const person = { name: 'Lee' sayHi() { console.log(`Hi ${this.name}`) } } person.sayHi() //Hi Lee
이러한 문제는 프로토타입 객체의 프로퍼티에 화살표 함수를 할당하는 경우에도 동일하게 발생


프로퍼티를 동적으로 추가할 땐 es6 메서드 정의를 사용할 수 없으므로 일반 함수를 할당
- 클래스 필드 정의 제안을 사용하여 클래스 필드에 화살표 함수 할당

위의 코드는 아래 코드와 같은 의미를 가진다.

this는 클래스 외부의 this를 참조하지 않고 클래스가 생성할 인스턴스를 참조
따라서 sayHi 클래스 필드에 할당한 화살표 함수 내부에서 참조한 this는 constructor 내부의 this 바인딩과 같다.
→ constructor 내부의 this 바인딩은 클래스가 생성한 인스턴스를 가리키므로 sayHi 클래스 필드에 할당한 화살표 함수 내부의 this 또한 클래스가 생성한 인스턴스를 가리킨다.
하지만클래스 필드에 할당한 화살표 함수는 프로토타입 메서드가 아니라 인스턴스 메서드가 괴므로 es6 메서드 축약표현을 사용하는 것이 좋다

super
화살표 함수는 함수 자체의 super 바인딩을 갖지 않는다 .따라서 화살표 함수 내부에서 super를 참조하면 this와 마찬가지로 상위 스코프의 super를 참조


super는 내부슬롯 [[HomeObject]]를 갖는 es6 메서드 내에서만 사용할 수 있는 키워드이다. sayHi 클래스 필드에 할당한 화살표 함수는 es6 메서드는 아니지만 함수 자체의 super 바인딩을 갖지 않기 때문에 참조해도 에러가 나지 않고 constructor의 super바인딩을 참조한다.
arguments
화살표 함수는 함수 자체의 arguments 바인딩을 갖지 않고 상위 스코프의 arguments를 참조한다.

화살표 함수는 상위 스코프의 arguments 객체는 참조할 수 있지만 자신에게 전달되는 인수 목록을 확인할 수 없고 상위 함수에게 전달된 인수 목록을 참조하므로 그다지 도움되지 않는다.
따라서 화살표 함수로 가변 인자 함수를 구현해야 할 때는 반드시 Rest 파라미터를 사용해야 한다.
Rest 파라미터
rest 파라미터(나머지 매개변수)는 매개변수 이름 앞에 …을 붙여서 함수에 전달된 인수들의 목록을 배열로 받는다.
일반 매개변수와 함께 사용할 수 있고 이 때 함수에 전달된 인수들은 매개변수와 Rest 파라미터에 순차적으로 할당된다.

Rest 파라미터는 이름 그대로 먼저 선언된 매개변수에 할당된 인수를 제외한 나머지 인수들로 구성된 배열이 할다되므로 반드시 마지막 파라미터여야 한다.
- Rest 파라미터는 단 하나만 선언할 수 있다
- 매개변수의 개수를 나타내는 함수 객체의 length 프로퍼티에 영향을 주지 않는다.
Rest 파라미터와 arguments 객체
es5 에서는 가변 인자 함수의 경우 arguments 객체를 활용하여 인수를 전달받았다.
하지만 arguments 객체는 순회 가능한 유사 배열 객체이므로 call, apply 메서드로 배열로 변환해야 하는 번거로움이 존재한다.
하지만 es6에서 rest파라미터를 사용하면 가변 인자 함수의 인수목록을 배열로 직접 전달받기 때문에 이런 번거로움을 피할 수 있다.
매개변수 기본값
인수가 전달되지 않은 매개변수의 값은 undefined이고 이를 방치하면 의도치 않은 결과가 생길 수 있다.

이러한 것을 방지하기 위해 방어코드가 필요하다.

그런데 es6에서 도입된 매개변수 기본 값을 사용하면 인수 체크 및 초기화를 간소화할 수 있다.

매개변수의 기본 값은 인수를 전달하지 않은 경우와 undefined를 전달한 경우에 유효
하지만 Rest 파라미터에는 기본값을 지정할 수 없고 함수 객체의 length 프로퍼티와 arguments객체에는 아무런 영향을 주지 않는다.