completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

Built-in prototypes

Learn where JavaScript’s built-in methods live, how arrays and functions inherit them, how to borrow methods safely, and why polyfills must be careful.

What you’ll be able to do
  • 01
    Follow a built-in prototype chainUse Object.getPrototypeOf to follow the chain, and find where a method lives.
  • 02
    Borrow a method safelyUse call to run an array or object method on another object, without changing any built-in.
  • 03
    Add missing methods with careExplain why adding methods to built-ins is risky, and how a careful polyfill differs from a ponyfill.

Built-in methods live on shared prototypes

Arrays, strings, and functions come with ready-made methods. For example, every array has map and join. We call these built-in methods, because JavaScript gives them to you.

JavaScript does not copy these methods into every array. It keeps one copy of each method on a shared object, called a built-in prototype. For arrays, that shared object is Array.prototype.

You met prototypes in the lesson The prototype chain. Here is a quick reminder:

  • Every object has a hidden link to one other object. That other object is its prototype.
  • When you read a property the object doesn't have, JavaScript follows the link and looks in the prototype.
  • If the property isn't there either, JavaScript follows the next link, and so on. This line of links is the prototype chain.

In this lesson you will meet Object.prototype, Array.prototype, and Function.prototype. You will also learn three things to do with them: borrow a method, leave them unchanged, and add a missing method safely.

Real-life analogyThe town library

Think of a town library. You don't buy your own copy of every book. When you need one, you borrow it from the shared shelf.

An array works the same way. It doesn't carry its own copy of map. When it needs map, it uses the one on the shared shelf, Array.prototype. Most values can also reach the main shelf, Object.prototype.

In real life: A shared library shelf
In JavaScript: A built-in prototype, such as Array.prototype
In real life: Anyone in town can borrow a book from it
In JavaScript: Every array can use map, join, and the other array methods
In real life: The main shelf that every reader can reach
In JavaScript: Object.prototype, near the end of most chains

Object.prototype: at the end of most chains

INTERACTIVE

Follow the prototype chain of almost any object, and you reach Object.prototype. After it comes null, which means the chain has ended.

So plain objects, arrays, functions, maps, and dates can all use the methods on Object.prototype. Here are three of them:

  • obj.hasOwnProperty(key) tells you if key is an own property of obj. An own property is stored on the object itself, not found through the chain.
  • a.isPrototypeOf(b) tells you if a is somewhere in the chain of b.
  • obj.toString() turns the object into text.

Newer code often writes Object.hasOwn(obj, key) instead of obj.hasOwnProperty(key). It does the same check. But it lives on Object itself, so it still works when an object has no hasOwnProperty method, or has its own property with that name.

The playground below uses Object.getPrototypeOf(x). It gives you the prototype of x: one step up the chain. Pick a value. The playground keeps taking one step up until it reaches null.

Line 2 uses Object(value). For a string or a number, this makes a wrapper object (explained just below), so the walk has an object to start from. For an object, it gives back the same object.

Follow a real prototype chain
Follow the chain up to nullPop out in the code editor (opens in a new tab)JavaScript
const value = [];let current = Object(value);while (current !== null) {  current = Object.getPrototypeOf(current);}
[]3 steps
  1. 1. Array.prototypeat, concat, constructor, copyWithin, entries, every, fill, filter
  2. 2. Object.prototype__defineGetter__, __defineSetter__, __lookupGetter__, __lookupSetter__, constructor, hasOwnProperty, isPrototypeOf, propertyIsEnumerable
  3. 3. nullend of the chain
Try it yourself

JavaScript starts at []. Object.getPrototypeOf gives the next prototype, one step at a time, until it reaches null.

These rows come from the real prototypes in your browser. Each row shows up to 8 of the methods stored on that prototype, in A to Z order.
Strings get their methods the same way

A string like "abc" is a primitive: a simple value, not an object. Still, "abc".toUpperCase() works.

