Function object, NFE

You already know that a function is a value in JavaScript — you can assign it to a variable, pass it around, and stash it in an array. Every value has a type, so it’s fair to ask: what type is a function?

Run typeof on one and you get "function", but that’s a convenience. Under the hood a function is an object. A special kind of object, one you can call, but an object all the same.

That single fact unlocks a lot. Because a function is an object, you can read properties off it, attach your own, and pass it by reference like any other object. A useful mental picture is a callable action object: a thing that does something when you invoke it, and that can also carry data on the side.

greet  function object
[[call]]runs the body
name“greet”
length0
counter(yours to add)
A function is an object you can invoke — it holds built-in properties and can carry your own.

The “name” property

Function objects come with a few built-in properties worth knowing. The first is name, which holds the function’s name as a string:

function greet() {
  alert("Hi");
}

alert(greet.name); // greet

Here’s the neat part: JavaScript figures out the name even when you don’t spell it out. Assign an anonymous function expression to a variable, and the engine copies the variable’s name onto the function:

let greet = function() {
  alert("Hi");
};

alert(greet.name); // greet (there's a name!)

The same thing happens when a function arrives as a default parameter value:

function f(greet = function() {}) {
  alert(greet.name); // greet (works!)
}

f();

The specification calls this a contextual name. When the function itself carries no name, the engine tries to infer one from the surrounding assignment.

Object methods get named too, whether you write them with the shorthand method syntax or assign a function expression to a property:

let menu = {

  open() {
    // ...
  },

  close: function() {
    // ...
  }

}

alert(menu.open.name); // open
alert(menu.close.name); // close

This isn’t magic, though, and the inference has limits. Some positions give the engine nothing to work with. Put a function inside an array literal and there’s no variable or property to borrow a name from, so name comes back empty:

// function created inside an array
let arr = [function() {}];

alert( arr[0].name ); // <empty string>
// the engine has no context to derive a name from, so there is none

In everyday code most functions land in a named variable, property, or declaration, so most of them do end up with a name.

function greet(){}“greet”(declaration)
let f = function(){}“f”(contextual)
{ open(){} }“open”(method)
[function(){}]“”(no context)
Where a function's name comes from, depending on how it's created.

Run the inspector below to watch the engine derive a name from each context — and come up empty when there’s nothing to borrow. Edit the code to add your own definitions and see what name reports.

interactiveWhere does .name come from?

The “length” property

Another built-in property, length, reports how many parameters the function declares:

function f1(a) {}
function f2(a, b) {}
function many(a, b, ...more) {}

alert(f1.length); // 1
alert(f2.length); // 2
alert(many.length); // 2

Notice that rest parameters don’t count — many declares a, b, and ...more, but length reports 2. Parameters with default values and anything after them are also left out of the count, which is worth remembering if you ever rely on this number.

length shows up in introspection: code that inspects other functions and adapts to their shape. Consider a poll function that takes a question plus any number of handler functions to run once the user answers. Two flavors of handler are allowed:

  • A zero-argument handler, run only when the user answers positively.
  • A handler that takes an argument, run in every case and handed the result.

To call each handler correctly, poll peeks at handler.length. That gives you a tidy no-argument syntax for the common positive-only case, while still supporting handlers that want to see the answer:

function poll(question, ...handlers) {
  let isYes = confirm(question);

  for(let handler of handlers) {
    if (handler.length == 0) {
      if (isYes) handler();
    } else {
      handler(isYes);
    }
  }

}

// on "yes", both handlers run
// on "no", only the second one runs
poll("Ship it?", () => alert('You said yes'), result => alert(result));
handler.length
== 0 ?
yes → 0 args
call only if isYes
no → has args
call always, pass isYes
poll() branches on each handler's declared arity.

The demo below is a browser-friendly poll: the buttons stand in for the yes/no answer. A zero-argument handler only fires on “Yes”, while a one-argument handler fires either way and receives the answer. poll decides which is which purely from handler.length.

interactiveDispatching on handler.length

This is a small taste of polymorphism — treating an argument differently based on its type or, here, its arity. Several JavaScript libraries lean on this pattern to offer flexible APIs.

Custom properties

Since a function is an object, you can attach your own properties to it. Here we add a counter property that tracks how many times the function has run:

function greet() {
  alert("Hi");

  // count how many times we've been called
  greet.counter++;
}
greet.counter = 0; // initial value

greet(); // Hi
greet(); // Hi

alert( `Called ${greet.counter} times` ); // Called 2 times

Click the button below to call greet for real. Each call runs the body and bumps greet.counter, a property living on the function object itself:

interactiveA counter stored on the function

Sometimes a function property can stand in for a closure. Recall the counter from Variable scope, closure. Here is the same idea as a ticket dispenser, but with the running number kept on the function object instead of in an enclosing scope:

