⭐️26.1 함수의 구분

<aside> 🔑

→ ES6 이전 함수의 목적 (다양한 쓰임새)

  1. 일반적인 함수로서 호출
  2. NEW 연산자와 함께 호출하여 인스턴스 생성자 함수
  3. 객체에 바인딩되어 메서드로서 호출

callable(호출 가능) + constructor(인스턴스 생성 가능) 이 동시에 됨.

callable → 호출할 수 있는 함수 객체 constructor → 인스턴스를 생성할 수 있는 객체

var foo = function () {
	return 1;
};

// 일반적인 함수로서 호출
foo(); // -> 1

// 생성자 함수로서 호출
new foo(); // foo {}

// 메서드로서 호출
var obj = { foo: foo };
obj.foo(); // -> 1

→ 이로 인한 문제점

메서드라고 부르던 객체에 바인딩된 함수도 callable + consturctor라는 것이다. 객체에 바인딩된 함수도 일반 함수로서 호출할 수도 있는 것은 물론 생성자 함수로서 호출할 수도있다.

var obj = {
	x: 10,
	f: function () { return this.x; } // 메서드로만 쓰려고 만든 함수인데..
};

// 프로퍼티 f에 바인딩된 함수를 메서드로 호출
console.log(obj.f()); // 10

// 프로퍼티 f에 바인딩된 함수를 일반 함수로서 호출
var bar = obj.f;
console.log(bar()); // undefined (this === window.x)

// 프로퍼티 f에 바인딩된 함수를 생성자 함수로서 호출
console.log(new obj.f()); // f {}

객체에 바인딩된 함수를 다시 생성자 함수로 호출한다?

흔치는 않지만, 문법상 가능했다는 점.. 그리고 이는 성능 면에서 문제가 있다.

  1. ES5 사양에서는 function 키워드로 정의한 함수는 항상 생성자 함수가 될 수 있는 대상으로 취급된다. → .prototype 객체가 하나 따라붙음
  2. 따라서 헬퍼/콜백 함수도 (절대 new 생성자로 호출하지 않음) .prototype을 하나씩 들고 있게 된다.
// 콜백 함수를 사용하는 고차함수 map. 콜백 함수도 constructor로 프로토타입을 생성
[1, 2, 3].map(function (item) {
	return item * 2;
}); // -> [2, 4, 6]

//-->  콜백은 인스턴스가 필요없는데 prototype이 붙어서 메모리 낭비!

→ 위를 해결하기 위해서 ES6에서는 함수를 목적에 따라 3가지 종류로 구분했다.

ES6 함수의 구분 consturctor prototype super arguments
일반함수 O O X O
메서드 X X O O
화살표 함수 X X X X
</aside>

⭐️26.2 메서드

<aside> 🔥

→ 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

→ ES6 사양에서 정의한 메서드는 인스턴스를 생성할 수 없는 non-constructor이다.

→ ES6 메서드는 인스턴스를 생성할 수 없으므로 prototype 프로퍼티가 없고 프로토타입도 생성하지 않는다.

// obj.foo 는 constructor가 아닌 ES6 메서드이므로 prototype 프로퍼티가 없다.
obj.foo.hasOwnProperty('prototype'); // -> false

// obj.bar는 constructor인 일반 함수이므로 prototype 프로퍼티가 있다.
obj.bar.hasOwnProperty('prototype'); // -> true

++ 표준 빌트인 객체가 제공하는 프로토타입 메서드와 정적메서드는 모두 non-constructor다.

→ ES6 메서드는 자신을 바인딩한 객체를 가리키는 내부슬롯 [[HomeObject]]를 갖는다.

const base = {
	name: 'Lee',
	sayHi() {
		return `Hi! ${this.name}`;
	}
};

const derived = {
	__proto__: base,
	// sayHi는 ES6 메서드다, 메서드는 [[HomeObject]]를 갖는다.
	// sayHi의 [[HomeObject]]는 derived.prototype을 가리킨다.
	// super는 sayHi의 [[HomeObject]]의 프로토타입인 base.prototype을 가리킨다.
	sayHi() {
		return `${super.sayHi()}. how are you doing?`;
	}
};

console.log(derived.sayHi()); // Hi! Lee. how are you doing?

++ ES6 메서드가 아닌 함수는 super 키워드를 사용할 수 없다.

→ ES6 메서드는 본연의 기능(super)을 추가하고, 의미적으로 맞지 않는 기능(constructor)은 제거했다.

</aside>

⭐️26.3 화살표 함수

<aside> 👉

→ 화살표 함수는 function 키워드 대신 화살표를 사용하여 기존의 함수 정의 방식보다 간략하게 함수를 정의할 수 있다.

📌 26.3.1 화살표 함수 정의

<aside> 📖

→ 화살표 함수 정의 문법

  1. 함수정의

→ 화살표 함수는 함수 선언문으로 정의할 수 없고 함수 표현식으로 정의해야 한다.

const multiply = (x, y) => x * y; // 함수 표현식 축약형
multiply(2, 3); // -> 6 기존과 동일한 호출
  1. 매개변수 선언
  1. 함수 몸체 정의
// concise body (간결한 몸체) -> 단일 표현식, {}중괄호 없어도 자동 return
const power = x => x ** 2;
power(2); // -> 4
// 위 표현은 다음과 동일하다. 
// block body
const power = x => { return x ** 2; };

// 중괄호 {} 생략 시 함수 몸체 내부의 문이 표현식이 아니면 에러
const arrow = () => const x = 1; // SyntaxError why? 선언문이기 때문.
// 위 표현은 다음과 동일하다.
const arrow = () => { return const x = 1; };
// 함수 몸체의 문이 표현식이 아닌 문이라면 중괄호를 생략할 수 없다.
const arrow = () => { const x = 1; };

