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("слоны", "киты")
вернёт
"Львы, и слоны, и киты! О, боже мой!"
.
Есть несколько тонкостей, связанных с параметрами по умолчанию:
-
В отличие от Python, выражения для определения значений по умолчанию вычисляются в момент вызова функции, слева направо. Это также значит, что такие выражения могут использовать значения из параметров, заполненных перед ними. К примеру, мы можем сделать нашу функцию для генерации предложения о животных более причудливой, вот так:
function animalSentenceFancy(animals2="тигры", animals3=(animals2 == "медведи") ? "морские львы" : "медведи") { return `Львы, и ${animals2}, и ${animals3}! О, боже мой!`; }
И
animalSentenceFancy("медведи")
вернёт"Львы, и медведи, и морские львы! О, боже мой!"
-
Если функции передано
undefined
, то это считается эквивалентным тому, что мы вообще ничего не передали. Таким образом,animalSentence(undefined, "единороги")
вернёт"Львы, и тигры, и единороги! О, боже мой!"
. -
У параметра, для которого не указано значение по умолчанию, оно неявно равно
undefined
. То есть,function myFunc(a=42, b) {...}
допустимо и эквивалентно
function myFunc(a=42, b=undefined) {...}
Закругляемся с 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 в деталях.