cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

Functions are objects

Inspect JavaScript functions as callable objects: name, length, inferred names, custom properties, constructors, toString, and safe forwarding wrappers.

By the end, you can
  • 01
    Inspect a function objectRead name, length, typeof, instanceof Function, and source text honestly.
  • 02
    Use names and properties carefullyExplain inference, named expressions, custom metadata, and minification caveats.
  • 03
    Write safe wrappersForward this and arguments with apply, and know why new Function is dangerous.

Functions are callable objects

You already know functions as reusable actions: call them with parentheses, pass values in, and get a result back. This lesson adds the other half of the picture: in JavaScript, a function is also an object. It can have properties, it has a prototype chain, and other code can inspect its built-in details.

That is why save.name, save.length, save.count, save.toString(), save.bind(...), and Object.defineProperty(save, ...) all make sense. The function is still callable, but it is also a value with labels attached.

Real-life analogyA phone contact with a label

A phone contact can be called, has a name, and can carry a note. A JavaScript function is similar: it can run and also has object properties you can read or add.

In real life: Tap the contact to call
In JavaScript: A function can be called
In real life: The contact has a name
In JavaScript: fn.name is JavaScript’s label
In real life: The contact shows needed details
In JavaScript: fn.length hints how many leading parameters it expects
In real life: Add a note to the contact
In JavaScript: Custom properties like fn.count or fn.meta

Where the analogy stops: A phone contact is easy to rename. Function names can also change, so use them as debugging hints, not core data.

We will cover the topics in the curriculum order: name and length, name inference, custom properties, named function expressions, new Function, generator and async function constructors, Function.prototype.toString, and forwarding wrappers (sometimes called decorators, without the later @ syntax).

name & length: the label and the slots

STEP THROUGH

Every function object has a name string and a length number. name is the best label JavaScript can infer. length is the number of parameters before the first default parameter. Rest parameters never count.

Inspect name and length
Step 0 of 4Ready
Your turn: follow the blue line

Step through four functions and watch JavaScript record their labels (name) and arity hints (length).

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
  return a + b;}const inferred = function (first, second = 2, ...rest) {  return first + second + rest.length;};const arrow = (value, index) => value + index;const bound = declared.bind(null, 1);console.log(declared.name, declared.length);console.log(inferred.name, inferred.length);console.log(arrow.name, arrow.length);console.log(bound.name, bound.length);
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
Common function shapes
KindExamplenamelength
Declarationfunction add(a, b) {}add2
Anonymous expression assigned to constconst add = function (a, b) {}add2
Default before later parametersfunction f(a, b = 1, c) {}f1
Rest parameterfunction f(a, ...rest) {}f1
Bound with one argumentadd.bind(null, 1)bound add1
Getter descriptorget total() {}get total0
Default and rest parameter rule

function f(a, b = 1, c) has length === 1, not 3. JavaScript stops counting at b = 1. function g(a, ...rest) also has length === 1; rest is ignored.

Function name inference

INTERACTIVE

Anonymous functions often receive useful names from their surroundings. JavaScript writes the variable’s name on an unlabeled tool when you store it: const greet = () => gives greet.name === "greet". Object literal keys, default values, getters, setters, and symbol keys can also provide names.

Function inspector
Function declarationPop out in the code editor (opens in a new tab)JavaScript
function add(a, b) { return a + b; }
Observed propertiesdeclaration
nameadd
length2
typeoffunction
instanceof Functiontrue
Try it yourself

Pick a function shape. The details are real JavaScript results verified by the lesson tests; production bundlers may still rewrite names and source text.

A focused inspector for function-object facts. It avoids evaluating strings in the page.

The code below shows more inference rules. Assignment gives assigned, method shorthand gives run, a getter descriptor has get total, a symbol method with description becomes [secret], and a default parameter initializer can name the anonymous function fn.

