This page assumes the objects and arrays page, including how this depends on how a function is called. Samples use a running example of robots. Predict each stated output before you run it, and use the Object walker in section 3 to look at the chains of actual objects, including a button from the DOM.
1. Constructors
Constructors are special functions that prepare new instances of an object for use. What makes one special is the way it is called. JavaScript has no separate constructor syntax for this older form, so an ordinary function becomes a constructor when you call it with new. Constructors are capitalized by convention, and the language does not check that convention.
The || in that constructor gives the parameter a default. It works here because || returns one of its operands, a behavior the types page explains. new Robot() passes nothing, so needsToFly is undefined and the property becomes false.
What new does
The new operator does the work that the word "constructor" suggests. It takes four steps:
- It creates a blank object.
- It sets that object's prototype to the object in the function's
prototypeproperty. If that property does not hold an object, the new object getsObject.prototypeinstead. - It runs the function with
thisbound to the new object, sothis.hasJetPack = …adds a property to it. - It gives you the new object, unless the function returned an object of its own, which then takes its place. A returned primitive is ignored.
Step 2 is the subject of the rest of this page. MDN sets out the same steps in its reference for the new operator.
Forgetting new
Nothing stops you from calling a constructor as a plain function, and in an ordinary script nothing complains. There is no new object, so this is the global object, and the constructor writes its properties onto that. The call returns undefined. The capital letter exists to warn a reader about this: a capitalized function called without new is probably a bug.
In strict mode and in modules, this is undefined in that call, so the assignment throws a TypeError and you find the bug at once. An arrow function cannot be used with new at all.
One copy of every method per robot
Assigning to this gives each instance its own data, which is what you want for hasJetPack. It is less useful for behavior. A method written in the constructor is created again every time the constructor runs, so a thousand robots hold a thousand identical functions. In this playground, the two instances have different doAction functions, though the code is the same. Run it, then try the call without new.
A constructor also carries an object prototype that defines the code and data each instance has by default. Moving doAction there leaves one function, shared by every robot.
2. Prototypes
The prototype is a reference to an object describing the code and data that all objects of that type have in common. Two different things in the language are called "prototype":
- Every object has an internal
[[Prototype]]. It is a link to another object, or tonull. It is not a property you can reach with a dot. You read it withObject.getPrototypeOf. - Functions have a
.prototypeproperty. It is an ordinary property that holds an ordinary object. Its one job is step 2 ofnew: it is the object that instances of that function get as their[[Prototype]].
So an instance does not have a .prototype, and the .prototype of a function is not that function's own prototype. A function's own [[Prototype]] is Function.prototype. The double brackets are how the specification writes an internal slot, and Chrome's DevTools uses the same notation.
[[Prototype]] link that new sets. An instance links to the prototype object and has no link to the function.Putting the shared parts on the prototype
This version moves the robot's defaults and its method to the prototype. The first two statements assign to Robot.prototype before the function Robot line, and they run because function declarations are hoisted, as the functions page explains. A robot gets an own hasJetPack only when it can fly. Otherwise a read of that name finds the default.
Reads walk, writes stay home
The model has one rule for reading and a different one for writing.
A read looks on the object first. If the name is not there, it follows [[Prototype]] and looks there, and so on upward. The first match wins. If the walk reaches null, the result is undefined.
A write creates or changes a property on the object itself and leaves the prototype alone. (The exceptions are a setter or a read-only property further up the chain.) So assigning sidekick.doAction gives that one robot its own method, which hides the shared one. This is called shadowing. Deleting the own property brings the shared one back into view, because the shared one was never touched.
Prototypes are shared by reference and consulted at the moment of the read, so a change to the prototype reaches every instance, including those created before the change. This playground shows all of it: one shared function, a change that reaches existing robots, an own property that shadows it, and a delete that undoes the shadow.
Other ways to set the link
A constructor is one way to set [[Prototype]]. Object.create does it directly, with no function involved, and it accepts null for an object with no prototype. An object literal can name its prototype with __proto__:, and that literal form is standard.
You will read obj.__proto__ in older code and tutorials. That accessor is deprecated and kept for web compatibility, so read the link with Object.getPrototypeOf. Object.setPrototypeOf can change the link after creation, but MDN warns that doing so disrupts the optimizations engines make to the chain, so set it when the object is created where you can.
3. Walking the chain
A prototype is an object, so it has a prototype too. Following the links gives the prototype chain, and every chain ends at null, which has no prototype. A literal is close to the end: its prototype is Object.prototype, whose prototype is null. A robot's chain has one more level, and toString comes from the top of it.
hasJetPack on the prototype is never reached from guard, because the own property shadows it.The Object walker
The same steps can be run against any object. Pick a starting object below, and the walker climbs with Object.getPrototypeOf, listing the own properties at each level until it reaches null. Type a name, or pick one, to see where a read of that name would stop and which levels further up it shadows. The name is only compared with property names as text, so nothing you type runs. The tool also has its own page.
Four things to look for as you try it:
- An array's
pushis onArray.prototype, while its elements andlengthare its own. That is why every array can push without holding a copy ofpush. toStringon an array stops atArray.prototype, and the one onObject.prototypeis shadowed. A built-in type overrides a general method in the same way a robot would.- A
Datehas no own properties at all. Its time is held in an internal slot that its methods read, so everything you can call is onDate.prototype. The built-ins page covers what those methods do. - A button also has no own properties until you add some. Its
disabledis onHTMLButtonElement.prototype,clickonHTMLElement.prototype,idonElement.prototype,textContentonNode.prototypeandaddEventListeneronEventTarget.prototype, followed by the sameObject.prototypeandnullthat a literal has.
The DOM and events are described by a separate standard from the language, yet their inheritance is the same mechanism as the robot's. Many of those DOM properties show as accessors in the walker: disabled is a getter and setter pair on the prototype, which is why the walker reads descriptors and never reads the value. Reading HTMLButtonElement.prototype.disabled directly throws, because the prototype is not a button.
What walks and what stays on the object
Several tools answer the question "what properties does this have?", and they differ on whether they walk. in walks the chain. for/in walks it too, listing enumerable names, as the syntax page warned. Object.keys and Object.hasOwn stay on the object.
Primitives join in during a read. A string is not an object, but when you call a method on one, the language looks the method up on String.prototype, using a temporary wrapper that it then discards. Object.getPrototypeOf applied to a primitive gives the same answer. Writing a property on a string has nothing to keep it, so the value is gone by the next line.
4. Using prototypes on built-ins
Prototypes are of course shared, so you can do some interesting things. Built-in prototypes are ordinary objects that your script can write to, so a method you add to String.prototype is available on every string in the page, and you can invoke it as you would any other built-in method.
There is a small oddity inside that method. In an ordinary script, a function called on a primitive string receives the wrapper object as this, so typeof this is 'object'. In strict mode it receives the primitive.
Why to be careful
Every script on the page uses the same String.prototype, including libraries, analytics and whatever arrives next year. MDN calls extending a built-in prototype monkey patching and a misfeature, for these reasons:
- Names collide. Two scripts that add
getThirdCharoverwrite each other, and the last one to load wins for everybody, including code written against the other one. - The language keeps growing. If a later edition adds a method with your name and different behavior, one of the two breaks. The web has a record of this: the proposed
Array.prototype.flattenbroke a popular site in Firefox Nightly, probably through the MooTools library's ownflatten. TC39 renamed the standard methodflatso as not to break existing pages. Old patches can limit what the whole platform may name things. - Assigned properties are enumerable. A method added by assignment appears in every
for/inover that type, which the built-in methods do not. The playground below shows it.Object.definePropertyavoids this, but not the other two problems.
MDN names one good reason to extend a built-in: a polyfill, which adds a standard method to an older engine that lacks it, with the standard's name and behavior. The how much JavaScript page returns to feature detection and polyfills. For your own helpers, write a function that takes the value, as in getThirdChar('Example'), or make a subclass, as section 7 shows.
There is a security version of this problem too. In prototype pollution, code writes to obj[key1][key2] with keys taken from outside the program. If key1 is "__proto__", the write lands on Object.prototype, and every ordinary object in the program inherits the attacker's value. MDN's defenses include objects with a null prototype, a Map for key and value data, and Object.hasOwn before a read that decides something.
Improving one instance
Sometimes you don't even need prototypes, especially if you just want to improve some built-in object instances. Assigning to an instance gives that one object an own property, and the shadowing rule from section 2 does the rest. This sample comes from the time when pages wrote their output with document.write, which the built-ins page covers as history.
A document already has a writeln, inherited from Document.prototype. The assignment does not change that one. It puts an own property on this one document object, which shadows the inherited method, so any other document, such as the one in an iframe, is unaffected. The same applies to any object.
A patch on one instance affects only the object you hold, so other arrays never see it. Overwriting a method that other scripts call on that same object, as the writeln sample does, still changes it for them.
5. Inheritance via the prototype chain
Inheritance in JavaScript is achieved through prototypes. To derive a new object type from a type that already exists, you arrange for instances to inherit all the properties of their own type in addition to any properties embodied in their parent. The way to do that with constructors is to make one type's prototype object inherit from the other's. This classic form still appears in older code.
Walk each read. guard.feature stops on guard, because the constructor received a feature and assigned it. A second UltraRobot with no argument gets no own feature, so the read stops one level up and finds the default, 'Radar'. hasJetPack is also one level up, since that prototype object is itself a Robot. doAction is two levels up.
UltraRobot is an actual robot, created once. Every UltraRobot inherits that one robot's hasJetPack.What that pattern costs
The pattern has three problems. new Robot() runs the parent's constructor once, when the types are set up, with no arguments. So UltraRobot has no way to pass flying up, and each of its instances inherits the one hasJetPack: false. If the parent's constructor did something with side effects or required an argument, that would happen or fail at setup time. Replacing UltraRobot.prototype also discards the object that held the correct constructor, so guard.constructor finds the one on Robot.prototype and reports the wrong type.
The repair that became standard before class has three parts. Robot.call(this, flying) runs the parent's constructor on the new instance, so each instance gets its own data. Object.create(Robot.prototype) links the two prototypes without running a constructor. A final assignment puts constructor back. Compare the two in this playground.
instanceof in the last line of that sample is another chain walk. It asks whether the function's .prototype appears anywhere on the object's chain, so guard is an instance of UltraRobot, Robot and Object. Because the test looks at the chain as it stands now, changing a function's .prototype afterward can change its answer for objects that already exist.
6. Common properties and methods
Nearly every object ends its chain at Object.prototype, so nearly every object inherits a common set of members. The table lists them, with the internal link first. That link is often described as a prototype property on every object, but prototype is a property of functions.
| Member | Description |
|---|---|
[[Prototype]] | Internal link to the object from which this one inherits, read with Object.getPrototypeOf(obj). A function's prototype property is the separate object that becomes the [[Prototype]] of its instances. |
constructor | Reference to the function associated with the prototype. It is inherited and writable, so it reports what the prototype says, and it can be wrong, as in section 5. |
toString() | Converts the object into a string (object-dependent behavior). |
toLocaleString() | Converts the object into a localized string (object-dependent behavior). |
valueOf() | Converts the object into an appropriate primitive where its type defines one, such as a number for a Date. The default on Object.prototype returns the object itself. |
hasOwnProperty(prop) | Returns true if the object has an own property named prop, and false otherwise. Prefer Object.hasOwn(obj, prop) in new code. |
isPrototypeOf(obj) | Returns true if this object appears anywhere on the chain of obj, not only as its immediate prototype. |
propertyIsEnumerable(prop) | Returns true if prop is an own property that is enumerable. An inherited enumerable property returns false, even though for/in lists it. |
Object.hasOwn has been available across browsers since March 2022 and is the recommended replacement for hasOwnProperty. The method form depends on the chain to exist and to be unmodified. An object with no prototype does not inherit it, and any object can define its own property with the same name.
7. Objects the ES6 way
JavaScript supports OOP, but its form is disconcerting to those who learned in a "class" style. ES6, published in June 2015, rectifies this with a class style syntax, documented in MDN's classes reference. This is the first robot again.
In a browser's console, console.log(guard) shows you the object itself, which you can expand to see its [[Prototype]]. The sample uses JSON.stringify so that its stated output is the same text everywhere.
The same model underneath
A class declares a constructor function and a prototype object in one statement. MDN says that classes do not bring a new inheritance pattern. You can check that directly. The name is a function, methods live on its .prototype, and instances link to that object. Adding to the prototype later still reaches instances that already exist. Choose "A class instance" in the walker and you will see doAction on Robot.prototype.
What class changes
The syntax also closes off several of the traps in the sections above:
- Calling without
newthrows aTypeError, so the silent global from section 1 cannot happen. - Class bodies are in strict mode, with or without a directive.
- Methods are not enumerable, so they stay out of
for/in. Compare thedoActionthatfor/inlisted in section 3. - A class cannot be used before its declaration runs. A function declaration is hoisted, which is what let the prototype assignments in section 2 come first. A class has the same temporal dead zone as
let.
Strict mode has one effect that catches people. A method pulled off its object loses its this, and in a class that this is undefined, so the call throws where sloppy code would have quietly used the global object. The objects page covers how a call sets this.
extends and super
extends does the wiring that took three statements in section 5. It links the prototypes without running the parent's constructor at setup. super(…) runs the parent's constructor on the new instance, which is the job of Robot.call(this, …), and it must run before a derived constructor uses this. super.doAction() calls the method the current one shadows. constructor comes out right with no extra line. The class constructors are linked too, so a static method on Robot is inherited by UltraRobot.
Fields, accessors and private members
A public field such as step = 1 is defined as an own property of each instance, before the constructor body runs, or just after super() returns in a subclass. A get accessor lives on the prototype, which is the same arrangement as disabled on a button. A static member goes on the class rather than on instances. A name starting with # is private: reading it from outside the class body is a SyntaxError. The Chrome console relaxes this rule for debugging, so test it in a script.
Private members are the one place where the prototype model does not describe what you get. MDN describes them as metadata attached to each instance, outside prototypal inheritance, and no Object method can reach them. They are not properties, so the walker cannot show them, and that output shows no #count. A subclass cannot read its parent's private names either.
The safer way to extend a built-in
A subclass answers the problem in section 4. It gives you a new method on a type of your own, and Array.prototype stays as it was. The chain of a Stack runs through Stack.prototype to Array.prototype, so it remains an array in every way. The DOM uses the same arrangement: a custom element is a class that extends HTMLElement, as the components series shows.
Transpile, need or not
When this syntax was new, the standing warning was to watch out for transpiling. Browsers in use did not all understand class, and Internet Explorer 11 never did, so a build step would rewrite classes as constructor functions and prototype assignments like those in section 5.
Treat that warning as history for class itself. MDN marks classes as Baseline widely available, across browsers since March 2016, and private members since July 2021. Microsoft ended support for the Internet Explorer 11 desktop app on most versions of Windows 10 in June 2022. For the newest syntax you meet, the question still applies. Check its Baseline status before adding a build step for it, as the how much JavaScript page describes.
8. Summary
| Concept | Key points |
|---|---|
| Constructors | An ordinary function called with new, capitalized by convention. new makes an object, sets its prototype, runs the function with this as that object, and returns it. |
| [[Prototype]] | Every object's internal link to another object or null. Read it with Object.getPrototypeOf. Avoid obj.__proto__. |
| .prototype | A property of functions. It holds the object that instances get as their [[Prototype]]. Instances do not have one. |
| Reads and writes | Reads walk the chain until the name appears, or reach null and give undefined. Writes go on the object itself and shadow anything further up. |
| Sharing | One function on the prototype serves all instances, and a change to it reaches instances that already exist. |
| The chain | Ends at null. A button walks through HTMLButtonElement, HTMLElement, Element, Node, EventTarget and Object by the same mechanism as a robot. |
| Extending built-ins | It works and it is shared with every script on the page. Names collide, future standards collide, and assigned methods show in for/in. Polyfills are the accepted reason. |
| Own and inherited | in and for/in walk. Object.keys and Object.hasOwn do not. Prefer Object.hasOwn to hasOwnProperty. |
| Inheritance by hand | Parent.call(this), Object.create(Parent.prototype), and restoring constructor. Child.prototype = new Parent() appears in old code and shares one parent's data. |
| class | Syntax over the same model, with stricter rules: needs new, strict mode, methods not enumerable, unusable before its declaration. extends and super do the wiring. Private # members sit outside the prototype model. |
| Transpiling class | A concern from the Internet Explorer years. Classes have run natively across browsers since 2016. |
