Default Parameter

function f (a, b, c) {
  if (!a) {
    a = 1
  }
  
  b = b || 2
  
  c = c 
    ? c 
    : 3
}

ES6이전에는 위에 기술해놓은 3가지 방법등으로 파라미터에 값이 없는경우 기본값을 할당 했었습니다. 그러나 이렇게 기본값을 할당하는 경우, 전달된 인자의 값이 falsy인 경우에는 문제가 되곤 했었죠.

console.log(Boolean(0)) // false
console.log(Boolean(NaN)) // false
console.log(Boolean(false)) // false
console.log(Boolean(null)) // false
console.log(Boolean('')) // false

자바 스크립트의 특정한 값들은 Boolean으로 변경했을때 falsy로 판단됩니다. 그렇기 때문에 위의 값들이 함수의 인자로 전달되었을 경우에는 원하지 않는 기본 값들이 할당되곤 했었죠. 이를 해결 하기 위해서는 전달된 인자를 typeof 인자 === 'undefined'로 비교하여 판단하면 비교적 안전하지만 이 또한 만만치 않은 작업비용이 들어갑니다.

const f = (a = 1, b = 2, c = 3) => {
  console.log(a, b, c)
}

f()  // 1, 2, 3
f(null, 0, NaN) // null, 0, NaN
f('', false, undefined) // '', false, 3

ES6에서는 번거롭고 오류가 발생할 수 있는 동작을 하는 여러 방법들 대신에, 파라미터에 기본값을 할당할 수 있게 변경 되었습니다. 단순히 매개변수 뒤에 대입 연산자로 표기만 해 두면 인자에 undefined가 전달되었을때 기본값을 할당 해 줍니다. 주의해야 할 것은 기존처럼 falsy한 특성을 갖는 값들은 그대로 할당되며 오로지 undefined만 기본값이 할당된다는 점 입니다.


const f1 = a => a * 2

const f = (a = 1, b = f1(a)) => {
  console.log(a, b)
}

f() // 1, 2

기본 값에는 에 해당되는 모든 것이 들어갈 수 있습니다. 자바스크립트에서는 함수 또한 값에 해당하는 식이기 때문에 기본값으로 할당할 수 있게 됩니다. 그 뿐 아니라 자신의 앞에 선언된 변수의 값 또한 사용할 수 있습니다. 주의해야 할 점은 기본값 b에서 실행될 f1은 함수 f의 두번째 인자의 값으로 undefined가 전달되었을때 실행된다는 점입니다.

const f = (a = b * 3, b = 2) => {
  console.log(a, b)
}

f() // b is not defined

함수의 매개변수 영역에 선언된 변수는 let으로 선언됩니다. 그렇기 때문에 TDZ 또한 동일하게 적용되게 됩니다. 자바스크립트는 좌에서 우로, 위에서 아래로 읽어 들이기 때문에 변수 a가 선언되었을때 아직 b는 선언되지 않은 상태이며 변수 bTDZ영역이 됩니다.

Named Value

const f = ({a, b}) => {
  console.log(a, b)
}

f({ a: 1, b: 2 }) // 1, 2

지난 포스팅에서 설명했듯이 Destructuring을 활용하면 객체를 인자로 전달받을때에도 구조분해를 할 수 있게 되었습니다. 함수에 객체를 전달할때 함수에 정의된 키값을 전달하면 해당 값이 각 매개변수에 매칭되게 됩니다.

const f = ({a = 1, b = 2}) => {
  console.log(a, b)
}

f({ a: undefined }) // 1, 2

만약 전달된 객체의 키 값이 존재하지 않거나 해당 키의 값이 undefined인 경우에는 기본 값을 할당 합니다.

const f = ({a = 1, b = 2}) => {
  console.log(a, b)
}

f() // Cannot destructure property `a` of 'undefined' or 'null'.

주의해야할 점은 객체를 구조분해하고 있는 함수에 undefined를 전달하는 경우에는 undefined.aundefined.b를 수행하기 때문에 에러가 발생하게 됩니다. 아무런 값이 전달되지 않았을 경우에는 그 즉시 프로그램을 종료하는 우아한 실드패턴이라고도 부르지만 만약 구성해야하는 서비스가 함수에 값이 전달되지 않았을때 에러 로그를 전송하고 다른 기능을 수행하거나 앱을 존속시켜야 하는 경우라면 주의하여 사용해야 합니다.

Rest Parameter

function f () {
  console.log(arguments)
}

f(1, 2, 3, 4, 5) // Arguments(5) [1, 2, 3, 4, 5]

ES6이전에는 함수에 정해지 않은 불특정한 값들을 인자로 전달받기 위해서는 arguments라는 유사배열객체를 사용하여 왔습니다. 그 뿐 아니라 arguments는 배열이 아닌 객체이기 때문에 배열처럼 사용하려면 Array.prototype.slice.call(arguments)등의 방법으로 사용하는 불편함도 있었습니다. 물론 ES6에서 도입된 Array.from(arguments)를 사용하면 되지만 불편한 건 변하지 않습니다. 게다가 Arrow Function에서는 arguments를 지원하지 않기 때문에 정해지지 않은 개수의 인자를 전달받으려면 새로운 방법이 필요하게 되었죠.

const f = (a, ...b) => {
  console.log(a, b)
}

f(1, 2, 3, 4, 5)

기존의 불편했던 동작들을 ES6에서는 Rest Parameter라는 기능으로 지원해주고 있습니다. 사용방법은 보시는바와 같이 맨 마지막 변수의 앞에 ...를 붙여주기만 하면 됩니다. 나머지 연산자라고도 불리우는 이 연산자는 앞에 지정된 변수가 있다면 순서대로 해당 변수에 값을 할당해 주고 더이상 선언된 변수가 없다면 함수에 전달된 남은 모든 인자들을 배열로 할당 합니다. 이때 b에 할당된 배열은 arguments와 같은 유사배열이 아닌 진짜 Array입니다.

const f = (a, ...b, c) => {
  console.log(a, b, c)
}

f(1, 2, 3, 4, 5) // Rest parameter must be last formal parameter

나머지 연산자는 반드시 맨 마지막에 선언되어야 하며 변수의 선언 중간에 포함될 수 없습니다. 중간에 나머지 연산자를 넣는경우 몇개의 파라미터를 절삭해야하는지 판단하기 어렵기 때문이죠.

const A = class {
  constructor () {
    this._a = 3
  }
  
  set a (...a) { 
    this._a = a
  }
}
// Setter function argument must not be a rest parameter

그 뿐 아니라 setter함수에서도 나머지 연산자는 사용될 수 없습니다. getter함수에서 인자를 받을 수 없듯이 setter함수에서는 하나의 인자만 받도록 구성되어 있기 때문입니다.