Name inference examplesPop out in the code editor (opens in a new tab)JavaScript
const assigned = function () {};const object = {  run() {},  get total() { return 42; },  [Symbol("secret")]() {},};const descriptor = Object.getOwnPropertyDescriptor(object, "total");const fallback = (fn = function () {}) => fn.name;console.log(assigned.name);console.log(object.run.name);console.log(descriptor.get.name);console.log(Object.getOwnPropertySymbols(object).map((key) => object[key].name)[0]);console.log(fallback());
Production caveat

Minifiers and bundlers can shorten or rearrange function names. Use fn.name for logs, diagnostics, and friendly labels, not for security decisions or important program logic.

What fact is this about?
  • bound save
  • Rest parameters are ignored
  • fn.count = 3
  • get total
  • function f(a, b = 1, c) {} is 1
  • fn.toString()
Try it yourself
0 of 6 correct

Sort each card by whether it describes fn.name, fn.length, or neither.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Custom properties

STEP THROUGH

Because functions are objects, you can attach your own data: fn.count = 0, fn.meta = , or fn.cache = new Map(). That is handy for tiny utilities and debugging. It is also public: any code with the function reference can read or replace the property. Closures, from the Closure patterns lesson, are better for private state.

Count calls with a function property
Step 0 of 5Ready
Your turn: follow the blue line

A function can carry tags. This wrapper counts calls by storing a property on the function object.

Running in
  1. script
Next: line 2
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
function makeCounter(fn) {  return function counted(...args) {    fn.count += 1;    return fn(...args);  };}function double(n) { return n * 2; }const countedDouble = makeCounter(double);console.log(countedDouble(3));console.log(countedDouble(4));console.log(double.count);
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
Memoize with a cache propertyPop out in the code editor (opens in a new tab)JavaScript
function memoize(fn) {  function memoized(n) {    if (!memoized.cache.has(n)) {      memoized.cache.set(n, fn(n));    }    return memoized.cache.get(n);  }  memoized.cache = new Map();  return memoized;}

This memoizer stores its cache on the returned function. That makes it easy to inspect or clear with memoized.cache.clear(). If you need the cache to be private, close over a Map instead.

Named function expressions

A named function expression gives an internal name to a function value: const factorial = function inner(n) { … }. The outside variable is factorial. The inside-only name inner is visible inside the function body, which makes recursion safe even if the outer variable is reassigned later.

The inner name stays insidePop out in the code editor (opens in a new tab)JavaScript
const factorial = function inner(n) {  if (n <= 1) return 1;  return n * inner(n - 1);};console.log(factorial(4));try {  console.log(inner);} catch (error) {  console.log(error.name);}

Running that code prints 24, then ReferenceError. inner exists for the recursive call on line 3, but it is not a variable in the outer scope.

The new Function syntax

JavaScript has a constructor that builds a function from strings: new Function("a", "b", "return a + b"). This site never executes it in lesson code. It is included here because you will see it in documentation and older metaprogramming examples.

Static example only: do not use in app codePop out in the code editor (opens in a new tab)JavaScript
function demo() {  const secret = 7;  globalThis.shared = 3;  const addShared = new Function("value", "return value + shared;");  console.log(addShared(4));  try {    const readSecret = new Function("return secret;");    console.log(readSecret());  } catch (error) {    console.log(error.name);  }}demo();
Treat it like eval

new Function compiles text as code. That is a security risk with untrusted input, is often blocked by Content Security Policy, and can hurt optimization. It also does not close over local scope: a body created this way can see globals like shared, but not local variables like secret. The tests for this lesson prove that behavior in a sandbox only.

GeneratorFunction, AsyncFunction & AsyncGeneratorFunction

Ordinary Function is not the only function constructor in the language. Generator functions, async functions, and async generator functions each have their own constructor. You almost never call these constructors directly. The useful fact is how to recognize them safely: get the constructor from a real function’s prototype.

Getting special constructors without calling themPop out in the code editor (opens in a new tab)JavaScript
const GeneratorFunction = Object.getPrototypeOf(function* () {}).constructor;const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;const AsyncGeneratorFunction = Object.getPrototypeOf(async function* () {}).constructor;console.log(GeneratorFunction.name);console.log(AsyncFunction.name);console.log(AsyncGeneratorFunction.name);

