ES6 In Depth: 레스트 파라메터와 디폴트 파라메터

ES6 In Depth는 ECMAScript 표준 6번째 에디션(줄여서 ES6)을 맞아 JavaScript에 새로 추가된 요소들을 살펴보는 시리즈입니다.

오늘 글은 함수에 대한 JavaScript의 표현력을 더욱 풍부하게 만들어 주는 함수 문법 2개에 대한 것입니다. 레스트 파라메터(rest parameter)와 디폴트 파라메터(default parameter)가 그것입니다.

레스트 파라메터(rest parameter)

일반적으로 API를 정의할 때 variadic function이 필요한 경우가 있습니다. variadic function은 인자의 개수가 가변적인 함수입니다. 예를 들어 String.prototype.concat 메소드는 문자열 인자를 임의의 개수만큼 받습니다. 레스트 파라메터는 variadic function을 작성하기 위한 ES6의 새로운 문법입니다.

예를 들어 containsAll이라는 간단한 variadic function을 작성해 봅시다. 이 함수는 어떤 문자열 안에 임의의 갯수만큼 전달되는 작은 문자열들이 포함되어 있는지 체크합니다. 예를 들어 containsAll("banana", "b", "nan") 호출은 true를 리턴하고, containsAll("banana", "c", "nan") 호출은 false를 리턴합니다.

다음은 이 함수를 전통적인 방식으로 구현한 코드입니다.

function containsAll(haystack) {
  for (var i = 1; i < arguments.length; i++) {
    var needle = arguments[i];
    if (haystack.indexOf(needle) === -1) {
      return false;
    }
  }
  return true;
}

이 코드는 마법의 arguments 객체를 사용합니다. arguments 객체는 함수에 전달된 파라메터들을 담고 있는 배열입니다. 이 코드는 분명 우리가 원하는 대로 동작합니다. 하지만 가독성이 좋지 않습니다. 함수의 파라메터 목록에는 haystack 파라메터 하나만 정의되어 있습니다. 그래서 이 함수가 여러개의 인자를 취한다는 사실을 한번에 알아보기 어렵습니다. 더구나, 우리는 arguments 객체를 순회할 때 인덱스가 0이 아니라 1에서 시작하도록 주의해야 합니다. arguments[0]haystack 인자에 해당하기 때문입니다. 만약 haystack 인자의 앞이나 뒤에 다른 파라메터를 추가하고 싶다면, 반드시 루프도 함께 수정해야 합니다. 레스트 파라메터(rest parameter)가 이런 2가지 단점을 보완합니다. 다음은 레스트 파라메터를 이용해서 ES6 방식으로 구현한 containsAll 함수입니다.

function containsAll(haystack, ...needles) {
  for (var needle of needles) {
    if (haystack.indexOf(needle) === -1) {
      return false;
    }
  }
  return true;
}

이 함수도 처음 구현한 함수와 똑같이 동작합니다. 하지만 ...needles라는 특별한 문법을 쓰고 있습니다. containsAll("banana", "b", "nan")라고 이 함수를 호출하면 어떻게 동작하는지 살펴봅시다. haystack 인자는 언제나처럼 처음 전달되는 파라메터 "banana"로 채워집니다. needles 앞에 있는 생략부호는 레스트 파라메터(rest parameter)임을 표시합니다. 전달되는 나머지 파라메터들이 needles 배열 변수에 담겨 전달된다는 사실을 나타냅니다. 앞서 예시한 함수 호출의 경우 needles 배열 변수는 ["b", "nan"]와 같습니다. 이후의 함수 진행은 통상적인 경우와 같습니다. (우리가 ES6 for-of 루프를 사용했음을 눈여겨보세요.)

함수의 마지막 파라메터만 레스트 파라메터(rest parameter)가 될 수 있습니다. 함수 호출시 레스트 파라메터 앞의 파라메터들은 통상적인 규칙에 의해 채워집니다. “추가(extra)” 인자들만 레스트 파라메터 배열 변수에 담겨집니다. 만약 추가 인자가 없다면 레스트 파라메터는 그냥 빈 배열이 됩니다. 레스트 파라메터 배열 변수의 값이 undefined가 되는 경우는 절대 없습니다.

디폴트 파라메터(default parameter)

함수 호출시 함수의 파라메터들을 모두 채울 필요가 없는 경우가 많습니다. 그리고 전달되지 않은 파라메터들에 할당할 적절한 디폴트 값이 있는 경우가 많습니다. 지금까지 JavaScript에는 파라메터의 디폴트 값을 표현하는 방법이 부족했습니다. 특별히 값을 전달하지 않을 경우 파라메터의 디폴트 값은 언제나 undefined였습니다. ES6는 파라메터에 임의의 디폴트 값을 지정할 수 있게 하는 새로운 방법을 제공합니다.

여기 예제가 있습니다. (백틱 문자는 템플릿 문자열을 나타냅니다. 지난 글에서 템플릿 문자열을 소개했습니다.)

function animalSentence(animals2="tigers", animals3="bears") {
    return `Lions and ${animals2} and ${animals3}! Oh my!`;
}

각 파라메터의 = 기호 다음 부분이 함수를 호출할 때 파라메터 값을 전달하지 않으면 사용하는 디폴트 값을 표현합니다. 그래서 animalSentence()라고 호출하면 "Lions and tigers and bears! Oh my!"가 리턴되고, animalSentence("elephants")라고 호출하면 "Lions and elephants and bears! Oh my!"가 리턴됩니다. 그리고 animalSentence("elephants", "whales")라고 호출하면 "Lions and elephants and whales! Oh my!"가 리턴됩니다.

