[JavaScript] 26. ES6 함수의 추가기능

do_large·2021년 1월 2일
1

Deep Dive

목록 보기
8/13
post-thumbnail

함수의 구분

ES6 이전의 모든 함수는 일반함수로서 호출할 수 있는것은 물론 생성자 함수로서 호출할 수 있다. 즉, ES6이전의 모든 함수는 callable이면서 constructor이다. 메서드도 일반함수, 생성자 함수로 호출할 수 있었다!

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

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

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

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

ES6 이전의 모든 함수는 사용 목적에따라 명확한 구분이 없으므로 호출 방식에 특별한 제약이 없고 생성자 함수로 호출되지 않아도 프로토타입 객체를 생성한다.

그래서 ES6부터는 함수를 세가지 종류로 구분했음!!!

일반함수 > constructor, prototype, arguments
메서드 > non-constructor, super, arguments
화살표 함수 > non-constructor

메서드

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

화살표 함수

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

1. 화살표 함수 정의

함수 선언문말고 함수 표현식으로 정의할 수 있다.

const multiply = (x, y) => x * y;
multiply(2, 3); // -> 6

함수 몸체가 표현식 이면서 하나의 문으로 구성된경우에는 중괄호로 감싸주지 않아도 암묵적으로 반환된다. 여러개의 문으로 된 경우에는 중괄호로 감싸주고 반환값이 있으면 return을 적어줘야 한다.

그리고 객체 리터럴을 반환할때는 소괄호로 감싸주어야한다.
({...}) 이렇게 해주지 않고 {..}이렇게 해주면 함수몸체를 감싸는 중괄호로 인식한다.

const create = (id, content) => ({ id, content });
create(1, 'JavaScript'); // -> {id: 1, content: "JavaScript"}

// 위 표현은 다음과 동일하다.
const create = (id, content) => { return { id, content }; };

화살표 함수는 일급객체이므로 map, filter, reduce 같은 고차함수에 인수로 전달할수 있다. 화살표함수는 콜백함수로 정의할때 유용하다!
아래처럼!

// ES5
[1, 2, 3].map(function (v) {
  return v * 2;
});

// ES6
[1, 2, 3].map(v => v * 2); // -> [ 2, 4, 6 ]

2. 화살표 함수와 일반 함수와의 차이

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

화살표함수는 인스턴스를 생성할수 없으므로 prototype프로퍼티가없음

2) 중복된 매개변수 이름을 선언할 수 없다.

일반함수는 매개변수이름을 중복해서 사용할수있지만, 화살표함수에서 이렇게하면 에러발생함

3) 화살표함수는 함수 자체의 this, argument, super, new.target 바인딩을 갖지않는다.

화살표함수 내부에서 this, argument, super, new.target을 참조하면 스코프체인을 통해 상위 스코프의 this, argument, super, new.target를 참조한다.

(화살표함수가 중첩되어있을시 스코프체인상 가장 가까운 상위함수 중 화살표함수가 아닌 함수를 참조한다.)

3. this

화살표함수의 this는 일반함수의 this와 다르게 동작한다.

this 바인딩은 함수를 호출한 방식에 따라 동적으로 결정되는데, 고차함수의 경우 인수로 전달되어 고차함수 내부에서 호출되는 콜백함수를 일반함수로서 호출하게 되면 콜백함수내부의 this는 전역객체를 가리킨다.

그래서, 콜백함수내부의 this문제와 외부함수의 this가 서로 다른 값을 가리키게 되는 문제가 발생한다.

이러한 문제를 해결하기위해 의도적으로 설계된것이 화살표함수이다.
화살표함수는 함수 자체의 this바인딩을 갖지않기때문에 상위스코프의 this를 그대로 참조하는데, 이를 lexical this라고 한다.


  • 메서드를 화살표함수로 정의하는것을 피하기

프로퍼티에 할당한 화살표함수의 this는 스코프체인상에서 가장 가까운 상위함수의 this를 참조하게 된다.

아래의 경우 sayHi 프로퍼티에 할당한 화살표 함수의 this는 메서드를 호출한 객체는 person을 가리키지 않고 상위 스코프인 전역객체를 가리킨다. 그래서 화살표함수로 메서드를 정의하는 것은 바람직하지 않다.


const person = {
  name: 'Lee',
  sayHi: () => console.log(`Hi ${this.name}`)
  sayHi() {
    console.log(`Hi ${this.name}`);
  }
};

// sayHi 프로퍼티에 할당된 화살표 함수 내부의 this는 상위 스코프인 전역의 this가 가리키는
// 전역 객체를 가리키므로 이 예제를 브라우저에서 실행하면 this.name은 빈 문자열을 갖는
// window.name과 같다. 전역 객체 window에는 빌트인 프로퍼티 name이 존재한다.
person.sayHi();
  • 프로토타입 객체의 프로퍼티에 화살표함수 할당하지말기
