ES6 в деталях: остаточные параметры и параметры по умолчанию

ES6 в деталях — это цикл статей о новых возможностях языка программирования JavaScript, появившихся в 6-й редакции стандарта ECMAScript, кратко — ES6.

Сегодняшняя статья про две особенности, делающие синтаксис функций в JavaScript более выразительным: остаточные параметры и параметры по умолчанию.

Остаточные параметры

При создании API часто приходится писать вариадические функции — функции, принимающие любое количество аргументов. К примеру, метод String.prototype.concat может принимать любое количество строковых аргументов. ES6 предоставляет новый способ писать вариадические функции: при помощи остаточных параметров.

Давайте в качестве демонстрации напишем вариадическую функцию containsAll, которая проверяет, содержит ли строка некое количество подстрок. Например, containsAll("банан", "б", "нан") вернёт true, а containsAll("банан", "в", "нан")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 — массивоподобный объект, содержащий все параметры, переданные функции. Этот код определённо делает то, что нам нужно, но его читаемость оставляет желать лучшего. Список параметров функции содержит только один параметр, haystack, так что с первого взгляда непонятно, что эта функция, на самом деле, принимает множество аргументов. Вдобавок нам следует быть осторожными и не забывать, что начинать перебирать arguments следует со смещения 1, а не 0, потому что arguments[0] соответствует аргументу haystack. Если нам когда-нибудь захочется добавить ещё один параметр перед или после haystack, нужно будет не забыть обновить цикл for. Остаточные параметры призваны обходить оба этих затруднения. Вот так выглядит реализация containsAll на настоящем ES6, с использованием остаточного параметра:

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

У этой версии функции такое же поведение, как у первой, но в этой присутствует особой синтаксис ...needles. Давайте посмотрим, как работает эта функция, если её вызвать как containsAll("банан", "б", "нан"). Значение аргумента haystack, как обычно, равно параметру, переданному первым, а именно строке "банан". Многоточие перед needles обозначает, что это остаточный параметр. Все остальные переданные параметры собираются в массив, и переменной needles присваивается этот массив. В нашем случае переменная needles равна ["б", "нан"]. Далее выполнение функции продолжается как обычно. (Заметьте, мы использовали для цикла конструкцию for-of из ES6.)

Только последний параметр функции может быть помечен как остаточный. При вызове параметры перед остаточным параметром заполняются как обычно. Все «дополнительные» аргументы помещаются в массив и присваиваются остаточному параметру. Если дополнительных аргументов нет, остаточный параметр будет просто пустым массивом, он никогда не может быть равным undefined.

Параметры по умолчанию

Зачастую функции не нужно, чтобы все её возможные параметры передавались ей явным образом, и есть какие-то разумные значения по умолчанию, которые используются вместо тех параметров, которые не были переданы. В JavaScript всегда была негибкая разновидность параметров по умолчанию — параметры, для которых значение не передано, равны undefined. В ES6 же появилась возможность задавать для параметров произвольные значения по умолчанию.

Вот пример. (Обратные кавычки обозначают шаблонные строки, которые мы обсуждали на прошлой неделе.)

function animalSentence(animals2="тигры", animals3="медведи") {
    return `Львы, и ${animals2}, и ${animals3}! О, боже мой!`;
}

В каждом из параметров часть после = — это выражение, определяющее значение параметра по умолчанию, если вызывающий код его не указал. Так, animalSentence() вернёт "Львы, и тигры, и медведи! О, боже мой!", animalSentence("слоны") вернёт "Львы, и слоны, и медведи! О, боже мой!", а animalSentence("слоны", "киты") вернёт "Львы, и слоны, и киты! О, боже мой!".

Есть несколько тонкостей, связанных с параметрами по умолчанию:

Закругляемся с arguments

Мы увидели, как остаточные параметры и значения по умолчанию могут заменить использование объекта arguments и как код, где убран arguments, обычно приятнее читать. Помимо ухудшения читаемости кода, хорошо известно, что магическая сущность объекта arguments вызывает головную боль при оптимизации виртуальных машин JavaScript.

Есть надежда, что остаточные параметры и значения по умолчанию полностью вытеснят arguments из обращения. В качестве первого шага к этому функциям, использующим остаточные параметры или умолчания, запрещено обращаться к объекту arguments. Поддержку arguments уберут нескоро, если вообще уберут, но теперь предпочтительно избегать arguments и пользоваться остаточными параметрами и умолчаниями, когда это возможно.

Поддержка браузерами

В Firefox поддержка остаточных параметров и значений по умолчанию есть ещё с 15-й версии.

К сожалению, в других браузерах описанные параметры и значения пока не поддерживаются. В V8 недавно была добавлена экспериментальная поддержка остаточных параметров, и есть открытая задача реализовать умолчания. В JSC также поставлены задачи на остаточные параметры и умолчания.

Компиляторы Babel и Traceur поддерживают параметры по умолчанию, так что можно начинать пользоваться ими уже сегодня.

Заключение

Хотя остаточные параметры и параметры по умолчанию с технической точки зрения не добавляют какое-либо новое поведение, они могут сделать сигнатуры функций JavaScript более выразительными и читаемыми. Счастливого программирования!


Прим.: Спасибо Бенджамину Петерсону (Benjamin Peterson) за реализацию этой функциональности в Firefox, за весь его вклад в проект и, конечно, за статью этой недели.

На следующей неделе мы познакомимся с другой простой и практичной возможностью ES6, которую можно использовать в повседневной разработке. Она берёт знакомый синтаксис, которым вы уже пользуетесь для создания массивов и объектов, и переворачивает его с ног на голову, предоставляя новый, лаконичный способ разбирать массивы и объекты на части. Что это значит? Зачем бы нам вдруг хотеть разобрать объект на составляющие? Присоединяйтесь к нам в следующий четверг и узнаете. Инженер Mozilla Ник Фицджеральд (Nick Fitzgerald) представит в деталях деструктурирование в ES6.

Джейсон Орендорфф,

редактор ES6 в деталях.