Как использовать аргументы и параметры ES6
Опубликовано: 2022-03-10ECMAScript 6 (или ECMAScript 2015) — это новейшая версия стандарта ECMAScript, в которой значительно улучшена обработка параметров в JavaScript. Теперь мы можем использовать остальные параметры, значения по умолчанию и деструктурирование, среди других новых функций.
В этом руководстве мы подробно рассмотрим аргументы и параметры и посмотрим, как они были обновлены в ECMAScript 6.
Аргументы против параметров
Аргументы и параметры часто взаимозаменяемы. Тем не менее, для целей этого урока мы проведем различие. В большинстве стандартов параметры (или формальные параметры) — это то, что дается в объявлении функции, а аргументы (или фактические параметры) — это то, что передается функции. Рассмотрим эту функцию:
function foo(param1, param2) { // do something } foo(10, 20);
В этой функции param1
и param2
являются параметрами функции, а значения, передаваемые функции ( 10
и 20
), являются аргументами.
Оператор спреда (…)
В ECMAScript 5 метод apply()
является удобным инструментом для передачи массива в качестве аргументов функции. Например, он обычно используется с методом Math.max()
для поиска максимального значения в массиве. Рассмотрим этот фрагмент кода:
var myArray = [5, 10, 50]; Math.max(myArray); // Error: NaN Math.max.apply(Math, myArray); // 50
Метод Math.max()
не поддерживает массивы; он принимает только числа. Когда массив передается функции Math.max()
, она выдает ошибку. Но когда используется метод apply()
, массив отправляется в виде отдельных чисел, поэтому метод Math.max()
может его обработать.
К счастью, с введением оператора распространения в ECMAScript 6 нам больше не нужно использовать метод apply()
. С помощью оператора распространения мы можем легко расширить выражение до нескольких аргументов:
var myArray = [5, 10, 50]; Math.max(...myArray); // 50
Здесь оператор распространения расширяет myArray
, чтобы создать отдельные значения для функции. Хотя имитация оператора спреда с помощью apply()
в ECMAScript 5 возможна, синтаксис сбивает с толку и ему не хватает гибкости оператора спреда. Оператор спреда не только проще в использовании, но и обладает большим количеством функций. Например, его можно использовать несколько раз и смешивать с другими аргументами в вызове function
:
function myFunction() { for(var i in arguments){ console.log(arguments[i]); } } var params = [10, 15]; myFunction(5, ...params, 20, ...[25]); // 5 10 15 20 25
Еще одним преимуществом оператора распространения является то, что его можно легко использовать с конструкторами:
new Date(...[2016, 5, 6]); // Mon Jun 06 2016 00:00:00 GMT-0700 (Pacific Daylight Time)
Конечно, мы могли бы переписать предыдущий код на ECMAScript 5, но нам пришлось бы использовать сложный шаблон, чтобы избежать ошибки типа:
new Date.apply(null, [2016, 4, 24]); // TypeError: Date.apply is not a constructor new (Function.prototype.bind.apply(Date, [null].concat([2016, 5, 6]))); // Mon Jun 06 2016 00:00:00 GMT-0700 (Pacific Daylight Time)
Расширение поддержки браузера оператора в вызовах функций
Настольные браузеры:
Хром | Fire Fox | Интернет-проводник | Microsoft Edge | Опера | Сафари |
---|---|---|---|---|---|
46 | 27 | – | Поддерживается | – | 7.1 |
Мобильные браузеры:
Хром для Android | Мобильный Firefox | Сафари Мобильный | Опера Мобайл | IE Мобильный |
---|---|---|---|---|
46 | 27 | 8 | – | – |
Остальные параметры
Параметр rest имеет тот же синтаксис, что и оператор расширения, но вместо расширения массива в параметры он собирает параметры и превращает их в массив.
function myFunction(...options) { return options; } myFunction('a', 'b', 'c'); // ["a", "b", "c"]
Если аргументов нет, параметр rest будет установлен в пустой массив:
function myFunction(...options) { return options; } myFunction(); // []
Параметр остатка особенно полезен при создании функции с переменным числом аргументов (функции, которая принимает переменное количество аргументов). Имея преимущество в том, что они являются массивами, остальные параметры могут легко заменить объект arguments
(что мы объясним позже в этом руководстве). Рассмотрим эту функцию, написанную на ECMAScript 5:
function checkSubstrings(string) { for (var i = 1; i < arguments.length; i++) { if (string.indexOf(arguments[i]) === -1) { return false; } } return true; } checkSubstrings('this is a string', 'is', 'this'); // true
Эта функция проверяет, содержит ли строка несколько подстрок. Первая проблема с этой функцией заключается в том, что нам нужно заглянуть внутрь тела function
, чтобы увидеть, что она принимает несколько аргументов. Вторая проблема заключается в том, что итерация должна начинаться с 1
вместо 0
, потому что arguments[0]
указывает на первый аргумент. Если позже мы решим добавить еще один параметр до или после строки, мы можем забыть обновить цикл. С остальными параметрами мы легко избегаем этих проблем:
function checkSubstrings(string, ...keys) { for (var key of keys) { if (string.indexOf(key) === -1) { return false; } } return true; } checkSubstrings('this is a string', 'is', 'this'); // true
Вывод этой функции такой же, как и у предыдущей. Здесь снова string
параметра заполняется аргументом, который передается первым, а остальные аргументы помещаются в массив и назначаются переменным keys
.
Использование параметра rest вместо объекта arguments
улучшает читаемость кода и позволяет избежать проблем с оптимизацией в JavaScript. Тем не менее, остальные параметры не лишены ограничений. Например, это должен быть последний аргумент; в противном случае произойдет синтаксическая ошибка:
function logArguments(a, ...params, b) { console.log(a, params, b); } logArguments(5, 10, 15); // SyntaxError: parameter after rest parameter
Другое ограничение заключается в том, что в объявлении function
допускается только один остаточный параметр:
function logArguments(...param1, ...param2) { } logArguments(5, 10, 15); // SyntaxError: parameter after rest parameter
Поддержка браузера остальных параметров
Настольные браузеры:
Хром | Fire Fox | Интернет-проводник | Microsoft Edge | Опера | Сафари |
---|---|---|---|---|---|
47 | 15 | – | Поддерживается | 34 | – |
Мобильные браузеры:
Хром для Android | Мобильный Firefox | Сафари Мобильный | Опера Мобайл | IE Мобильный |
---|---|---|---|---|
47 | 15 | – | – | – |
Параметры по умолчанию
Параметры по умолчанию в ECMAScript 5
JavaScript не поддерживает параметры по умолчанию в ECMAScript 5, но есть простой обходной путь. Используя логический оператор OR
( ||
) внутри функции, мы можем легко имитировать параметры по умолчанию в ECMAScript 5. Рассмотрим эту функцию:
function foo(param1, param2) { param1 = param1 || 10; param2 = param2 || 10; console.log(param1, param2); } foo(5, 5); // 5 5 foo(5); // 5 10 foo(); // 10 10
Эта функция ожидает два аргумента, но когда она вызывается без аргументов, она будет использовать значения по умолчанию. Внутри функции отсутствующие аргументы автоматически устанавливаются в значение undefined; поэтому мы можем обнаружить эти аргументы и объявить для них значения по умолчанию. Чтобы обнаружить отсутствующие аргументы и установить значения по умолчанию, мы используем логический оператор OR
( ||
). Этот оператор проверяет свой первый аргумент: если он правдив, оператор возвращает его; если это не так, оператор возвращает свой второй аргумент.
Этот подход обычно используется в функциях, но у него есть недостаток. Передача 0
или null
также активирует значение по умолчанию, потому что они считаются ложными значениями. Итак, если нам действительно нужно передать 0
или null
этой функции, нам понадобится альтернативный способ проверить, отсутствует ли аргумент:
function foo(param1, param2) { if(param1 === undefined){ param1 = 10; } if(param2 === undefined){ param2 = 10; } console.log(param1, param2); } foo(0, null); // 0, null foo(); // 10, 10
Внутри этой функции типы переданных аргументов проверяются, чтобы убедиться, что они не определены, прежде чем присваиваются значения по умолчанию. Этот подход требует немного больше кода, но является более безопасной альтернативой и позволяет нам передавать 0
и null
в функцию.
Параметры по умолчанию в ECMAScript 6
С ECMAScript 6 нам больше не нужно проверять неопределенные значения для имитации параметров по умолчанию. Теперь мы можем указать значения по умолчанию прямо в объявлении function
:
function foo(a = 10, b = 10) { console.log(a, b); } foo(5); // 5 10 foo(0, null); // 0 null
Как видите, пропуск аргумента активирует значение по умолчанию, а передача 0
или null
— нет. Мы даже можем использовать функции для получения значений параметров по умолчанию:
function getParam() { alert("getParam was called"); return 3; } function multiply(param1, param2 = getParam()) { return param1 * param2; } multiply(2, 5); // 10 multiply(2); // 6 (also displays an alert dialog)
Обратите внимание, что функция getParam
вызывается только в том случае, если второй аргумент опущен. Таким образом, когда мы вызываем multiply()
с двумя параметрами, оповещение отображаться не будет.
Еще одна интересная особенность параметров по умолчанию заключается в том, что мы можем ссылаться на другие параметры и переменные в объявлении function
:
function myFunction(a=10, b=a) { console.log('a = ' + a + '; b = ' + b); } myFunction(); // a=10; b=10 myFunction(22); // a=22; b=22 myFunction(2, 4); // a=2; b=4
Вы даже можете выполнять операции в объявлении function
:
function myFunction(a, b = ++a, c = a*b) { console.log(c); } myFunction(5); // 36
Обратите внимание, что, в отличие от некоторых других языков, JavaScript оценивает параметры по умолчанию во время вызова:
function add(value, array = []) { array.push(value); return array; } add(5); // [5] add(6); // [6], not [5, 6]
Поддержка браузера параметров по умолчанию
Настольные браузеры:
Характерная черта | Хром | Fire Fox | Интернет-проводник | Microsoft Edge | Опера | Сафари |
---|---|---|---|---|---|---|
Базовая поддержка | 49 | 15 | – | 14 | – | – |
Параметры без значений по умолчанию после параметра по умолчанию | 49 | 26 | – | 14 | – | – |
Мобильные браузеры:
Характерная черта | Хром для Android | Мобильный Firefox | Сафари Мобильный | Опера Мобайл | IE Мобильный |
---|---|---|---|---|---|
Базовая поддержка | 49 | 15 | – | – | – |
Параметры без значений по умолчанию после параметра по умолчанию | 46 | 26 | – | – | – |
Разрушение
Деструктуризация — это новая функция в ECMAScript 6, которая позволяет нам извлекать значения из массивов и объектов и назначать их переменным, используя синтаксис, аналогичный литералам объектов и массивов. Синтаксис ясен и прост для понимания и особенно полезен при передаче аргументов в функцию.
В ECMAScript 5 объект конфигурации часто используется для обработки большого количества необязательных параметров, особенно когда порядок свойств не имеет значения. Рассмотрим эту функцию:
function initiateTransfer(options) { var protocol = options.protocol, port = options.port, delay = options.delay, retries = options.retries, timeout = options.timeout, log = options.log; // code to initiate transfer } options = { protocol: 'http', port: 800, delay: 150, retries: 10, timeout: 500, log: true }; initiateTransfer(options);
Этот шаблон обычно используется разработчиками JavaScript, и он работает хорошо, но нам нужно заглянуть внутрь тела function
, чтобы увидеть, какие параметры она ожидает. С деструктурированными параметрами мы можем четко указать параметры в объявлении function
:
function initiateTransfer({protocol, port, delay, retries, timeout, log}) { // code to initiate transfer }; var options = { protocol: 'http', port: 800, delay: 150, retries: 10, timeout: 500, log: true } initiateTransfer(options);
В этой функции мы использовали шаблон деструктурирования объекта вместо объекта конфигурации. Это делает нашу функцию не только более лаконичной, но и более удобной для чтения.
Мы также можем комбинировать деструктурированные параметры с обычными:
function initiateTransfer(param1, {protocol, port, delay, retries, timeout, log}) { // code to initiate transfer } initiateTransfer('some value', options);
Обратите внимание, что если в вызове function
не указаны параметры, будет выдана ошибка типа:
function initiateTransfer({protocol, port, delay, retries, timeout, log}) { // code to initiate transfer } initiateTransfer(); // TypeError: Cannot match against 'undefined' or 'null'
Это желаемое поведение, когда нам нужны обязательные параметры, но что, если мы хотим, чтобы они были необязательными? Чтобы предотвратить эту ошибку, когда параметры отсутствуют, нам нужно присвоить деструктурированным параметрам значение по умолчанию:
function initiateTransfer({protocol, port, delay, retries, timeout, log} = {}) { // code to initiate transfer } initiateTransfer(); // no error
В этой функции в качестве значения по умолчанию для деструктурированных параметров предоставляется пустой объект. Теперь, если эта функция вызывается без каких-либо параметров, ошибки не возникнет.
Мы также можем присвоить значение по умолчанию каждому деструктурированному параметру:
function initiateTransfer({ protocol = 'http', port = 800, delay = 150, retries = 10, timeout = 500, log = true }) { // code to initiate transfer }
В этом примере каждое свойство имеет параметр по умолчанию, что избавляет нас от необходимости вручную проверять неопределенные параметры и назначать значения по умолчанию внутри тела function
.
Деструктуризация поддержки браузера
Настольные браузеры:
Характерная черта | Хром | Fire Fox | Интернет-проводник | Microsoft Edge | Опера | Сафари |
---|---|---|---|---|---|---|
Базовая поддержка | 49 | 2.0 | – | 14 | – | 7.1 |
Неструктурированный параметр с присвоением значения по умолчанию | 49 | 47 | – | 14 | – | – |
Мобильные браузеры:
Характерная черта | Хром для Android | Мобильный Firefox | Сафари Мобильный | Опера Мобайл | IE Мобильный |
---|---|---|---|---|---|
Базовая поддержка | 49 | 1 | 8 | – | – |
Параметры без значений по умолчанию после параметра по умолчанию | 49 | 47 | – | – | – |
Передача аргументов
Есть два способа передать аргументы функции: по ссылке или по значению. Изменение аргумента, переданного по ссылке, отражается глобально, но изменение аргумента, переданного по значению, отражается только внутри функции.
В некоторых языках, таких как Visual Basic и PowerShell, у нас есть возможность указать, следует ли передавать аргумент по ссылке или по значению, но это не относится к JavaScript.
Передача аргументов по значению
Технически JavaScript может передавать данные только по значению. Когда мы передаем аргумент функции по значению, в области действия function
создается копия этого значения. Таким образом, любые изменения значения отражаются только внутри function
. Рассмотрим этот пример:
var a = 5; function increment(a) { a = ++a; console.log(a); } increment(a); // 6 console.log(a); // 5
Здесь изменение аргумента внутри функции не влияет на исходное значение. Таким образом, когда переменная регистрируется вне функции, напечатанное значение по-прежнему равно 5
.
Передача аргументов по ссылке
В JavaScript все передается по значению, но когда мы передаем переменную, которая ссылается на объект (включая массивы), «значение» — это ссылка на объект, и изменение свойства объекта, на который ссылается переменная, изменяет лежащий в основе объект.
Рассмотрим эту функцию:
function foo(param){ param.bar = 'new value'; } obj = { bar : 'value' } console.log(obj.bar); // value foo(obj); console.log(obj.bar); // new value
Как видите, свойство объекта изменяется внутри функции, но измененное значение видно вне функции.
Когда мы передаем не примитивное значение, такое как массив или объект, за сценой создается переменная, указывающая на расположение исходного объекта в памяти. Затем эта переменная передается функции, и ее изменение повлияет на исходный объект.
Проверка типа и отсутствующие или дополнительные параметры
В строго типизированном языке мы должны указывать тип параметров в объявлении function
, но в JavaScript такой возможности нет. В JavaScript не имеет значения, какой тип данных или сколько аргументов мы передаем функции.
Предположим, у нас есть функция, которая принимает только один аргумент. Когда мы вызываем эту функцию, мы не ограничены передачей функции только одного аргумента; мы можем передать один, два или более аргументов! Мы можем даже вообще ничего не передавать, и ошибок не будет.
Количество аргументов и параметров может различаться двумя способами:
- Меньше аргументов, чем параметров .
Отсутствующие параметры будут равныundefined
. - Больше аргументов, чем параметров .
Дополнительные параметры будут игнорироваться, но их можно получить с помощью специальных аргументов переменных, подобных массиву (обсуждается далее).
Обязательные аргументы
Если в вызове function
отсутствует аргумент, для него будет установлено значение undefined
. Мы можем воспользоваться этим поведением и выдать ошибку, если аргумент опущен:
function foo(mandatory, optional) { if (mandatory === undefined) { throw new Error('Missing parameter: mandatory'); } }
В ECMAScript 6 мы можем пойти дальше и использовать параметры по умолчанию для установки обязательных аргументов:
function throwError() { throw new Error('Missing parameter'); } function foo(param1 = throwError(), param2 = throwError()) { // do something } foo(10, 20); // ok foo(10); // Error: missing parameter
Объект аргументов
Поддержка остальных параметров была добавлена в ECMAScript 4 с намерением заменить объект arguments
, но ECMAScript 4 так и не был реализован. С выпуском ECMAScript 6 JavaScript теперь официально поддерживает остальные параметры. Это также отменило план по отказу от поддержки объекта arguments
.
Объект arguments
— это массивоподобный объект, который доступен во всех функциях. Он позволяет извлекать значения argument
, переданные функции, по номеру, а не по имени. Объект позволяет нам передавать любое количество аргументов функции. Рассмотрим следующий фрагмент кода:
function checkParams(param1) { console.log(param1); // 2 console.log(arguments[0], arguments[1]); // 2 3 console.log(param1 + arguments[0]); // 2 + 2 } checkParams(2, 3);
Эта функция ожидает получить только один аргумент. Когда мы вызываем его с двумя аргументами, первый аргумент доступен в функции по имени параметра param1
или объекту arguments[0]
, а второй аргумент доступен только как arguments[1]
. Также обратите внимание, что объект arguments
можно использовать в сочетании с именованными аргументами.
Объект arguments
содержит запись для каждого аргумента, переданного функции, а индекс первой записи начинается с 0
. Если бы мы хотели получить доступ к большему количеству аргументов в приведенном выше примере, мы бы написали arguments[2]
, arguments[3]
и так далее.
Мы могли бы даже вообще не устанавливать именованные параметры и просто использовать объект arguments
:
function checkParams() { console.log(arguments[1], arguments[0], arguments[2]); } checkParams(2, 4, 6); // 4 2 6
На самом деле именованные параметры — это удобство, а не необходимость. Точно так же остальные параметры могут использоваться для отражения переданных аргументов:
function checkParams(...params) { console.log(params[1], params[0], params[2]); // 4 2 6 console.log(arguments[1], arguments[0], arguments[2]); // 4 2 6 } checkParams(2, 4, 6);
Объект arguments
похож на массив, но в нем отсутствуют методы массива, такие как slice()
и foreach()
. Чтобы использовать методы массива для объекта arguments
, объект сначала необходимо преобразовать в настоящий массив:
function sort() { var a = Array.prototype.slice.call(arguments); return a.sort(); } sort(40, 20, 50, 30); // [20, 30, 40, 50]
В этой функции Array.prototype.slice.call()
используется как быстрый способ преобразовать объект arguments
в массив. Далее метод sort()
сортирует элементы массива и возвращает его.
В ECMAScript 6 есть еще более простой способ. Array.from()
, новое дополнение в ECMAScript 6, создает новый массив из любого массивоподобного объекта:
function sort() { var a = Array.from(arguments); return a.sort(); } sort(40, 20, 50, 30); // [20, 30, 40, 50]
Свойство длины
Хотя объект arguments технически не является массивом, у него есть свойство length
, которое можно использовать для проверки количества аргументов, переданных функции:
function countArguments() { console.log(arguments.length); } countArguments(); // 0 countArguments(10, null, "string"); // 3
Используя свойство length
, мы лучше контролируем количество аргументов, передаваемых функции. Например, если для работы функции требуется два аргумента, мы можем использовать свойство length
, чтобы проверить количество переданных аргументов и выдать ошибку, если их меньше, чем ожидалось:
function foo(param1, param2) { if (arguments.length < 2) { throw new Error("This function expects at least two arguments"); } else if (arguments.length === 2) { // do something } }
Остальные параметры являются массивами, поэтому у них есть свойство length
. В ECMAScript 6 предыдущий код можно переписать с остальными параметрами:
function foo(...params) { if (params.length < 2) { throw new Error("This function expects at least two arguments"); } else if (params.length === 2) { // do something } }
Свойства вызывающего абонента и вызывающего абонента
Свойство callee
относится к функции, которая выполняется в данный момент, а caller
относится к функции, вызвавшей выполняющуюся в данный момент функцию. В строгом режиме ECMAScript 5 эти свойства устарели, и попытка доступа к ним вызывает ошибку TypeError.
Свойство arguments.callee
полезно в рекурсивных функциях (рекурсивная функция — это обычная функция, которая ссылается на себя по имени), особенно когда имя функции недоступно (анонимная функция). Поскольку у анонимной функции нет имени, единственный способ сослаться на нее — с помощью arguments.callee
.
var result = (function(n) { if (n <= 1) { return 1; } else { return n * arguments.callee(n - 1); } })(4); // 24
Объект аргументов в строгом и нестрогом режимах
В нестрогом режиме ECMAScript 5 объект arguments
имеет необычную функцию: он синхронизирует свои значения со значениями соответствующих именованных параметров.
Рассмотрим следующий фрагмент кода:
function foo(param) { console.log(param === arguments[0]); // true arguments[0] = 500; console.log(param === arguments[0]); // true return param } foo(200); // 500
Внутри этой функции arguments[0]
присваивается новое значение. Поскольку значения arguments
всегда синхронизируются со значениями именованных параметров, изменение arguments[0]
также изменит значение param
. По сути, это как бы два разных имени одной и той же переменной. В строгом режиме ECMAScript 5 это запутанное поведение объекта arguments
было удалено:
"use strict"; function foo(param) { console.log(param === arguments[0]); // true arguments[0] = 500; console.log(param === arguments[0]); // false return param } foo(200); // 200
На этот раз изменение arguments[0]
не влияет на param
, и результат соответствует ожидаемому. Вывод этой функции в ECMAScript 6 такой же, как и в строгом режиме ECMAScript 5, но имейте в виду, что когда в объявлении function
используются значения по умолчанию, объект arguments
не затрагивается:
function foo(param1, param2 = 10, param3 = 20) { console.log(param1 === arguments[0]); // true console.log(param2 === arguments[1]); // true console.log(param3 === arguments[2]); // false console.log(arguments[2]); // undefined console.log(param3); // 20 } foo('string1', 'string2');
В этой функции, несмотря на то, что param3
имеет значение по умолчанию, он не равен arguments[2]
, поскольку в функцию передаются только два аргумента. Другими словами, установка значений по умолчанию не влияет на объект arguments
.
Заключение
ECMAScript 6 привнес в JavaScript сотни мелких и крупных улучшений. Все больше и больше разработчиков используют функции ECMAScript 6, и вскоре эти функции станут неизбежными. В этом руководстве мы узнали, как ECMAScript 6 улучшил обработку параметров в JavaScript, но мы только поверхностно коснулись ECMAScript 6. Многие другие новые и интересные функции языка заслуживают внимания.
Ссылки
- Таблица совместимости ECMAScript 6, Юрий Зайцев
- «Спецификация языка ECMAScript 2015», ECMA International