Именованные функциональные выражения
Материал на этой странице устарел, поэтому скрыт из оглавления сайта.
Более новая информация по этой теме находится на странице https://learn.javascript.ru/function-object.
Специально для работы с рекурсией в JavaScript существует особое расширение функциональных выражений, которое называется «Named Function Expression» (сокращённо NFE) или, по-русски, «именованное функциональное выражение».
Named Function Expression
Обычное функциональное выражение:
Именованное с именем sayHi :
Что же это за имя, которое идёт в дополнение к f , и зачем оно?
Имя функционального выражения ( sayHi ) имеет особый смысл. Оно доступно только изнутри самой функции ( f ).
Это ограничение видимости входит в стандарт JavaScript и поддерживается всеми браузерами, кроме IE8-.
var f = function sayHi(name) < alert( sayHi ); // изнутри функции - видно (выведет код функции) >; alert( sayHi ); // снаружи - не видно (ошибка: undefined variable 'sayHi')
Кроме того, имя NFE нельзя перезаписать:
var test = function sayHi(name) < sayHi = "тест"; // попытка перезаписи alert( sayHi ); // function. (перезапись не удалась) >; test();
В режиме use strict код выше выдал бы ошибку.
Как правило, имя NFE используется для единственной цели – позволить изнутри функции вызвать саму себя.
Пример использования
NFE используется в первую очередь в тех ситуациях, когда функцию нужно передавать в другое место кода или перемещать из одной переменной в другую.
Внутреннее имя позволяет функции надёжно обращаться к самой себе, где бы она ни находилась.
Вспомним, к примеру, функцию-факториал из задачи Вычислить факториал:
function f(n) < return n ? n * f(n - 1) : 1; >; alert( f(5) ); // 120
Попробуем перенести её в другую переменную g :
function f(n) < return n ? n * f(n - 1) : 1; >; var g = f; f = null; alert( g(5) ); // запуск функции с новым именем - ошибка при выполнении!
Ошибка возникла потому что функция из своего кода обращается к своему старому имени f . А этой функции уже нет, f = null .
Для того, чтобы функция всегда надёжно работала, объявим её как Named Function Expression:
var f = function factorial(n) < return n ? n*factorial(n-1) : 1; >; var g = f; // скопировали ссылку на функцию-факториал в g f = null; alert( g(5) ); // 120, работает!
Как мы говорили выше, в браузере IE до 9 версии имя NFE видно везде, что является ошибкой с точки зрения стандарта.
…Но на самом деле ситуация ещё забавнее. Старый IE создаёт в таких случаях целых две функции: одна записывается в переменную f , а вторая – в переменную factorial .
var f = function factorial(n) < /*. */ >; // в IE8- false // в остальных браузерах ошибка, т.к. имя factorial не видно alert( f === factorial );
Все остальные браузеры полностью поддерживают именованные функциональные выражения.
Если вы давно работаете с JavaScript, то, возможно, знаете, что раньше для этой цели также служило специальное значение arguments.callee .
Если это название вам ни о чём не говорит – всё в порядке, читайте дальше, мы обязательно обсудим его в отдельной главе.
Если же вы в курсе, то стоит иметь в виду, что оно официально исключено из современного стандарта. А NFE – это наше настоящее.
Итого
Если функция задана как Function Expression, ей можно дать имя.
Оно будет доступно только внутри функции (кроме IE8-).
Это имя предназначено для надёжного рекурсивного вызова функции, даже если она записана в другую переменную.
Обратим внимание, что с Function Declaration так поступить нельзя. Такое «специальное» внутреннее имя функции задаётся только в синтаксисе Function Expression.
Javascript function names var
The var statement declares function-scoped or globally-scoped variables, optionally initializing each to a value.
Try it
Syntax
var name1; var name1 = value1; var name1 = value1, name2 = value2; var name1, name2 = value2; var name1 = value1, name2, /* …, */ nameN = valueN;
The name of the variable to declare. Each must be a legal JavaScript identifier.
Initial value of the variable. It can be any legal expression. Default value is undefined .
The destructuring syntax can also be used to declare variables.
var bar > = foo; // where foo = < bar: 10, baz: 12 >; // This creates a variable with the name 'bar', which has a value of 10
Description
The scope of a variable declared with var is one of the following curly-brace-enclosed syntaxes that most closely contains the var statement:
Or if none of the above applies:
- The current module, for code running in module mode
- The global scope, for code running in script mode.
function foo() var x = 1; function bar() var y = 2; console.log(x); // 1 (function `bar` closes over `x`) console.log(y); // 2 (`y` is in scope) > bar(); console.log(x); // 1 (`x` is in scope) console.log(y); // ReferenceError, `y` is scoped to `bar` > foo();
Importantly, other block constructs, including block statements, try. catch , switch , headers of one of the for statements, do not create scopes for var , and variables declared with var inside such a block can continue to be referenced outside the block.
for (var a of [1, 2, 3]); console.log(a); // 3
In a script, a variable declared using var is added as a non-configurable property of the global object. This means its property descriptor cannot be changed and it cannot be deleted using delete . JavaScript has automatic memory management, and it would make no sense to be able to use the delete operator on a global variable.
"use strict"; var x = 1; Object.hasOwn(globalThis, "x"); // true delete globalThis.x; // TypeError in strict mode. Fails silently otherwise. delete x; // SyntaxError in strict mode. Fails silently otherwise.
In both NodeJS CommonJS modules and native ECMAScript modules, top-level variable declarations are scoped to the module, and are not added as properties to the global object.
The list that follows the var keyword is called a binding list and is separated by commas, where the commas are not comma operators and the = signs are not assignment operators. Initializers of later variables can refer to earlier variables in the list and get the initialized value.
Hoisting
var declarations, wherever they occur in a script, are processed before any code within the script is executed. Declaring a variable anywhere in the code is equivalent to declaring it at the top. This also means that a variable can appear to be used before it’s declared. This behavior is called hoisting, as it appears that the variable declaration is moved to the top of the function, static initialization block, or script source in which it occurs.
Note: var declarations are only hoisted to the top of the current script. If you have two elements within one HTML, the first script cannot access variables declared by the second before the second script has been processed and executed.
This is implicitly understood as:
For that reason, it is recommended to always declare variables at the top of their scope (the top of global code and the top of function code) so it’s clear which variables are scoped to the current function.
Only a variable’s declaration is hoisted, not its initialization. The initialization happens only when the assignment statement is reached. Until then the variable remains undefined (but declared):
function doSomething() console.log(bar); // undefined var bar = 111; console.log(bar); // 111 >
This is implicitly understood as:
function doSomething() var bar; console.log(bar); // undefined bar = 111; console.log(bar); // 111 >
Redeclarations
Duplicate variable declarations using var will not trigger an error, even in strict mode, and the variable will not lose its value, unless the declaration has an initializer.
var a = 1; var a = 2; console.log(a); // 2 var a; console.log(a); // 2; not undefined
var declarations can also be in the same scope as a function declaration. In this case, the var declaration’s initializer always overrides the function’s value, regardless of their relative position. This is because function declarations are hoisted before any initializer gets evaluated, so the initializer comes later and overrides the value.
var a = 1; function a() > console.log(a); // 1
var declarations cannot be in the same scope as a let , const , class , or import declaration.
var a = 1; let a = 2; // SyntaxError: Identifier 'a' has already been declared
Because var declarations are not scoped to blocks, this also applies to the following case:
let a = 1; var a = 1; // SyntaxError: Identifier 'a' has already been declared >
It does not apply to the following case, where let is in a child scope of var , not the same scope:
A var declaration within a function’s body can have the same name as a parameter.
function foo(a) var a = 1; console.log(a); > foo(2); // Logs 1
A var declaration within a catch block can have the same name as the catch -bound identifier, but only if the catch binding is a simple identifier, not a destructuring pattern. This is a deprecated syntax and you should not rely on it. In this case, the declaration is hoisted to outside the catch block, but any value assigned within the catch block is not visible outside.
try throw 1; > catch (e) var e = 2; // Works > console.log(e); // undefined