This prints GeneratorFunction, AsyncFunction, and AsyncGeneratorFunction. Generators and async code get full lessons later; here we only inspect their object identities.

Function.prototype.toString

Function.prototype.toString returns source text for functions the engine has source for. Built-ins usually return a native-function marker. In a Next.js page, however, the browser receives compiled bundled JavaScript, not your original TypeScript or JSX. So the article uses displayed source strings and tests to verify examples, instead of asking the production bundle to be pretty.

What toString can showPop out in the code editor (opens in a new tab)JavaScript
function sample(a) {  return a * 2;}console.log(sample.toString().includes("return a * 2"));console.log(Math.max.toString());
Honest debugging rule

toString() is useful for curiosity and diagnostics, but it is not a parser API and not a source-map reader. Bundlers, minifiers, and native functions can all make the text different from the file you wrote.

Forwarding wrappers & decorators

STEP THROUGH

Here, a decorator means an ordinary wrapper function, not the later @ syntax. A wrapper can add logging, timing, caching, or “run only once” behavior around another function. The key is forwarding: methods need the same this, and callers expect every argument to arrive unchanged.

Real-life analogyA gift box around a present

A wrapper is like putting a present in a gift box with a card. The card adds information, but the receiver should still get the same present.

In real life: The present inside
In JavaScript: The original function
In real life: The box and card
In JavaScript: The wrapper adds logging, timing, or caching
In real life: Handing over the same present
In JavaScript: fn.apply(this, args) forwards receiver and arguments

Where the analogy stops: A gift box does not change the present. A wrapper can change return values or errors if you write it that way, so keep forwarding behavior explicit.

Forward a method through a wrapper
Step 0 of 3Ready
Your turn: follow the blue line

A decorator is just a wrapper function. The safe version forwards this and all arguments.

Running in
  1. script
Next: line 6
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
function withLogging(fn) {  function wrapper(...args) {    console.log("calling", fn.name, args.join(","));    return fn.apply(this, args);  }  return wrapper;}const cart = {  prefix: "Item",  label(id) { return this.prefix + " #" + id; },};cart.label = withLogging(cart.label);console.log(cart.label(7));console.log(cart.label.name);
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
The complete forwarding wrapperPop out in the code editor (opens in a new tab)JavaScript
function withLogging(fn) {  function wrapper(...args) {    console.log("calling", fn.name, args.join(","));    return fn.apply(this, args);  }  Object.defineProperty(wrapper, "name", { value: "logged" + fn.name[0].toUpperCase() + fn.name.slice(1) });  return wrapper;}const cart = {  prefix: "Item",  label(id) { return this.prefix + " #" + id; },};cart.label = withLogging(cart.label);console.log(cart.label(7));console.log(cart.label.name);

Where you’ll use this

Function-object facts show up in practical code more often than they first appear:

  • Logging libraries read fn.name to make traces easier to understand, while staying robust when names are missing.
  • Test helpers and form validators sometimes read fn.length as an arity hint, not as a guarantee.
  • Memoization utilities store cache metadata on a wrapper or in a closure.
  • UI frameworks, analytics, and permission checks wrap callbacks while preserving this and arguments.
Custom property vs closure state
ChoiceGood forTrade-off
Custom propertyInspectable tags like fn.count, fn.cache, or fn.metaPublic and easy to overwrite
ClosurePrivate counters, once flags, hidden memo cachesHarder to inspect or reset from outside
WeakMapMetadata keyed by function without changing itMore code, but avoids property-name collisions

Common misconceptions

“Functions are not objects because typeof fn is function.”

typeof has a special answer for callable objects. A normal function still has properties and passes fn instanceof Function.

“length means how many arguments were passed.”

fn.length is fixed on the function object. It does not change per call; it counts leading parameters before defaults.

“fn.name is safe for program logic.”

Names are helpful labels, but assignment, bind, and minifiers can change them. Prefer explicit IDs for important behavior.

“Named function expressions leak their name outside.”