When you call a method on a string, JavaScript wraps it in an object for a moment. That wrapper object links to String.prototype, where toUpperCase lives. The lesson Methods on primitives covers this in detail.

A string uses a method it doesn't ownPop out in the code editor (opens in a new tab)JavaScript
const word = "tea";
console.log(word.toUpperCase());
console.log(Object.hasOwn(Object(word), "toUpperCase"));
console.log(Object.getPrototypeOf(Object(word)) === String.prototype);
  • Line 2 prints "TEA". JavaScript wraps the string for a moment and finds toUpperCase on String.prototype.
  • Line 3 makes the wrapper on purpose, with Object(word). Then it asks if the wrapper owns toUpperCase. It prints false: the wrapper doesn't store the method itself.
  • Line 4 prints true. The wrapper's prototype is String.prototype.

The wrapper lasts for just that one step. word stays a plain string. That is why you can't add your own lasting properties to a string.

Array.prototype and Function.prototype

STEP THROUGH

An array stores its own items and its length. It does not store map, filter, join, or toString. Those live on Array.prototype.

Functions work the same way. A function does not store call, apply, or bind. It gets them from Function.prototype.

Reading list.map. Three cards in a row joined by arrows: list, Array.prototype, and Object.prototype. list holds 1, 2 and length, but no map. Array.prototype holds map, join and toString, so map is found there. Object.prototype is not needed.list.map((n) => n * 10)listArray.prototypeObject.prototype1, 2, lengthmap, join, toStringtoString, hasOwnProperty1. no map2. map foundnot needed
list has no map of its own, so JavaScript follows the link to Array.prototype and finds it there. It stops looking as soon as it finds it.

Step through the code below and watch where each method comes from. Line 5 uses .call(list). It runs Object.prototype.toString with list as its this. (this is the object a method works on.)

Where each method is found
Step 0 of 5Ready
Your turn: follow the blue line

Guess where each method comes from. Then step through and watch JavaScript look on the array first, then on its prototype.

Running in
  1. script
Next: line 1
const ownMap = Object.hasOwn(list, "map");const mapped = list.map((n) => n * 10);const arrayText = list.toString();const objectText = Object.prototype.toString.call(list);
CallStoreChangeResultBlue line: runs next. 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.

Lines 4 and 5 use two different toString methods:

  • Line 4: JavaScript finds toString on Array.prototype first. That one joins the items, so you get "1,2".
  • Line 5: you pick Object.prototype.toString on purpose. That one gives a type label, "[object Array]".

When two objects in a chain have a property with the same name, JavaScript uses the one it reaches first. We say the nearer one shadows (hides) the other.

An array's own property hides the shared methodPop out in the code editor (opens in a new tab)JavaScript
const list = [1, 2];
list.map = "later";
console.log(Object.hasOwn(list, "map"));
console.log(typeof Array.prototype.map);
  • Line 2 stores a string called map directly on this one array.
  • Line 3 prints true: the array now owns a map property.
  • Line 4 prints "function": the shared Array.prototype.map is still there.

But JavaScript finds the array's own map first. So list.map() would now fail, because list.map is a string, not a function.

This helps when you see an error like list.map is not a function. First check Object.hasOwn(list, "map"). If it is true, some code stored a property called map on the array. Delete that property, and the shared method works again.

Run map on an object that looks like an arrayPop out in the code editor (opens in a new tab)JavaScript
const scores = { 0: 4, 1: 7, length: 2 };
const doubled = Array.prototype.map.call(scores, (score) => score * 2);
console.log(doubled.join(","));

scores is not an array. It is a plain object with number keys (0 and 1) and a length. We call an object like this array-like.

Line 2 runs Array.prototype.map on scores with .call. .call(x, …) runs a function with x as its this. map only needs number keys and a length, so it works. It returns a real array, [8, 14], and line 3 prints "8,14".

Functions get call and bind from a prototypePop out in the code editor (opens in a new tab)JavaScript
function greet(name) {  return "Hi, " + name;}const called = greet.call(null, "Ada");const bound = greet.bind(null, "Lin");const later = bound();