디폴트 파라메터에 관해 알아둘 것이 조금 더 있습니다.

  • Python과 달리, 디폴트 값은 함수를 호출하는 시점에 계산됩니다. 계산 방향은 왼쪽에서 오른쪽입니다. 이것은 디폴트 값 표현식에 바로 직전에 채워진 파라메터 값을 사용할 수 있음을 의미합니다. 예를 들어 우리는 animalSentence()를 다음처럼 조금 더 팬시(fancy)하게 만들 수 있습니다.
    function animalSentenceFancy(animals2="tigers",
        animals3=(animals2 == "bears") ? "sealions" : "bears")
    {
      return `Lions and ${animals2} and ${animals3}! Oh my!`;
    }
    

    그러면 animalSentenceFancy("bears")라고 호출할 때 "Lions and bears and sealions. Oh my!"가 리턴됩니다.

  • undefined를 전달하는 것은 아무것도 전달하지 않는 것과 동일합니다. 그래서 animalSentence(undefined, "unicorns")라고 호출하면 "Lions and tigers and unicorns! Oh my!"가 리턴됩니다.
  • 디폴트 값을 지정하지 않은 파라메터의 디폴트 값은 암묵적으로 undefined 입니다. 그래서
    function myFunc(a=42, b) {...}
    

    라고 정의하는 것이 허용되며, 이것은 다음과 같이 정의하는 것과 동일합니다.

    function myFunc(a=42, b=undefined) {...}
    

arguments는 이제 그만

우리는 지금까지 레스트 파라메터(rest paramenter)와 디폴트 파라메터(default parameter)가 arguments 객체를 대신할 수 있음을 보았습니다. arguments 객체를 쓰지 않는 편이 코드를 읽기 쉽게 만듭니다. 마법의 arguments 객체는 코드를 읽기 어렵게 만들 뿐 아니라 JavaScript VM의 성능을 최적화하는 것도 어렵게 만듭니다.

레스트 파라메터(rest paramenter)와 디폴트 파라메터(default parameter)로 arguments 객체를 완전히 대치하는 것이 바람직합니다. 이를 위한 첫단계로 레스트 파라메터나 디폴트 파라메터를 사용하는 함수에서는 arguments 객체를 쓰지 말아야 합니다. arguments 객체는 당분간 없어지지 않고 유지될 것입니다. 그렇다고 하더라도 지금부터 가능하면 arguments 객체의 사용을 피하고 레스트 파라메터와 디폴트 파라메터를 사용하는 것이 좋습니다.

브라우저 지원

Firefox는 버전 15부터 레스트 파라메터(rest paramenter)와 디폴트 파라메터(default parameter)를 지원해왔습니다.

불행히 다른 브라우저들은 아직 레스트 파라메터 또는 디폴트 파라메터를 지원하지 않고 있습니다. V8은 최근 실험적으로 레스트 파라메터를 지원하기 시작했습니다. 그리고 V8 프로젝트에는 디폴트 파라메터 지원 이슈가 오픈되어 있습니다. 웹킷의 자바스크립트 엔진인 JSC 프로젝트에도 레스트 파라메터 지원 이슈디폴트 파라메터 지원 이슈가 오픈되어 있습니다.

BabelTraceur 컴파일러는 모두 디폴트 파라메터를 지원합니다. 이 컴파일러들을 이용하면 지금 당장 레스트 파라메터와 디폴트 파라메터를 사용할 수 있습니다.

결론

비록 기술적으로 전에 없던 새로운 기능을 제공하는 것은 아니지만 레스트 파라메터(rest parameter)와 디폴트 파라메터(default parameter)는 좀더 풍부하고 가독성 좋은 표현으로 JavaScript 함수를 선언할 수 있게 합니다. 기분 좋게 호출하세요!


Note: 이 기능들을 Firefox에 구현한 Benjamin Peterson에게 감사를 전합니다. 그가 프로젝트에 기여한 모든 작업들과 이번 글을 작성하는데 기울인 모든 노고에 대해서도 감사를 전합니다.

다음에 우리는 간단하고, 우아하고, 실용적이고, 언제나 사용할만한 ES6의 또다른 요소를 소개할 것입니다. 이것은 당신이 이미 배열과 객체를 정의할 때 익숙하게 사용하고 있던 문법과 유사한 문법을 갖고 있습니다. 이 요소는 배열과 객체를 분해하는 새롭고 간결한 방법입니다. 이것이 무엇일까요? 당신은 왜 객체를 분해하고 싶어할까요? 다음 글에서 함께 알아봅시다. 모질라 엔지니어 Nick Fitzgerald가 ES6 디스트럭쳐링(destructuring)에 대해 자세히 알려줄 것입니다.

Jason Orendorff

ES6 In Depth 편집자

이 글은 이 쓴 ES6 In Depth: Rest parameters and defaults의 한국어 번역본입니다.

작성자: ingeeKim

"누구에게나 평등하고 자유로운 웹"에 공감하는 직장인.

ingeeKim가 작성한 문서들…


1개 댓글

  1. alice

    잘 읽었습니다! 공부 하는 데 도움이 많이 되요.

    9월 17th, 2017 at 10:37 오전

댓글 쓰기