// 객체 리터럴을 반환하는 경우 객체 리터럴을 소괄호로 감싸주어야 한다.
const create = (id, content) => ({id, content});
create(1, 'JavaScript'); // -> {id: 1, content: 'JavaScript'}
// 객체 리터럴을 소괄호로 감싸지 안으면 함수 몸체를 감싸는 중괄호 {}로 잘못 해석한다
// {id, content}를 함수 몸체 내의 쉼표 연산자문으로 해석한다.
const create = (id, content) => { id, content };
create(1, 'JavaScript'); // -> undefined

// 함수 몸체가 여러 개의 문으로 구성된다면 함수 몸체를 감싸는 중괄호 
// {}를 생략할 수 없다! 반환값이 있다면 명시적으로 반환해야 한다.
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.

// 화살표 함수도 고차함수에 인수로 전달할 수 있다.
// ES5
[1, 2, 3].map(function(v) {
	return v * 2;
});
// ES6
[1, 2, 3].map(v => v * 2); 

</aside>

📌 26.3.2 화살표 함수와 일반 함수의 차이

<aside> 💦

→ 화살표 함수와 일반 함수의 차이

  1. 화살표 함수는 인스턴스를 생성할 수 없는 non-constructor다.

    const Foo = () => {};
    // 화살표 함수는 생성자 함수로서 호출할 수 없다.
    new Foo(); // TypeError
    // 인스턴스가 없으므로 prototype 프로퍼티도 없고, 프로토타입도 생성하지 않는다.
    Foo.hasOwnProperty('prototype'); // -> false
    
  2. 중복된 매개변수 이름을 선언할 수 없다.

    // 일반함수는 중복된 매개변수 이름을 선언해도 에러가 발생하지 않음
    function normal(a, a) { return a * a; }
    console.log(normale(1,2)); // 4
    
    // 화살표 함수에서 중복된 매개변수 이름을 선언하면 에러가 발생한다.
    const arrow = (a, a) => a + a; // SyntaxError
    
    // ++ 일반함수여도 strict mode에서 중복된 매개변수 사용 시 에러 발생!
    
  3. 화살표 함수는 함수 자체의 this, arguments, super, new.target 바인딩을 갖지 않는다.

📌 26.3.3 this

<aside> 🕳️

→ 화살표 함수가 일반 함수와 구별되는 가장 큰 특징은 바로 this다.

class + 화살표 함수 this 문제

class Prefixer {
  constructor(prefix) {
    this.prefix = prefix;
  }

  add(arr) {
    return arr.map(function (item) {
      return this.prefix + item;
    });
  }
}

const prefixer = new Prefixer('pre-');
console.log(prefixer.add(['a', 'b']));
// TypeError: Cannot read properties of undefined (this.prefix)

map 콜백이 일반 함수로 호출되니까 thisundefined (엄격 모드) 혹은 전역 객체를 가리킨다.

→ why ? 일반 함수로 호출되면 왜 this가 없을까 ?

  1. 메서드 호출 시 해당 호출 주제가 있어서 해당 객체가 this로 바인딩 된다.
  2. 일반 함수로 호출 시에는 this 바인딩할 대상이 없다.
// 메서드 호출
const obj = {
  x: 10,
  foo() { console.log(this.x); }
};
obj.foo(); // this = obj → 10

// 일반 함수 호출
function foo() { console.log(this); }
foo(); 
// 엄격 모드: undefined
// 비엄격 모드: window

→ 다시 돌아와서 이런 콜백 함수 this 문제를 ES6 이전에는 어떻게 해결 했나?

  1. 회피

    function Prefixer(prefix) {
      this.prefix = prefix;
    }
    Prefixer.prototype.add = function (arr) {
      var that = this; // this를 회피 시키기
      return arr.map(function (item) {
        return that.prefix + item; // this 대신 that으로 참조
      });
    };
    
  2. Array 메서드의 두 번째 인자 thisArg 사용

    function Prefixer(prefix) {
      this.prefix = prefix;
    }
    Prefixer.prototype.add = function (arr) {
      return arr.map(function (item) {
        return this.prefix + item;
      }, this); // ← thisArg로 this 지정
    };
    
  3. Function.prototype.bind(this)로 this 고정

    function Prefixer(prefix) {
      this.prefix = prefix;
    }
    Prefixer.prototype.add = function (arr) {
      return arr.map(function (item) {
        return this.prefix + item;
      }.bind(this)); // ← 여기서 this 고정
    };
    

→ 이제 ES6에서는 화살표함수로 해결 ~!

class Prefixer {
  constructor(prefix) { this.prefix = prefix; }
  add(arr) {
    return arr.map(item => this.prefix + item); // 바로 됨!
  }
}

정리. 화살표 함수는 함수 자체의 this 바인딩을 갖지 않는다. 따라서 화살표 함수 내부에서 this를 참조하면 상위 스코프의 this를 그대로 참조한다. 이를 lexical this라 한다.

→ 그렇다면 화살표 함수 안에서 this를 참조한다면?

→ 그렇다면 화살표 함수와 화살표 함수가 중첩되어 있다면?

→ 화살표 함수가 전역 함수라면 ?

</aside>

📌 26.3.4 super

<aside> 🧘‍♀️

</aside>

📌 26.3.5 arguments

<aside> 🕵️‍♂️

</aside>

⭐️26.4 Rest 파라미터

📌 26.4.1 기본 문법

<aside> 🥙

</aside>

📌 26.4.2 Rest 파라미터와 arguments 객체