- PVSM.RU - https://www.pvsm.ru -
Если вы занимаетесь JavaScript-разработкой, о какой бы платформе ни шла речь, это значит, что вы способны оценить значение функций. То, как они устроены, те возможности, которыми они наделяют программиста, делают их поистине универсальным и незаменимым инструментом. Так думают и разработчики Test262 [1] — официального набора тестов, который предназначен для проверки JavaScript-движков на совместимость со стандартом EcmaScript.
В этом материале они дают обзор синтаксических форм определения функций. В частности, речь пойдёт о том, что существует в JS со дня его появления, о том, что появилось в нём за годы развития, и о том, чего стоит ждать в будущем.
Самые известные и широко используемые способы определения функций в JS, кроме того, являются и самыми старыми. Это — объявление функции (Function Declaration) и функциональное выражение (Function Expression). Первый способ был частью исходного варианта языка с 1995-го года и был отражён в первой редакции [3] спецификации, в 1997-м. Второй представлен в третьей редакции [4], в 1999-м.
Если присмотреться к этим способам определения функций, можно увидеть три варианта их использования:
// Объявление функции
function BindingIdentifier() {}
// Именованное функциональное выражение
// (BindingIdentifier недоступно за пределами этой функции)
(function BindingIdentifier() {});
// Анонимное функциональное выражение
(function() {});
Тут, однако, стоит учесть, что у анонимного функционального выражения всё-таки может быть «имя». Вот [5] хороший материал об именах функций.
Если говорить об «API функций» в JavaScript, то начать такой разговор стоит с конструктора Function
. Принимая во внимание изначальный подход к проектированию языка, по аналогии с другими конструкциями, рассмотренное выше объявление функции можно интерпретировать в виде «литерала» к API конструктора Function
.
Конструктор Function
предоставляет средства для определения функций путём указания параметров и тела функции посредством строковых аргументов. Последний из этих аргументов представляет собой тело функции:
new Function('x', 'y', 'return x ** y;');
Важно обратить внимание на то, что определяя функции с использованием конструкторов, мы вынуждены прибегать к динамическому исполнению кода, что чревато проблемами с безопасностью.
На практике конструктор Function
используется очень редко, хотя он присутствует в языке с первой редакции EcmaScript. В большинстве случаев у него есть гораздо более удобные альтернативы.
В стандарте ES2015 [6] были представлены несколько новых синтаксических форм определения функций. У них огромное количество вариантов.
Если у вас есть опыт работы с модулями ES, вам должна быть знакома новая форма анонимного объявления функции. Хотя это очень похоже на анонимное функциональное выражение, такая функция, на самом деле, имеет связанное имя [7] "*default*"
. В результате, функция получается не такой уж и анонимной.
// Не такое уж и анонимное объявление функции
export default function() {}
Кстати, такое «имя» не является корректным идентификатором и привязка тут не создаётся.
В свойствах объектов, представляющих собой функции, легко можно узнать функциональные выражения — именованные и анонимные. Обратите внимание на то, что эти конструкции не являются некими особыми синтаксическими формами определения функций. Это — уже рассмотренные выше функциональные выражения, которые используются в инициализаторах объекта. Эти конструкции были представлены в ES3.
let object = {
propertyName: function() {},
};
let object = {
// (BindingIdentifier недоступен за пределами этой функции)
propertyName: function BindingIdentifier() {},
};
Вот объявления свойств-аксессоров, представленные В ES5:
let object = {
get propertyName() {},
set propertyName(value) {},
};
В ES2015 появился сокращённый синтаксис для определения методов объектов, который можно использовать как в формате обычного имени свойства, так и с квадратными скобками, в которые заключено строковое представление имени. То же самое касается и свойств-аксессоров:
let object = {
propertyName() {},
["computedName"]() {},
get ["computedAccessorName"]() {},
set ["computedAccessorName"](value) {},
};
Похожий подход можно использовать и для определения методов прототипов в объявлениях классов (Class Declarations) и в выражениях классов (Class Expressions):
// Объявление класса
class C {
methodName() {}
["computedName"]() {}
get ["computedAccessorName"]() {}
set ["computedAccessorName"](value) {}
}
// Выражение класса
let C = class {
methodName() {}
["computedName"]() {}
get ["computedAccessorName"]() {}
set ["computedAccessorName"](value) {}
};
То же самое применимо и к статическим методам классов:
// Объявление класса
class C {
static methodName() {}
static ["computedName"]() {}
static get ["computedAccessorName"]() {}
static set ["computedAccessorName"](value) {}
}
// Выражение класса
let C = class {
static methodName() {}
static ["computedName"]() {}
static get ["computedAccessorName"]() {}
static set ["computedAccessorName"](value) {}
};
Стрелочные функции, которые появились в ES2015, наделали много шума, однако, в итоге приобрели широкую известность и популярность. Существует две формы стрелочных функций. Первая — это краткая форма (Concise Body), которая не предусматривает наличия фигурных скобок после стрелки, там находится лишь выражение присваивания (Assignment Expression). Вторая — блочная форма (Block Body). Здесь, после стрелки, идёт тело функции в фигурных скобках, которые могут быть пустыми, либо содержать некоторое количество выражений.
Если стрелочная функция не имеет аргументов, или их больше одного, то, что находится перед стрелкой, должно быть заключено в круглые скобки. Если же такая функция имеет лишь один аргумент, использование круглых скобок необязательно.
На практике вышесказанное означает наличие множества вариантов определения стрелочных функций:
// Краткая форма Функции без параметров
(() => 2 ** 2);
// Краткая форма функции с одним параметром
(x => x ** 2);
// Функция с одним параметром и телом функции
(x => { return x ** 2; });
// Краткая форма функции со списком параметров в скобках
((x, y) => x ** y);
В последней части примера показан набор параметров стрелочной функции в скобках (covered parameters). Такой подход даёт возможность работы со списком параметров, например, позволяя использовать шаблоны деструктуризации:
({ x }) => x
Параметр без скобок (uncovered parameter), как уже было сказано, позволяет задать стрелочную функцию, имеющую лишь один аргумент. Перед этим единственным аргументом можно использовать ключевые слова await
или yield —
если стрелочная функция определена внутри асинхронной функции или генератора, но на этом возможности такого синтаксиса заканчиваются.
Стрелочные функции можно использовать в инициализаторах объектов и при задании их свойств. Тут используются стрелочные функциональные выражения (Arrow Function Expression):
let foo = x => x ** 2;
let object = {
propertyName: x => x ** 2
};
Генераторы имеют особый синтаксис. Заключается он в добавлении звёздочки к определениям функций, за исключением стрелочных функций и объявлений геттеров и сеттеров. В результате получаются объявления функций и методов, функциональные выражения, и даже конструкторы. Взглянем на всё это в следующем примере:
// Объявление генератора
function *BindingIdentifer() {}
// Ещё одно объявление не слишком анонимного генератора
export default function *() {}
// Выражение генератора
// (BindingIdentifier не доступен за пределами этой функции)
(function *BindingIdentifier() {});
// Анонимное выражение генератора
(function *() {});
// Определение методов
let object = {
*methodName() {},
*["computedName"]() {},
};
// Определение методов в объявлении класса
class C {
*methodName() {}
*["computedName"]() {}
}
// Определение статических методов в объявлении класса
class C {
static *methodName() {}
static *["computedName"]() {}
}
// Определение методов в выражении класса
let C = class {
*methodName() {}
*["computedName"]() {}
};
// Определение статических методов в выражении класса
let C = class {
static *methodName() {}
static *["computedName"]() {}
};
В июне 2017-го был опубликован стандарт ES2017, в котором, после нескольких лет разработки, были представлены асинхронные функции (Async Functions). Несмотря на то, что стандарт буквально только что «вышел из типографии», множество разработчиков уже пользуется асинхронными функциями благодаря Babel [8].
Асинхронные функции позволяют удобно описывать асинхронные операции. Благодаря их использованию код получается чистым и единообразным. При вызове асинхронной функции будет возвращён промис, который разрешится после того, как асинхронная функция возвратит результат своей работы. Если в асинхронной функции встречается выражение с ключевым словом await
, она может приостановить работу, и, дождавшись выполнения выражения, например, возвратить его результаты.
Синтаксис асинхронных функций не особенно отличается от того, что уже было рассмотрено. Главная их особенность — префикс async
:
// Объявление асинхронной функции
async function BindingIdentifier() { /**/ }
// Ещё одно объявление не такой уж анонимной асинхронной функции
export default async function() { /**/ }
// Именованное асинхронное функциональное выражение
// (BindingIdentifier недоступно за пределами этой функции)
(async function BindingIdentifier() {});
// Анонимное асинхронное функциональное выражение
(async function() {});
// Асинхронные методы
let object = {
async methodName() {},
async ["computedName"]() {},
};
// Асинхронные методы в объявлении класса
class C {
async methodName() {}
async ["computedName"]() {}
}
// Статические асинхронные методы в объявлении класса
class C {
static async methodName() {}
static async ["computedName"]() {}
}
// Асинхронные методы в выражении класса
let C = class {
async methodName() {}
async ["computedName"]() {}
};
// Статические асинхронные методы в выражении класса
let C = class {
static async methodName() {}
static async ["computedName"]() {}
};
Ключевые слова async
и await
можно использовать не только с традиционными объявлениями функций и функциональными выражениями. Они совместимы и со стрелочными функциями:
// Краткая форма функции с одним параметром
(async x => x ** 2);
// Функция с одним параметром, за которым следует тело функции
(async x => { return x ** 2; });
// Краткая форма функции со списком параметров в скобках
(async (x, y) => x ** y);
// Список параметров в скобках, за которым следует тело функции
(async (x, y) => { return x ** y; });
В будущих версиях спецификации JavaScript применение ключевых слов async
и await
будет расширено и на генераторы. За ходом работ по реализации этой функциональности можно наблюдать здесь [9]. Как вы, вероятно, догадались, речь идёт о комбинации ключевых слов async/await
и существующих форм определения генераторов — через объявления и выражения.
Асинхронный генератор, при вызове, возвращает итератор, метод которого next()
возвращает промис, который будет разрешён объектом, являющимся тем, что обычно возвращает итератор. В обычных условиях при вызове этого метода выполняется прямой возврат результата.
Асинхронные генераторы можно найти там, где уже имеются обычные функции-генераторы.
// Объявление асинхронного генератора
async function *BindingIdentifier() { /**/ }
// Объявление не такого уж и анонимного асинхронного генератора
export default async function *() {}
// Асинхронное выражение генератора
// (BindingIdentifier не доступен за пределами этой функции)
(async function *BindingIdentifier() {});
// Анонимное выражение генератора
(async function *() {});
// Определение методов
let object = {
async *propertyName() {},
async *["computedName"]() {},
};
// Определение методов прототипа в объявлении класса
class C {
async *propertyName() {}
async *["computedName"]() {}
}
// Определение методов прототипа в выражении класса
let C = class {
async *propertyName() {}
async *["computedName"]() {}
};
// Определение статических методов в объявлении класса
class C {
static async *propertyName() {}
static async *["computedName"]() {}
}
// Определение статических методов в выражении класса
let C = class {
static async *propertyName() {}
static async *["computedName"]() {}
};
Представим себе путь нового способа определения функции от идеи до рабочего кода. В упрощённом виде это выглядит так. Сначала идея становится предложением к стандарту, потом входит в стандарт, дальше идёт её реализация в JS-движках, потом — изучение программистами и практическое применение.
Вклад в этот процесс тех, кто работает над Test262, заключается в том, чтобы, разобравшись со стандартами, подготовить испытания, проверяющие новые языковые конструкции с учётом уже существующих. Такой подход означает огромнейшую работу по созданию тестов, которую нерационально возлагать на человека. Например, проверка аргументов по умолчанию должна быть проведена со всеми формами функций, в подобных вещах нельзя ограничиться, скажем, только простой формой объявления функции. Всё это привело к разработке инструментария для создания тестов, что позволяет говорить о том, что испытаниям подвергается практически всё, что можно проверить.
Сейчас проект содержит набор файлов с исходным кодом [10], которые состоят из разных тестовых сценариев и шаблонов [11].
Например, тут [12] можно посмотреть, как проверяется свойство функции arguments
, тут [12] — тесты различных форм функций. Конечно, в Test262 есть ещё много всего. Скажем, вот [13] и вот [14] — тесты, связанные с деструктурированием. В процессе работы над тестами получаются немаленькие pull-запросы, обнаруживаются и исправляются ошибки [15]. Всё это ведёт к постоянному росту качества Test262, а значит, к улучшению проверок JS-движков на соответствие спецификации EcmaScript. Это имеет прямое воздействие на JavaScript-индустрию. Чем больше программных конструкций будет идентифицировано и покрыто тестами, тем легче разработчикам движков будет внедрять новые возможности, тем стабильнее и надёжнее, в итоге, будут работать программы на JavaScript.
Уважаемые читатели! Какими способами определения функций в JavaScript вы пользуетесь чаще всего?
Автор: RUVDS.com
Источник [16]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/259271
Ссылки в тексте:
[1] Test262: https://github.com/tc39/test262
[2] Image: https://habrahabr.ru/company/ruvds/blog/332020/
[3] первой редакции: https://www.ecma-international.org/publications/files/ECMA-ST-ARCH/ECMA-262,%201st%20edition,%20June%201997.pdf
[4] третьей редакции: https://www.ecma-international.org/publications/files/ECMA-ST-ARCH/ECMA-262,%203rd%20edition,%20December%201999.pdf
[5] Вот: https://bocoup.com/blog/whats-in-a-function-name
[6] ES2015: https://www.ecma-international.org/ecma-262/6.0/index.html
[7] связанное имя: https://tc39.github.io/ecma262/#sec-function-definitions-static-semantics-boundnames
[8] Babel: https://babeljs.io/
[9] здесь: https://github.com/tc39/proposal-async-iteration
[10] файлов с исходным кодом: https://github.com/tc39/test262/tree/master/src
[11] шаблонов: https://github.com/tc39/test262/tree/master/src/function-forms/default
[12] тут: https://github.com/tc39/test262/tree/master/src/arguments
[13] вот: https://github.com/tc39/test262/tree/master/src/dstr-binding
[14] вот: https://github.com/tc39/test262/tree/master/src/dstr-assignment
[15] ошибки: https://github.com/tc39/test262/pull/651
[16] Источник: https://habrahabr.ru/post/332020/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.