function makeTicketDispenser() {
  // instead of:
  // let number = 0

  function next() {
    return next.number++;
  };

  next.number = 0;

  return next;
}

let next = makeTicketDispenser();
alert( next() ); // 0
alert( next() ); // 1

Now number lives directly on the next function, not in the outer lexical environment. Is that better or worse than the closure version? It comes down to visibility.

With a closure, number sits in an outer variable that nothing outside can touch — only the nested function can read or change it. That’s genuine privacy. When you store the value as a property, it’s exposed, so outside code can reach in and change it:

function makeTicketDispenser() {

  function next() {
    return next.number++;
  };

  next.number = 0;

  return next;
}

let next = makeTicketDispenser();

next.number = 10;
alert( next() ); // 10
let number (closure)
private
reachable only by nested code
next.number (property)
public
any code with the reference can set it
Closure hides the state; a function property exposes it.

So neither approach wins outright — pick based on whether the state should be sealed off or open to callers.

Named Function Expression

A Named Function Expression, or NFE, is just a function expression that carries a name. Start with a plain, anonymous one:

let greet = function(who) {
  alert(`Hello, ${who}`);
};

Now drop a name between function and the parentheses:

let greet = function self(who) {
  alert(`Hello, ${who}`);
};

Did that change anything? It’s still a function expression — the name after function did not turn it into a function declaration, because the whole thing is created as part of an assignment. And it broke nothing: the function is still reachable through greet exactly as before.

let greet = function self(who) {
  alert(`Hello, ${who}`);
};

greet("Maya"); // Hello, Maya

The extra name self earns its keep for two reasons:

  1. It lets the function refer to itself from the inside.
  2. It’s invisible everywhere outside the function body.

Here greet calls itself with "Guest" when who is missing, using its internal name self:

let greet = function self(who) {
  if (who) {
    alert(`Hello, ${who}`);
  } else {
    self("Guest"); // call itself using the internal name
  }
};

greet(); // Hello, Guest

// But this fails outside:
self(); // Error, self is not defined (not visible outside the function)

Fair question: why bother with self when we could just call greet from inside? Most of the time you can:

let greet = function(who) {
  if (who) {
    alert(`Hello, ${who}`);
  } else {
    greet("Guest");
  }
};

The catch is that greet is an outer variable, and outer variables can change. Reassign the function to a different variable and null out the original, and the self-call collapses:

let greet = function(who) {
  if (who) {
    alert(`Hello, ${who}`);
  } else {
    greet("Guest"); // Error: greet is not a function
  }
};

let alias = greet;
greet = null;

alias(); // Error, the nested greet call no longer works!

The function reads greet from its outer lexical environment. There’s no local greet, so it uses the outer one — and by the time alias() runs, that outer greet has been set to null.

calls outer greet
alias = greet
greet = null
alias() → looks up greet → null → 💥
calls internal self
alias = greet
greet = null
alias() → self always = this fn → ✓
Outer-variable self-reference vs. the internal NFE name.

The optional name on a function expression exists precisely to sidestep this. Switch back to self and the code holds up:

let greet = function self(who) {
  if (who) {
    alert(`Hello, ${who}`);
  } else {
    self("Guest"); // still fine
  }
};

let alias = greet;
greet = null;

alias(); // Hello, Guest (the nested call works)

It works because self is function-local. It isn’t pulled from the outer scope and isn’t visible there. The specification guarantees self always points at the current function, no matter what happens to the outer variables that reference it.

So the outer code keeps its greet or alias variables, while self serves as a private, dependable handle the function uses to call itself — for recursion or any other self-reference.

Try both versions below. Each one copies the function into alias, sets the original variable to null, then calls alias(). The outer-variable self-call breaks; the NFE self-call keeps working:

interactiveOuter variable vs. internal NFE name

Summary

Functions are objects, and this article covered what that buys you.

Two built-in properties:

  • name — the function’s name. Usually it comes straight from the definition, and when the function is anonymous JavaScript tries to infer a name from context, such as the variable it’s assigned to.
  • length — the number of declared parameters, not counting rest parameters (or parameters with defaults and anything after them).

A function expression that carries its own name is a Named Function Expression. That internal name is visible only inside the function and reliably references the function itself, which makes it the safe way to write recursion or other self-calls.

You can also hang your own properties on a function. Many well-known libraries build on this: they expose one main function and bolt helpers onto it. jQuery ships a function called $; lodash ships _ and then adds _.clone, _.keyBy, and the rest as properties (their docs list them all). Packing everything onto a single object keeps the global namespace clean — one library, one global — which cuts down on naming clashes.

A function, then, can do a job on its own and carry a whole toolkit of related functionality in its properties.