Line 4 uses greet.call, so called becomes "Hi, Ada". Line 5 uses greet.bind to make a new function, and line 6 calls it, so later becomes "Hi, Lin".

greet doesn't own call or bind. JavaScript finds both on Function.prototype. The lesson call, apply & bind shows how to use them.

The table lists common built-in prototypes. Each row says what the prototype holds and which values use it.

Common built-in prototypes and what they hold
Built-in prototypeSome methods it holdsWhich values use it
Object.prototypetoString, hasOwnProperty, and isPrototypeOf.Almost all objects: plain objects, arrays, functions, dates, and maps.
Array.prototypemap, filter, join, at, and toString.Arrays, such as [1, 2].
Function.prototypecall, apply, bind, and toString.All functions, including classes and arrow functions.
String.prototypetoUpperCase, slice, and includes.Strings, through a short-lived wrapper object.
Map.prototypeget, set, has, and entries.Maps made with new Map().

Borrowing a method

STEP THROUGH

To borrow a method means:

  1. Take a method from a built-in prototype.
  2. Run it once on a different object, with .call.
  3. Leave the prototype as it was.

It's like borrowing a library book for a school project. You use it and give it back. You don't write in it.

Borrowing join. The join function from Array.prototype goes, with call, to the letters cart, which holds length: 3. join runs once with this = letters and gives the text "a-b-c".Array.prototype.join.call(letters, "-")join()Array.prototypecalllength: 3lettersthis = letters"a-b-c"the result
.call runs join one time, with letters as its this. Nothing is added to letters, and Array.prototype stays the same.

Borrowing only works if the method can handle your object. join needs just number keys and a length, so it works on array-like objects. map does too.

If you need many array methods, Array.from(obj) is often clearer. It makes a real array with the same items. Try a different separator, then step through.

Borrow join for an object that looks like an array
Step 0 of 4Ready
Your turn: follow the blue line

Change the separator. Then step through and watch one borrowed method work on an object that isn't an array.

Running in
  1. script
Next: line 1
const joined = Array.prototype.join.call(letters, "-");const copied = Array.from(letters);const label = Object.prototype.toString.call(copied);
CallStoreChangeResultBlue line: runs next. 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.

Line 4 borrows another method: Object.prototype.toString.call(value). It gives a type label, such as [object Array]. The lesson Checking types reliably uses this. It isn't perfect: an object can change its own label with Symbol.toStringTag.

Not every method can be borrowed by any object. For example, Map.prototype.get needs a real map. A map keeps its items in a hidden place that plain objects don't have. So Map.prototype.get.call({}, "a") throws a TypeError.

One more catch. Each page has its own set of built-ins, and so does each iframe (a page shown inside another page). One full set of built-ins is called a realm.

An array made inside an iframe links to the iframe's Array.prototype, not the page's. So in the page, arr instanceof Array gives false, because instanceof Array looks for the page's Array.prototype in the chain. Array.isArray(arr) works in every realm.

Why you shouldn't change built-in prototypes

SAFE SANDBOX

You can add your own method to a built-in prototype. For example, an app might add Array.prototype.last. But there is only one Array.prototype in a page. So every array in the page gets last, including the arrays inside libraries you use.

That causes three problems:

  • A plain assignment makes the method enumerable, which means loops like for…in list it. A for…in loop over an array then shows last next to the index numbers.
  • Another library may add its own last that works differently. One overwrites the other.
  • A future version of JavaScript may add a real last method that works differently from yours.
Real-life analogyDon't write in library books

Your own helper function is like notes in your own notebook. Only you read them.

Adding a method to a built-in prototype is like writing in a library book. Your note may help you, but everyone who borrows the book reads it too.

