Question
I'm not that into dynamic programming languages but I've written my fair share of JavaScript code. I never really got my head around this prototype-based programming, does any one know how this works?
var obj = new Object();
obj.prototype.test = function() { alert('Hello?'); };
var obj2 = new obj();
obj2.test();
I remember a lot discussion I had with people a while back (I'm not exactly sure what I'm doing) but as I understand it, there's no concept of a class. It's just an object, and instances of those objects are clones of the original, right?
But what is the exact purpose of this ".prototype" property in JavaScript? How does it relate to instantiating objects?
Update: correct way
var obj = new Object(); // not a functional object
obj.prototype.test = function() { alert('Hello?'); }; // this is wrong!
function MyObject() {} // a first class functional object
MyObject.prototype.test = function() { alert('OK'); } // OK
Also these slides really helped a lot.
Answer
Every JavaScript object [has an internal "slot"](https://www.ecma-
international.org/ecma-262/10.0/index.html#sec-ordinary-object-internal-
methods-and-internal-slots) called [[Prototype]]
whose value is either
null
or an object
. You can think of a slot as a property on an object,
internal to the JavaScript engine, hidden from the code you write. The square
brackets around [[Prototype]]
are deliberate, and are an ECMAScript
specification convention to denote internal slots.
The value pointed at by the [[Prototype]]
of an object, is colloquially
known as "the prototype of that object."
If you access a property via the dot (obj.propName
) or bracket
(obj['propName']
) notation, and the object does not directly have such a
property (ie. an own property , checkable via
obj.hasOwnProperty('propName')
), the runtime looks for a property with that
name on the object referenced by the [[Prototype]]
instead. If the
[[Prototype]]
also does not have such a property, its [[Prototype]]
is
checked in turn, and so on. In this way, the original object's prototype
chain is walked until a match is found, or its end is reached. At the top of
the prototype chain is the null
value.
Modern JavaScript implementations allow read and/or write access to the
[[Prototype]]
in the following ways:
- The
new
operator (configures the prototype chain on the default object returned from a constructor function), - The
extends
keyword (configures the prototype chain when using the class syntax), Object.create
will set the supplied argument as the[[Prototype]]
of the resulting object,Object.getPrototypeOf
andObject.setPrototypeOf
(get/set the[[Prototype]]
after object creation), and- The standardized accessor (ie. getter/setter) property named
__proto__
(similar to 4.)
Object.getPrototypeOf
and Object.setPrototypeOf
are preferred over
__proto__
, in part because the behavior of o.__proto__
is
unusual when an object has a
prototype of null
.
An object's [[Prototype]]
is initially set during object creation.
If you create a new object via new Func()
, the object's [[Prototype]]
will, by default, be set to the object referenced by Func.prototype
.
Note that, therefore, all classes, and all functions that can be used with
thenew
operator, have a property named .prototype
in addition to their own
[[Prototype]]
internal slot. This dual use of the word "prototype" is the
source of endless confusion amongst newcomers to the language.
Using new
with constructor functions allows us to simulate classical
inheritance in JavaScript; although JavaScript's inheritance system is - as we
have seen - prototypical, and not class-based.
Prior to the introduction of class syntax to JavaScript, constructor functions
were the only way to simulate classes. We can think of properties of the
object referenced by the constructor function's .prototype
property as
shared members; ie. members which are the same for each instance. In class-
based systems, methods are implemented the same way for each instance, so
methods are conceptually added to the .prototype
property; an object's
fields, however, are instance-specific and are therefore added to the object
itself during construction.
Without the class syntax, developers had to manually configure the prototype chain to achieve similar functionality to classical inheritance. This led to a preponderance of different ways to achieve this.
Here's one way:
function Child() {}
function Parent() {}
Parent.prototype.inheritedMethod = function () { return 'this is inherited' }
function inherit(child, parent) {
child.prototype = Object.create(parent.prototype)
child.prototype.constructor = child
return child;
}
Child = inherit(Child, Parent)
const o = new Child
console.log(o.inheritedMethod()) // 'this is inherited'
...and here's another way:
function Child() {}
function Parent() {}
Parent.prototype.inheritedMethod = function () { return 'this is inherited' }
function inherit(child, parent) {
function tmp() {}
tmp.prototype = parent.prototype
const proto = new tmp()
proto.constructor = child
child.prototype = proto
return child
}
Child = inherit(Child, Parent)
const o = new Child
console.log(o.inheritedMethod()) // 'this is inherited'
The class syntax introduced in ES2015 simplifies things, by providing
extends
as the "one true way" to configure the prototype chain in order to
simulate classical inheritance in JavaScript.
So, similar to the code above, if you use the class syntax to create a new object like so:
class Parent { inheritedMethod() { return 'this is inherited' } }
class Child extends Parent {}
const o = new Child
console.log(o.inheritedMethod()) // 'this is inherited'
...the resulting object's [[Prototype]]
will be set to an instance of
Parent
, whose [[Prototype]]
, in turn, is Parent.prototype
.
Finally, if you create a new object via Object.create(foo)
, the resulting
object's [[Prototype]]
will be set to foo
.