9.4 KiB
Function object, NFE
As we already know, functions in Javascript are values.
Every value in Javascript has the type. What type of value is a function?
In Javascript, a function is an object.
From one side, they are callable "actions". From the other side, we can treat them as objects: add/remove properties, pass by reference etc.
The "name" property
A function name is accessible as the "name" property.
For Function Declarations, it's obvious:
function sayHi() {
alert("Hi");
}
alert(sayHi.name); // sayHi
But what's more funny, the name-assigning logic is smart. It also sticks the right name to function that are used in assignments:
let sayHi = function() {
alert("Hi");
}
alert(sayHi.name); // sayHi (works!)
Also here, as a default parameter:
function f(sayHi = function() {}) {
alert(sayHi.name); // sayHi (works!)
}
f();
In the specification, this is called a "contextual name". The function itself does not provide one, but in an assignment it's easy to figure out from context.
Object methods have names too:
let user = {
sayHi() {
// ...
},
sayBye: function() {
// ...
}
}
alert(user.sayHi.name); // sayHi
alert(user.sayBye.name); // sayBye
There's no magic though. In other cases, when there's no way to figure out the right name, it's empty, like here:
// function created inside array
let arr = [function() {}];
alert( arr[0].name ); // <empty string>
// the engine has no way to set up the right name, so there is none
In practice, most functions do have a name. It's mostly used for debugging and checking.
The "length" property
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.
The length
property is sometimes used for introspection: what kind of function we have?
For instance, in the code below ask
function accepts question
to ask and an arbitrary number of handler
functions to call.
Each handler may be either a zero-argument function, then it is only called for a positive answer, or a single(or more)-argument function, then it is called with any answer as an argument.
function ask(question, ...handlers) {
let isYes = confirm(question);
for(let handler of handlers) {
if (handler.length == 0) {
if (isYes) handler();
} else {
handler(isYes);
}
}
}
// for positive answer, both handlers are called
// for negative answer, only the second one
ask("Question?", () => alert('You said yes'), result => alert(result));
This is a particular case of polymorphism -- treating arguments differently depending on their type -- or, in our case of their length
.
Custom properties
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 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.
We can treat a function as an object, store properties in it, but that has no effect on its execution. Variables never use function properties and vise versa. These are just parallel words.
Also we can rewrite the counter example from the chapter info:closure to use function property instead of the closure:
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
Unlike examples from chapter info:closure, the count
is now stored in the function directly, not in its outer Lexical Environment.
Is it worse or better than using the closure?
The main difference is that if the value of count
lives in an outer variable, then an external code is unable to access it. Only nested functions may modify it. And if it's bound to function, then such thing is possible:
function makeCounter() {
function counter() {
return counter.count++;
};
counter.count = 0;
return counter;
}
let counter = makeCounter();
*!*
counter.count = 10;
alert( counter() ); // 10
*/!*
So it depends on our aims which variant to choose.
Named Function Expression
Named Function Expression or, shortly, NFE, is a term a Function Expression that has 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}`);
};
What's the role 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
:
- It allows to reference the function from inside itself.
- It is not visible outside of the function.
For instance, the function sayHi
below re-calls itself 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 exactly meant to solve this kind of problems.
Let's use it to fix the code:
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 always references the current function.
The outer code still has it's variable sayHi
or welcome
later. And func
is an "internal function name", how it calls itself privately.
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 for them.
Sometimes, when we need a reliable internal name, it's the reason to rewrite a Function Declaration to Named Function Expression form.
Summary
Functions are objects.
Here we covered their properties:
name
-- the function name. Exists not only when given in the function definition, but also for assignments and object properties.length
-- the number of arguments in the function definition. Rest parameters are not counted.
If the function is declared as a Function Expression (not in the main code flow), and it carries the name, then it is called Named Function Expression. The name can be used inside to reference itself, for recursive calls or such.
Also, functions may carry additional properties. Many well-known Javascript libraries make a great use of this feature.
They create a "main" function and attach many other "helper" functions to it. For instance, the jquery library creates a function named $
. The lodash library creates a function _
. And then adds _.clone
, _.keyBy
and other properties to (see the docs when you want learn more about them). Actually, they do it to less pollute the global space, so that a single library gives only one global variable. That lowers the chance of possible naming conflicts.
So, a function can do a useful job by itself and also carry a bunch of other functionality in properties.