As we already know, functions in JavaScript are values. Every value in JavaScript has a type. What type is a function? In JavaScript, functions are objects. We can not only call them, but also treat them as objects: add/remove properties, pass by reference etc.
A function’s name is accessible as the “name” property:
function sayHi() { alert("Hi"); } alert(sayHi.name); // sayHi
What’s more funny, the name-assigning logic is smart. It also assigns the correct name to functions that are used in assignments:
let sayHi = function() { alert("Hi"); } alert(sayHi.name); // sayHi (works!)
It also works if the assignment is done via a default value:
function f(sayHi = function() {}) { alert(sayHi.name); // sayHi (works!) } f();
In the specification, this feature is called a “contextual name”. If the function does not provide one, then in an assignment it is figured out from the context.
Object methods have names too:
let user = { sayHi() { // ... }, sayBye: function() { // ... } } alert(user.sayHi.name); // sayHi alert(user.sayBye.name); // sayBye
There is another built-in property “length” that returns the number of function parameters, for instance:
function f1(a) {} function f2(a, b) {} function many(a, b, ...more) {} alert(f1.length); // 1 alert(f2.length); // 2 alert(many.length); // 2
Here we can see that rest parameters are not counted.
In most cases the length is equals to arguments.length
. For example -
function check(a, b){ console.log(arguments.length == check.length); // true }
We can also add properties of our own. Here we add the counter
property to track the total calls count:
function sayHi() { alert("Hi"); // let's count how many times we run sayHi.counter++; } sayHi.counter = 0; // initial value sayHi(); // Hi sayHi(); // Hi alert( `Called ${sayHi.counter} times` ); // Called 2 times
A property is not a variable.
A property assigned to a function like sayHi.counter = 0
does not define a local variable counter
inside it. In other words, a property counter
and a variable let counter
are two unrelated things.
Function properties can replace closures sometimes. For instance, we can rewrite the counter function example from the chapter Closure to use a function property:
function makeCounter() { // instead of: // let count = 0 function counter() { return counter.count++; }; counter.count = 0; return counter; } let counter = makeCounter(); alert( counter() ); // 0 alert( counter() ); // 1
The count
is now stored in the function directly, not in its outer Lexical Environment.
Is it better or worse than using a closure?
The main difference is that if the value of count
lives in an outer variable (Lexical Environment), then external code is unable to access it. Only nested functions may modify it. And if it’s bound to a function, then such a thing is possible:
function makeCounter() { function counter() { return counter.count++; }; counter.count = 0; return counter; } let counter = makeCounter(); counter.count = 10; alert( counter() ); // 10 alert( counter() ); // 11
Named Function Expression, or NFE, is a term for Function Expressions that have a name. For instance, let’s take an ordinary Function Expression:
let sayHi = function(who) { alert(`Hello, ${who}`); };
And add a name to it:
let sayHi = function func(who) { alert(`Hello, ${who}`); };
Did we achieve anything here? What’s the purpose of that additional "func" name?
First let’s note, that we still have a Function Expression. Adding the name "func" after function did not make it a Function Declaration, because it is still created as a part of an assignment expression. Adding such a name also did not break anything. The function is still available as sayHi()
:
let sayHi = function func(who) { alert(`Hello, ${who}`); }; sayHi("John"); // Hello, John
There are two special things about the name func
:
For instance, the function sayHi
below calls itself again with "Guest" if no who
is provided:
let sayHi = function func(who) { if (who) { alert(`Hello, ${who}`); } else { func("Guest"); // use func to re-call itself } }; sayHi(); // Hello, Guest // But this won't work: func(); // Error, func is not defined (not visible outside of the function)
Why do we use func
? Maybe just use sayHi
for the nested call? Actually, in most cases we can:
let sayHi = function(who) { if (who) { alert(`Hello, ${who}`); } else { sayHi("Guest"); } };
The problem with that code is that the value of sayHi
may change. The function may go to another variable, and the code will start to give errors:
let sayHi = function(who) { if (who) { alert(`Hello, ${who}`); } else { sayHi("Guest"); // Error: sayHi is not a function } }; let welcome = sayHi; sayHi = null; welcome(); // Error, the nested sayHi call doesn't work any more!
That happens because the function takes sayHi
from its outer lexical environment. There’s no local sayHi
, so the outer variable is used. And at the moment of the call that outer sayHi
is null
. The optional name which we can put into the Function Expression is meant to solve exactly these kinds of problems.
let sayHi = function func(who) { if (who) { alert(`Hello, ${who}`); } else { func("Guest"); // Now all fine } }; let welcome = sayHi; sayHi = null; welcome(); // Hello, Guest (nested call works)
Now it works, because the name "func" is function-local. It is not taken from outside (and not visible there). The specification guarantees that it will always reference the current function.
The “internal name” feature described here is only available for Function Expressions, not to Function Declarations. For Function Declarations, there’s just no syntax possibility to add a one more “internal” name. Sometimes, when we need a reliable internal name, it’s the reason to rewrite a Function Declaration to Named Function Expression form.
WriteSolution function sum that would work like this:
sum(1)(2) == 3; // 1 + 2 sum(1)(2)(3) == 6; // 1 + 2 + 3 sum(5)(-1)(2) == 6 sum(6)(-1)(-2)(-3) == 0 sum(0)(1)(2)(3)(4)(5) == 15
function sum(a) { let currentSum = a; function f(b) { currentSum += b; return f; } f.toString = function() { return currentSum; }; return f; } alert( sum(1)(2) ); // 3 alert( sum(5)(-1)(2) ); // 6 alert( sum(6)(-1)(-2)(-3) ); // 0 alert( sum(0)(1)(2)(3)(4)(5) ); // 15
There’s one more way to create a function. It’s rarely used, but sometimes there’s no alternative. The syntax for creating a function:
let func = new Function ([arg1[, arg2[, ...argN]],] functionBody)
In other words, function parameters (or, more precisely, names for them) go first, and the body is last. All arguments are strings. It’s easier to understand by looking at an example. Here’s a function with two arguments:
let sum = new Function('a', 'b', 'return a + b'); alert( sum(1, 2) ); // 3
If there are no arguments, then there’s only a single argument, the function body:
let sayHi = new Function('alert("Hello")'); sayHi(); // Hello
The major difference from other ways we’ve seen is that the function is created literally from a string, that is passed at run time. All previous declarations required us, programmers, to write the function code in the script. But new Function
allows to turn any string into a function. For example, we can receive a new function from a server and then execute it:
let str = ... receive the code from a server dynamically ... let func = new Function(str); func();
It is used in very specific cases, like when we receive code from a server, or to dynamically compile a function from a template. The need for that usually arises at advanced stages of development.
Usually, a function remembers where it was born in the special property [[Environment]]
. It references the Lexical Environment from where it’s created.
But when a function is created using new Function
, its [[Environment]]
references not the current Lexical Environment, but instead the global one.
function getFunc() { let value = "test"; let func = new Function('alert(value)'); return func; } getFunc()(); // error: value is not defined
Compare it with the regular behavior:
function getFunc() { let value = "test"; let func = function() { alert(value); }; return func; } getFunc()(); // "test", from the Lexical Environment of getFunc
This special feature of new Function looks strange, but appears very useful in practice. Imagine that we must create a function from a string. The code of that function is not known at the time of writing the script (that’s why we don’t use regular functions), but will be known in the process of execution. We may receive it from the server or from another source. Our new function needs to interact with the main script. Perhaps we want it to be able to access outer local variables. In this case the new Function
feature saves us.