function Person(name) {
  this.name = name;
}

// Bad > Hi
Person.prototype.sayHi = () => console.log(`Hi ${this.name}`);
// Good > Hi Lee
//Person.prototype.sayHi = function () { console.log(`Hi ${this.name}`); };

const person = new Person('Lee');

person.sayHi(); 

4. super

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

화살표 함수의 super는 상위 스코프인 constructor의 super를 가리킨다.

class Base {
  constructor(name) {
    this.name = name;
  }

  sayHi() {
    return `Hi! ${this.name}`;
  }
}

class Derived extends Base {
  sayHi = () => `${super.sayHi()} how are you doing?`;
}

const derived = new Derived('Lee');
console.log(derived.sayHi()); // Hi! Lee how are you doing?

5. arguments

화살표 함수는 함수 자체의 arguments 바인딩을 갖지 않는다.
그래서 화살표함수내의 arguments는 상위스코프의 arguments를 참조한다.

(function () {
  // 화살표 함수 foo의 arguments는 상위 스코프인 즉시 실행 함수의 arguments를 가리킨다.
  const foo = () => console.log(arguments); // [Arguments] { '0': 1, '1': 2 }
  foo(3, 4);
}(1, 2));

// 화살표 함수 foo의 arguments는 상위 스코프인 전역의 arguments를 가리킨다.
// 하지만 전역에는 arguments 객체가 존재하지 않는다. arguments 객체는 함수 내부에서만 유효하다.
const foo = () => console.log(arguments);
foo(1, 2); // ReferenceError: arguments is not defined

Rest 파라미터

1. 기본문법

rest파라미터(나머지 매개변수)는 매개변수 이름앞에 ...를 붙여서 정의한 매개변수를 의미한다. Rest파라미터는 함수에 전달된 인수들의 목록을 배열로 전달받는다

Rest 파라미터는 먼저 선언된 매개변수에 할당된 인수를 제외한 나머지 인수들로 구성된 배열이 할당된다. Rest파라미터는 반드시 마지막 파라미터여야 한다. 그렇지 않으면 syntaxError가 발생함

function foo(...rest) {
  console.log(rest); // [ 1, 2, 3, 4, 5 ]
}

foo(1, 2, 3, 4, 5);

// 일반 매개변수와 함께 사용가능
function foo2(param, ...rest) {
  console.log(param); // 1
  console.log(rest);  // [ 2, 3, 4, 5 ]
}

foo2(1, 2, 3, 4, 5);

2. Rest 파라미터와 arguments객체

arguments객체는 함수 호출시 전달된 인수들의 정보를 담고있는 유사배열 객체이다.
그래서 배열메서드를 사용하려면 객체를 배열로 먼저 변환해줘야 한다.

function sum() {
  // 유사 배열 객체인 arguments 객체를 배열로 변환한다.
  var array = Array.prototype.slice.call(arguments);

  return array.reduce(function (pre, cur) {
    return pre + cur;
  }, 0);
}

console.log(sum(1, 2, 3, 4, 5)); // 15

arguments를 배열로 사용하고자 하는경우에는 rest 파라미터를 사용해서 배열로 전달받을수 있다.

function sum(...args) {
  // args에는 배열 [1, 2, 3, 4, 5]가 할당된다.
  return args.reduce((pre, cur) => pre + cur, 0);
}
console.log(sum(1, 2, 3, 4, 5)); // 15

※ Spread 연산자
배열, 문자열 등의 이터러블을 분해해서 개별요소로 만들 수 있습니다

const arr = [1, 2, 3, 4, 5];
console.log(arr); // [ 1, 2, 3, 4, 5 ]
console.log(...arr); // 1, 2, 3, 4, 5

매개변수 기본값

함수를 호출할 때 매개변수의 개수만큼 인수를 전달하지 않으면 인수가 전달되지 않은 매개변수의 값은 undefined이다.
매개변수에 인수가 전달되었는지 확인하여 인수가 전달되지 않은 경우 매개변수에 기본값을 할당할필요가 있다.

아래와 같이 함수내부에서 인수를 체크해서 초기화할수도 있지만,

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

이렇게 인수체크 및 초기화를 간소화할수도 있다.
매개변수 기본값은 매개변수에 인수를 전달하지 않은 경우와 undefined를 전달한 경우에만 유효하다!

function sum(x = 0, y = 0) {
  return x + y;
}

console.log(sum(1, 2)); // 3
console.log(sum(1));    // 1

모던 자바스크립트 Deep Dive를 정리한 내용입니다.

0개의 댓글