In real life: Writing notes in a library book
In JavaScript: Adding your own method to a built-in prototype
In real life: Every reader of the book sees your notes
In JavaScript: Every value that uses that prototype gets the method
In real life: A new edition may print something else on that page
In JavaScript: A future JavaScript version may add a method with the same name
Line 1 adds last() to Array.prototype. Three arrays, ["red", "blue"], [1, 2] and an empty array, all have arrows up to Array.prototype, so all of them get last().Array.prototypelast()added["red", "blue"][1, 2][]the demo'syoursa library's
There is only one Array.prototype in a page. Line 1 adds last() to it, so every array gets it, even arrays inside libraries you use.

The demo below runs this risky code safely, inside a hidden iframe. The iframe has its own built-ins, so this page is not changed. Line 1 adds last. The loop on line 6 then lists 0, 1, last. Line 11 removes last again.

Run the risky code in a hidden iframe
Code that runs only in the iframePop out in the code editor (opens in a new tab)JavaScript
Array.prototype.last = function () {  return this[this.length - 1];}; const keys = [];for (const key in ["red", "blue"]) {  keys.push(key);}console.log(keys.join(", ")); delete Array.prototype.last;
What the iframe printedsent back to this page
Click Run. This page's own prototypes stay unchanged.
Try it yourself

The iframe has its own built-ins. Line 1 adds a last method to the iframe's Array.prototype, so the for…in loop lists it. Line 11 removes it again.

The iframe can only run this fixed code. This page only accepts results that come from that iframe.
A change stays inside one realm

A realm is one full set of built-ins. This page and the demo's iframe each have their own Array.prototype. So a change inside the iframe doesn't reach this page.

But a change in your page reaches every script in that page, including every library.

When JavaScript had to rename a method

In 2018, JavaScript planned a new array method called flatten. But an old library, MooTools, had already added its own flatten to Array.prototype. Websites that used MooTools would have broken. So the new method was renamed to flat.

Years before, the array method contains was renamed to includes for the same kind of reason, also because of MooTools code. Names on built-in prototypes are shared by the whole web.

Polyfills: adding a missing method carefully

CODE

Old browsers lack some newer methods. A polyfill is code that adds a missing standard method, so an old browser works like a new one. "Standard" means the method is part of the official JavaScript rules.

A careful polyfill does three things:

  1. It checks first if the method is missing. This is called feature detection. If the method is already there, the polyfill does nothing.
  2. It adds the method with the same settings as real built-in methods. For example, it is not enumerable.
  3. It handles the same special cases as the real method.

Check for the method itself, not for the browser's name. Two versions of the same browser can support different methods.

A full polyfill is hard to write. It must handle many special cases, such as odd inputs and very old engines. Real projects use tested packages such as core-js, added by build tools like Babel. The later lesson Transpilers & polyfills shows how.

In your own app code, a ponyfill is often safer. It is a plain function, such as at(array, index). It does the same job, but it doesn't touch any built-in prototype.

Real-life analogyTaping in a missing page

Imagine an old copy of a book with page 42 missing. You copy page 42 from a newer edition and tape it in.

You only do this if the page is missing. If page 42 is already there, you leave the book alone.

In real life: An old book with page 42 missing
In JavaScript: An old browser without Array.prototype.at
In real life: Tape in a copy only if page 42 is missing
In JavaScript: Check first, then add the method only if it is missing
In real life: A separate sheet with page 42 on it
In JavaScript: A ponyfill: a plain function such as at(array, index)
Line 1 of the polyfill, if not Array.prototype.at. Left, an old browser: Array.prototype has no at(), so the polyfill adds it. Right, a new browser: Array.prototype already has at(), so the polyfill leaves it alone.if (!Array.prototype.at)old browserArray.prototypeat() is missingadd at()new browserArray.prototypeat()at() is already thereleave it alone
Line 1 checks first. The polyfill adds at() only where it is missing, and never replaces a real built-in method.

Here is a polyfill for at. array.at(i) gives the item at index i, and a negative i counts from the end.

  • Line 1 checks if Array.prototype.at is missing.
  • Line 2 uses Object.defineProperty. It adds a property and sets its settings in one step.
  • Lines 3 to 8 are the method itself.
  • Lines 9 to 11 set the settings: writable, configurable, and not enumerable.