The inner name is available inside the function body only. Outside, you use the variable that stores the function.

“A wrapper can just call fn(...args).”

That loses this for methods. Use fn.apply(this, args) when the wrapper should be transparent.

“new Function is a harmless shortcut.”

It compiles a string as code, like eval. Avoid it in app code, especially with user input.

Practice: function objects

5 EXERCISES
Exercise 1 · Warm-upPredict name and length

Predict the second log line exactly.

Starter codePop out in the code editor (opens in a new tab)JavaScript
function save(a, b = 1, ...rest) {}
const wrapped = save.bind(null, "draft");
console.log(save.name, save.length);
console.log(wrapped.name, wrapped.length);

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeAdd a call counter property

    Read the code and confirm what the final count should be.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    function track(fn) {
      fn.count = 0;
      return function (...args) {
        fn.count += 1;
        return fn(...args);
      };
    }
    function shout(word) { return word.toUpperCase(); }
    const tracked = track(shout);
    tracked("hi");
    tracked("bye");
    console.log(shout.count);

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeWrite a logging decorator that keeps this

      Fix or write the wrapper so the method still sees its object.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      function logCalls(fn) {
        return function (...args) {
          return fn.apply(this, args);
        };
      }
      const user = {
        prefix: "User",
        label(id) { return this.prefix + ":" + id; },
      };
      user.label = logCalls(user.label);
      console.log(user.label(5));

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · ChallengeExplain the scope of new Function

        Explain why the snippet logs a number and then an error name.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        function demo() {
          const secret = 7;
          globalThis.shared = 3;
          const readShared = new Function("return shared;");
          console.log(readShared());
          try {
            const readSecret = new Function("return secret;");
            console.log(readSecret());
          } catch (error) {
            console.log(error.name);
          }
        }
        demo();

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeFix a wrapper that loses this

          Make the wrapper transparent so box.getValue() still works.

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          function wrap(fn) {
            return function (...args) {
              return fn.apply(this, args);
            };
          }
          const box = {
            value: 9,
            getValue() { return this.value; },
          };
          box.getValue = wrap(box.getValue);
          console.log(box.getValue());

          Answer, then press Check. Spacing and letter case don’t matter.

            Quiz: check your understanding

            7 QUESTIONS

            Every answer explains itself. Try to predict code output before clicking.

            Lesson quiz · 7 questionsScore: first tries count
            1. Question 1 of 7What are the inferred name and length?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const greet = function (name, punctuation = "!") {};
              console.log(greet.name, greet.length);

              Choose an answer to see the explanation.

            2. Question 2 of 7Which statement about custom properties on functions is true?

              Choose an answer to see the explanation.

            3. Question 3 of 7What is the bound method name?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const obj = { run() {} };
              const again = obj.run.bind(obj);
              console.log(again.name);

              Choose an answer to see the explanation.

            4. Question 4 of 7Why is new Function risky in application code?

              Choose an answer to see the explanation.

            5. Question 5 of 7What is the safe core of a forwarding wrapper for methods?

              Choose an answer to see the explanation.

            6. Question 6 of 7What is the symbol-keyed method name?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const key = Symbol("job");
              const tasks = { [key]() {} };
              console.log(tasks[key].name);

              Choose an answer to see the explanation.

            7. Question 7 of 7Which result should you expect from Function.prototype.toString?

              Choose an answer to see the explanation.

            Key takeaways

            • Functions are callable objects: they can be invoked and inspected.
            • name is a useful label; length counts leading parameters before defaults.
            • Name inference covers variables, object keys, getters, bound functions, defaults, and symbol-keyed methods.
            • Custom properties are public object properties; closures are better for private state.
            • A named function expression’s inner name is visible inside the function only.
            • new Function compiles strings as code, cannot see local variables, and should be avoided in app code.
            • Transparent wrappers forward with fn.apply(this, args).

            One-liner.
            A JavaScript function is an object with a call button: you can invoke it, read its properties, attach data, and wrap it.

            Up next: Methods on primitives.

            CompleteFrontend Clear concepts. Working examples.