Function.prototype.toString()

Returns the exact source text of a function, or a native code stub for built-ins.

Signature
toString()
  • Added in JavaScript ECMAScript 1 (1997)

Return value

stringA string representing the source code of the function.

Errors

Throws a TypeError ("Function.prototype.toString called on incompatible object") if its this value is not a Function object.

Changelog

  • PHP ECMAScript 2018The return value must now be the exact source code as declared, including all whitespace and comments; if the host does not have the source available it must return a native function string instead. Before this, engines were free to normalize or reformat the output.

toString() hands back the source text a function was defined with — keywords, parameter list, body, whitespace and comments included. It is invoked automatically whenever a function is coerced to a string, so `${fn}` and fn + "" produce the same result.

When to reach for it

  • Detecting native code. Built-ins, bound functions and host functions return function name() { [native code] }; matching that string is how libraries tell a genuine built-in from a polyfill or a monkey patch.
  • Debug output and error messages that need to show the offending predicate.
  • Shipping a function to a Worker as a string, or reading a parameter list for a dependency-injection container.

What trips people up

The returned text is not a closure. Reconstructing it with new Function() or eval() gives a function that has lost every variable it used to capture, so any code depending on outer scope quietly breaks. And native function strings are a guaranteed syntax error if you try to evaluate them.

Parsing the text is fragile too. Minifiers rename parameters, transpilers rewrite bodies, and comments survive into the output — the old Angular-style trick of reading dependency names from the source is exactly why such code needs a build-time annotation instead.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Check whether a method is the original built-in or has been monkey-patched
  • Include the failing predicate's source in an assertion error message
  • Send a self-contained function to a Web Worker or another realm as text
  • Print a function definition in a REPL or debugging tool

Frequently asked questions

Why does toString() return "[native code]"?

Because the function is implemented by the engine or host rather than in JavaScript — every built-in, every bound function, and anything from a non-JS host falls in this category. There is no source text to return, and evaluating the stub is a guaranteed syntax error.

Can I recreate a function from its toString() output?

Only if the function is fully self-contained. The string carries the source but not the closure, so new Function("return " + fn)() produces a function compiled in global scope that cannot see any variable the original captured.

Is it safe to parse parameter names out of a function's source?

No, not in production. Minifiers rename parameters, transpilers rewrite bodies, and comments and default values complicate the text. Frameworks that once did this now require explicit annotations or a build step for exactly this reason.

More examples

Detecting a native implementation

Array.prototype.map reports native code while a user-written arrow reports its source, which is the whole basis of "has this built-in been patched?" checks. Calling toString through Function.prototype on a non-function throws a TypeError rather than returning a string.

Example · javascript
const isNative = (fn) =>
  /\{\s*\[native code\]\s*\}$/.test(Function.prototype.toString.call(fn));

console.log(isNative(Array.prototype.map), isNative((x) => x));

// toString() is called implicitly on string coercion
console.log(`fn is: ${function hello() {}}`);

try {
  Function.prototype.toString.call("foo");
} catch (e) {
  console.log(e.constructor.name);
}

Round-tripping source, and losing the closure

The source text preserves defaults and rest syntax, so the rebuilt greet keeps its name and a length of 0. The last block is the trap: reader's source mentions captured, but the rebuilt copy is compiled in global scope where that binding does not exist, so calling it throws a ReferenceError.

Example · javascript
function greet(name = "world", ...rest) {
  return `hi ${name}`;
}
const src = greet.toString();
console.log(src.slice(0, src.indexOf(")") + 1));

const restored = new Function("return " + src)();
console.log(restored("there"), restored.name, restored.length);

const captured = 42;
const reader = () => captured;
const rebuilt = new Function("return " + reader.toString())();
try { rebuilt(); } catch (e) { console.log("rebuilt lost its closure:", e.constructor.name); }

Discussion

  • Be the first to comment on this lesson.