A polyfill for at that checks firstPop out in the code editor (opens in a new tab)JavaScript
if (!Array.prototype.at) {  Object.defineProperty(Array.prototype, "at", {    value(index) {      const length = this.length;      const integer = Math.trunc(index) || 0;      const position = integer < 0 ? length + integer : integer;      return this[position];    },    writable: true,    configurable: true,    enumerable: false,  });}
A ponyfill: a plain function that leaves built-ins alonePop out in the code editor (opens in a new tab)JavaScript
function at(array, index) {  const length = array.length;  const integer = Math.trunc(index) || 0;  const position = integer < 0 ? length + integer : integer;  return array[position];}

This at does the same work as the polyfill, as a plain function. You call it as at(list, -1). No built-in prototype changes.

Every property has three settings, called flags:

  • enumerable: loops like for…in list the property.
  • writable: you can give the property a new value.
  • configurable: you can delete the property or change its flags.

Object.getOwnPropertyDescriptor(obj, key) shows the flags of one property. Here it checks the real map method.

Check the flags of a real built-in methodPop out in the code editor (opens in a new tab)JavaScript
const descriptor = Object.getOwnPropertyDescriptor(Array.prototype, "map");
console.log(descriptor.enumerable);
console.log(descriptor.writable);
console.log(descriptor.configurable);

It prints false, true, and true. So map is hidden from loops, but it can still be replaced or deleted.

Safe or risky?
  • Borrow Array.prototype.join with call for one object
  • Add debug to Object.prototype
  • A tested polyfill package that checks first and hides the method from loops
  • A separate at(array, index) function
  • Replace console.log for the whole page
  • Add last() to Array.prototype in an app
Try it yourself
0 of 6 correct

Sort each practice: is it usually fine in app code, or risky?

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

Where you'll use this

You'll use what you learned here when you:

  • fix an error like x.map is not a function,
  • check if a property belongs to the object or comes from its prototype,
  • run an array method on an array-like object,
  • read older code that changes built-in prototypes,
  • decide whether to add a polyfill yourself or let your build tools do it.
Turn array-like data into a real arrayPop out in the code editor (opens in a new tab)JavaScript
const cards = { 0: "Buy milk", 1: "Send email", length: 2 };
const todo = Array.from(cards);
console.log(todo.map((card) => card.toLowerCase()).join(" | "));

Line 2 uses Array.from to make a real array from cards. Line 3 can then use map and join as usual. It prints "buy milk | send email".

When should you copy with Array.from, and when should you borrow?

  • Use Array.from when you'll call several array methods. Readers also see right away that you made a copy.
  • Borrow one method with .call when you need just one step and the object clearly fits.
  • Neither one changes the prototype of the original object.

Array.from makes a new array, so it uses a little more memory. In most page code that doesn't matter. Pick the code that is easier to read.

Some short rules to keep:

  • Use Object.hasOwn(obj, key) to check if a property is the object's own.
  • Use Array.from(nodeList) to get array methods on a NodeList (a list of page elements).
  • Borrow with .call when a method fits your object and you need it once.
  • In app code, prefer ponyfills. Leave polyfills to tested packages.

Common misconceptions

"Every array gets its own copy of each method."

No. All arrays share one copy of each method, on Array.prototype. This saves memory, and every array behaves the same.

"Borrowing a method changes the prototype."

No. .call sets this for one call only. Your object's prototype stays the same, and so does the prototype you borrowed from.

"Any method added to a built-in prototype is a polyfill."

No. A polyfill adds a missing standard method. Adding your own made-up method is called monkey-patching.

"A method hidden from loops is safe to add."

Not quite. Making it not enumerable fixes the for…in problem. But the name can still clash with a library's method or a future JavaScript method.

"Every object has Object.prototype in its chain."

