JavaScript: Что в имени тебе моем

В работе над каждым следующим проектом, я узнаю что-то новое о javascript. Мое последнее невероятное открытие принесло мне знание о значимости атрибута name функций в javascript.

JSHint — интересная штука, имеет одну особенность: сообщения анализатора кода. Результат анализа кода, включает в себя информацию об объектах-функциях, содержащихся в коде:

[js]jshint(‘function myFn() {}’);
console.log(jshint.data().functions);
/*
[{
name: ‘myFn’,
param: undefined,
line: 1,
character: 15,
last: 1,
lastcharacter: 19,
metrics: { complexity: 1, parameters: 0, statements: 0 }
}]
*/[/js]

Самый логичный способ использования описан на сайте JSHint, он предоставляет данные в реальном времени:

[js]Metrics

There is only one function in this file.
It takes no arguments.
This function is empty.
Cyclomatic complexity number for this function is 1.[/js]

Сначала я подумал, что это не верная реализация или какой-то баг. Чем больше я размышлял над этим, тем больше понимал, что это мое представление об именовании функций в javascript было ошибочным. После нескольких часов раздумий, я нашел причину такого поведения и уяснил для себя, раз и навсегда.

Мы думаем, что мы знаем

Для начала надо объяснить, как привязываются имена в javascript.

Я привык различать функции как объекты и функции-выражения. Первый вариант требует идентификатор, что я расценивал как именованную функцию:

[js]function myFunction() {

}[/js]

Второй вариант не требует идентификатора и потому называется анонимной функцией:

[js](function() {

}());[/js]

Эти логичные названия «именованная» и «анонимная» и могут ввести в заблуждение. При чем не только меня, но и многих других людей: сегодня спецификация javascript ECMAScript 5.1 или ES5, не дает гарантии на счет атрибута name функции. Беглое ознакомление со спецификацией убедило меня: идентификатор, который мы обычно принимаем за имя функции, используется только для создания точки входа, подобно конструкции var. Все остальное может зависеть от платформы.

Я знаю, что ничего не знаю

Следующая спецификация javascript ES6 описывает инициализацию атрибута name функции. Любопытно, что все зависит от одной только абстрактной операции, названной SetFunctionName. Понять работу ее не сложно, достаточно посмотреть на атрибуты и параметры, а также на места использования ее в ES6.  Главным образом это будет полезно разработчикам платформ, а для нас есть несколько примеров использования.

Во-первых, спецификация определяет поведения, которые мы и привыкли ожидать:

[js]// описание функции ……………….. значение атрибута "name"
function myFunc() {} // ‘myFunc;
(function() {}()); // »
[/js]

Но на этом мы не остановимся! Спецификация описывает несколько ситуаций, где анонимная функция будет иметь имя:

[js]// описание функции ……………….. значение атрибута "name"
new Function(); // ‘anonymous’
var toVar = function() {}; // ‘toVar’
(function() {}).bind(); // ‘bound’

var obj = {
myMethod: function() {}, // ‘myMethod’
get myGetter() {}, // ‘get myGetter’
set mySetter(value) {} // ‘set mySetter’
};[/js]

Что бы было понятнее: новая спецификация изменяет name-свойство объекта-функции только в этих случаях. При использовании ES5 синтаксиса, поведение остается прежним. Описание функции создает новую точку входа.

Такое поведение удивляет меня, так как описывая функцию я не привык присваивать ее переменной для создания объекта. В ES6 это так! Идентификатор функции не определяет ее объект, но во время выполнения может послужить способом ее вызвать.

И, наконец, спецификация ES6 описывает несколько новых форм кода, которые не будут валидными для ES5. Некоторые примеры новых семантических приемов:

[js]// описание функции ……………….. значение атрибута "name"
let toLet = function() {}; // ‘toLet’
const toConst = function() {}; // ‘toConst’
export default function() {} // ‘default’
function* myGenerator() {} // ‘myGenerator’
new GeneratorFunction() {} // ‘anonymous’

var obj = {
[‘exp’ + ‘ression’]: function() {}, // ‘expression’
myConciseMethod() {}, // ‘myConciseMethod’
*myGeneratorMethod() {} // ‘myGeneratorMethod’
};

class MyClass {
constructor() {} // ‘MyClass’
myClassMethod() {} // ‘myClassMethod’
}[/js]

Больше всего меня удивляет последний пример: функции constructor присвоено имя класса, а не constructor! Для большинства методов, краткая форма присвоит имя, которое вполне можно ожидать. Методы — конструкторы отличаются потому, что они ссылаются на класс, которому они принадлежат. На примере ES5:

[js]function MyClass() {}
MyClass.prototype.constructor === MyClass;[/js]

Похожим способом работает и ES6, хотя ключевое слово class и функция constructor принадлежат разным выражениям.

Отклонения — не всегда плохо

Даже имея перед глазами спецификацию, в процессе разработки не может быть все гладко. Иногда приходится отклоняться от стандарта.

Выражения

Во многих случаях, разработчики должны вызывать SetFunctionName с результатом какого-то выражения. (пусть propKey будет результатом PropertyName. SetFunction(propValue, propKey)). В данном случае JSHint, являющийся статическим анализатором кода, не вычисляет никакие выражения и в результате функция будет именована «(выражение)».

Безымянные

Спецификация говорит: «If description is undefined, then let name be the empty String.» Это означает, что функции объявленной так:

[js](function() {

})();[/js]

должно быть присвоено имя «». JSHint, являясь инструментом, может свободно интерпретировать спецификацию в интересах пользователей и отображает в таких случаях имя функции: «(empty)».

Как корабль назовешь…

Я не уделял должного внимания подобным нововведениям в следующие версии javascript. Возможно, пессимисты и сочтут это ненужными усложнением прекрасного js, но я считаю, что это толкает его вперед, в ногу со временем.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *