Theprefix prot-, or proto-, comes from Greek and has the basic meaning "first in time" or "first formed." A prototype is someone or something that serves as a model or inspiration for those that come later. A successful fund-raising campaign can serve as a prototype for future campaigns. The legendary Robin Hood, the "prototypical" kindhearted and honorable outlaw, has been the inspiration for countless other romantic heroes. And for over a century, Vincent van Gogh has been the prototype of the brilliant, tortured artist who is unappreciated in his own time.
In programming, inheritance refers to passing down characteristics from a parent to a child so that a new piece of code can reuse and build upon the features of an existing one. JavaScript implements inheritance by using objects. Each object has an internal link to another object called its prototype. That prototype object has a prototype of its own, and so on until an object is reached with null as its prototype. By definition, null has no prototype and acts as the final link in this prototype chain. It is possible to mutate any member of the prototype chain or even swap out the prototype at runtime, so concepts like static dispatching do not exist in JavaScript.
Although classes are now widely adopted and have become a new paradigm in JavaScript, classes do not bring a new inheritance pattern. While classes abstract most of the prototypal mechanism away, understanding how prototypes work under the hood is still useful.
JavaScript objects are dynamic "bags" of properties (referred to as own properties). JavaScript objects have a link to a prototype object. When trying to access a property of an object, the property will not only be sought on the object but on the prototype of the object, the prototype of the prototype, and so on until either a property with a matching name is found or the end of the prototype chain is reached.
Note: Following the ECMAScript standard, the notation someObject.[[Prototype]] is used to designate the prototype of someObject. The [[Prototype]] internal slot can be accessed and modified with the Object.getPrototypeOf() and Object.setPrototypeOf() functions respectively. This is equivalent to the JavaScript accessor __proto__ which is non-standard but de-facto implemented by many JavaScript engines. To prevent confusion while keeping it succinct, in our notation we will avoid using obj.__proto__ but use obj.[[Prototype]] instead. This corresponds to Object.getPrototypeOf(obj).
It should not be confused with the func.prototype property of functions, which instead specifies the [[Prototype]] to be assigned to all instances of objects created by the given function when used as a constructor. We will discuss the prototype property of constructor functions in a later section.
There are several ways to specify the [[Prototype]] of an object, which are listed in a later section. For now, we will use the __proto__ syntax for illustration. It's worth noting that the __proto__: ... syntax is different from the obj.__proto__ accessor: the former is standard and not deprecated.
In an object literal like a: 1, b: 2, __proto__: c , the value c (which has to be either null or another object) will become the [[Prototype]] of the object represented by the literal, while the other keys like a and b will become the own properties of the object. This syntax reads very naturally, since [[Prototype]] is just an "internal property" of the object.
JavaScript does not have "methods" in the form that class-based languages define them. In JavaScript, any function can be added to an object in the form of a property. An inherited function acts just as any other property, including property shadowing as shown above (in this case, a form of method overriding).
This way, all boxes' getValue method will refer to the same function, lowering memory usage. However, manually binding the __proto__ for every object creation is still very inconvenient. This is when we would use a constructor function, which automatically sets the [[Prototype]] for every object manufactured. Constructors are functions called with new.
Classes are syntax sugar over constructor functions, which means you can still manipulate Box.prototype to change the behavior of all instances. However, because classes are designed to be an abstraction over the underlying prototype mechanism, we will use the more-lightweight constructor function syntax for this tutorial to fully demonstrate how prototypes work.
This misfeature is called monkey patching. Doing monkey patching risks forward compatibility, because if the language adds this method in the future but with a different signature, your code will break. It has led to incidents like the SmooshGate, and can be a great nuisance for the language to advance since JavaScript tries to "not break the web".
It may be interesting to note that due to historical reasons, some built-in constructors' prototype property are instances themselves. For example, Number.prototype is a number 0, Array.prototype is an empty array, and RegExp.prototype is /(?:)/.
You may also see some legacy code using Object.create() to build the inheritance chain. However, because this reassigns the prototype property and removes the constructor property, it can be more error-prone, while performance gains may not be apparent if the constructors haven't created any instances yet.
In JavaScript, as mentioned above, functions are able to have properties. All functions have a special property named prototype. Please note that the code below is free-standing (it is safe to assume there is no other JavaScript on the webpage other than the below code). For the best learning experience, it is highly recommended that you open a console, navigate to the "console" tab, copy-and-paste in the below JavaScript code, and run it by pressing the Enter/Return key. (The console is included in most web browser's Developer Tools. More information is available for Firefox Developer Tools, Chrome DevTools, and Edge DevTools.)
We can now use the new operator to create an instance of doSomething() based on this prototype. To use the new operator, call the function normally except prefix it with new. Calling a function with the new operator returns an object that is an instance of the function. Properties can then be added onto this object.
As seen above, the [[Prototype]] of doSomeInstancing is doSomething.prototype. But, what does this do? When you access a property of doSomeInstancing, the runtime first looks to see if doSomeInstancing has that property.
If doSomeInstancing does not have the property, then the runtime looks for the property in doSomeInstancing.[[Prototype]] (a.k.a. doSomething.prototype). If doSomeInstancing.[[Prototype]] has the property being looked for, then that property on doSomeInstancing.[[Prototype]] is used.
Otherwise, if doSomeInstancing.[[Prototype]] does not have the property, then doSomeInstancing.[[Prototype]].[[Prototype]] is checked for the property. By default, the [[Prototype]] of any function's prototype property is Object.prototype. So, doSomeInstancing.[[Prototype]].[[Prototype]] (a.k.a. doSomething.prototype.[[Prototype]] (a.k.a. Object.prototype)) is then looked through for the property being searched for.
If the property is not found in doSomeInstancing.[[Prototype]].[[Prototype]], then doSomeInstancing.[[Prototype]].[[Prototype]].[[Prototype]] is looked through. However, there is a problem: doSomeInstancing.[[Prototype]].[[Prototype]].[[Prototype]] does not exist, because Object.prototype.[[Prototype]] is null. Then, and only then, after the entire prototype chain of [[Prototype]]'s is looked through, the runtime asserts that the property does not exist and conclude that the value at the property is undefined.
When using the __proto__ key in object initializers, pointing the __proto__ key to something that is not an object only fails silently without throwing an exception. Contrary to the Object.prototype.__proto__ setter, __proto__ in object literal initializers is standardized and optimized, and can even be more performant than Object.create. Declaring extra own properties on the object at creation is more ergonomic than Object.create.
Constructor functions have been available since very early JavaScript. Therefore, it is very fast, very standard, and very JIT-optimizable. However, it's also hard to "do properly" because methods added this way are enumerable by default, which is inconsistent with the class syntax or how built-in methods behave. Doing longer inheritance chains is also error-prone, as previously demonstrated.
Similar to the __proto__ key in object initializers, Object.create() allows directly setting the prototype of an object at creation time, which permits the runtime to further optimize the object. It also allows the creation of objects with null prototype, using Object.create(null). The second parameter of Object.create() allows you to precisely specify the attributes of each property in the new object, which can be a double-edged sword:
Classes offer the highest readability and maintainability when defining complex inheritance structures. Private properties are a feature with no trivial replacement in prototypal inheritance. However, classes are less optimized than traditional constructor functions and are not supported in older environments.
While all methods above will set the prototype chain at object creation time, Object.setPrototypeOf() allows mutating the [[Prototype]] internal property of an existing object. It can even force a prototype on a prototype-less object created with Object.create(null) or remove the prototype of an object by setting it to null.
However, you should set the prototype during creation if possible, because setting the prototype dynamically disrupts all optimizations that engines have made to the prototype chain. It might cause some engines to recompile your code for de-optimization, to make it work according to the specs.
3a8082e126