No. Object.create(null) makes an object with no prototype at all. This is handy for a simple list of keys and values. But such an object has no toString or hasOwnProperty to use.

A dictionary with no prototypePop out in the code editor (opens in a new tab)JavaScript
const settings = Object.create(null);
settings.theme = "dark";
console.log(Object.getPrototypeOf(settings));
console.log(Object.hasOwn(settings, "theme"));
console.log("hasOwnProperty" in settings);
  • Line 1 makes an object with no prototype.
  • Line 3 prints null: there is no prototype.
  • Line 4 prints true, because settings owns theme.
  • Line 5 prints false. The in operator looks at the object and its whole chain, and there is no hasOwnProperty anywhere.

Practice: built-in prototypes

5 EXERCISES
Exercise 1 · Warm-upPredict the lookup

Predict both lines this code prints.

Starter codePop out in the code editor (opens in a new tab)JavaScript
const xs = [1, 2];
console.log(Object.hasOwn(xs, "map"));
console.log(xs.map((n) => n + 1).join("|"));

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

    Exercise 2 · PracticeBorrow join for an array-like object

    What does the borrowed method print?

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    const buttons = { 0: "Save", 1: "Cancel", length: 2 };
    console.log(Array.prototype.join.call(buttons, " / "));

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

      Exercise 3 · PracticeWrite a ponyfill last(array)

      Write a plain function that returns the last item in an array. Don't change Array.prototype.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      function last(array) {
        return array[array.length - 1];
      }
      console.log(last(["red", "blue"]));

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

        Exercise 4 · ChallengeRead a guarded polyfill

        The code adds the polyfill, then reads the flags of at. What do the last two lines print?

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        if (!Array.prototype.at) {
          Object.defineProperty(Array.prototype, "at", {
            value(index) {
              const length = this.length;
              const integer = Math.trunc(index) || 0;
              const position = integer < 0 ? length + integer : integer;
              return this[position];
            },
            writable: true,
            configurable: true,
            enumerable: false,
          });
        }
        const descriptor = Object.getOwnPropertyDescriptor(Array.prototype, "at");
        console.log(typeof descriptor.value);
        console.log(descriptor.enumerable);

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

          Exercise 5 · ChallengeExplain the rename

          JavaScript planned an array method called flatten, but the name clashed with old websites. What name was used instead?

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

            Quiz: check your understanding

            7 QUESTIONS
            Lesson quiz · 7 questionsScore: first tries count
            1. Question 1 of 7Where does [1, 2].map find map?

              Choose an answer to see the explanation.

            2. Question 2 of 7What does this print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const xs = [1, 2];
              console.log(xs.toString());

              Choose an answer to see the explanation.

            3. Question 3 of 7What type label does this print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              console.log(Object.prototype.toString.call([1, 2]));

              Choose an answer to see the explanation.

            4. Question 4 of 7Why does Array.prototype.join.call({0: 'a', length: 1}, '-') work?

              Choose an answer to see the explanation.

            5. Question 5 of 7What is the biggest problem with adding your own methods to built-in prototypes?

              Choose an answer to see the explanation.

            6. Question 6 of 7What should a careful polyfill do first?

              Choose an answer to see the explanation.

            7. Question 7 of 7Which sentence about Function.prototype is true?

              Choose an answer to see the explanation.

            Key takeaways

            • Built-in methods live on shared prototype objects, not on each value.
            • Object.prototype is near the end of most chains. Objects made with Object.create(null) don't have it.
            • Array.prototype holds the array methods. Function.prototype holds call, apply, and bind.
            • Borrowing a method with .call changes nothing. Adding a method to a built-in prototype changes it for the whole page.
            • A polyfill adds a missing standard method, carefully. A ponyfill is a plain function and changes no built-ins.

            Remember the one-liner.
            Built-in prototypes are shared shelves of methods: use them, borrow from them with care, and don't add your own methods to them.

            Up next: Object.create & null-prototype objects.

            CompleteFrontend Clear concepts. Working examples.