<!DOCTYPE html>
<!--
Javascript prototypes explained.
-->
<html>
<head>
<meta charset="utf-8">
</head>
<body>
</body>
<script>
// some utility functions...
function showKeys(prefix, obj) {
if( typeof obj === 'undefined' ) {
document.write("<p>" + prefix + ": undefined</p>");
}
else {
document.write("<p><b>" + prefix + " Type:</b> " +
typeof obj + "</p>");
var json = JSON.stringify(Object.keys(obj));
if( json !== undefined ) {
document.write("<p><b>" + prefix + " Keys:</b> " +
json + "</p>");
}
}
}
function showOwn(name, obj) {
document.write("<h2>" + name + "</h2>");
showKeys("Object", obj);
showKeys("__proto__", obj.__proto__);
showKeys("prototype", obj.prototype);
document.write("<hr>");
}
function step(s) {
document.write("<h1>" + s + "</h1>");
}
function print(msg) {
document.write("<p><b>Print:</b> " + msg + "</p>");
}
function error(msg) {
document.write("<p><b>Assert Failed:</b> " + msg + "</p>");
}
function assert(condition, msg) {
if( !condition )
error(msg);
}
/////////////////////////////////////////////////////////////////////////////
//
// Javascript prototype inheritance explained in 10 easy steps.
//
// Run this in a browser, and follow the output with the comments.
//
//
// 1) Everything is an object. MyObject is an object with a type of function.
// MyObject itself contains no properties. But it is a function,
// which creates a property 'hello' when it runs.
//
step("1.");
function MyObject() {
this.hello = function() {
print("hello #1");
}
this.this_is_new_MyObject = "";
}
showOwn("MyObject", MyObject);
//
// 2) Every object has an internal [[Prototype]] property, which some
// JS implementations allow access to via __proto__. This property
// controls the prototype chain. If you access a property in the object
// which doesn't exist, JS will check __proto__, and then
// __proto__.__proto__, and so on up the chain until the end.
//
// This is why there is a difference between hasOwnProperty() and
// the in operator. 'in' looks in the prototype chain.
//
step("2.");
assert(MyObject.__proto__ === Function.prototype, "MyObject derives from Function");
//
// 3) Every object can have an optional .prototype property. This is
// NOT part of the prototype chain, but simply determines the
// value of a new object's __proto__.
//
// Side notes:
// Since MyObject is a function, it's __proto__ comes from
// Function.prototype.
//
// MyObject.prototype is an empty Object created automatically,
// but we can override this later.
//
step("3.");
showOwn("MyObject.prototype", MyObject.prototype);
//
// 4) We can override our earlier prototype with a new object.
//
step("4.");
function Base() {
this.hello = function() {
print("Base hello");
}
this.goodbye = function() {
print("Base goodbye");
}
this.this_is_new_Base = "";
}
var base = new Base();
MyObject.prototype = base;
var o = new MyObject();
showOwn("MyObject", MyObject);
showOwn("base", base);
showOwn("o", o);
//
// 5) We now have:
// o A new object, with a property .hello referencing hello1,
// since the MyObject constructor function set it up.
// o.__proto__ This points to the new Base() we created, taken from
// MyObject.prototype.
// o.prototype undefined
//
step("5.");
o.hello(); // hello1
o.goodbye(); // there is no goodbye property in the object, so uses
// o.__proto__.goodbye()
assert(o.goodbye === o.__proto__.goodbye, "goodbye comes from prototype");
assert(o.prototype === undefined, "o.prototype is undefined");
//
// 6) Changing the base object also changes the behaviour of any object
// descending from it! In other words, __proto__ objects are
// SHARED across all objects that have the same object reference.
//
step("6.");
base.goodbye = function() {
print("Base was changed, goodbye!");
}
o.goodbye(); // o.__proto__ still points to base, which has now been
// changed, so prints new string
showOwn("base", base);
showOwn("o", o);
//
// 7) But replacing the entire object at the .prototype level does not
// change the prototype chain, since that is based on the hidden
// __proto__. You can change the object itself *through* the
// prototype reference, but replacing it loses the reference.
//
step("7.");
// adding a wave() member to base through the MyObject.prototype reference
MyObject.prototype.wave = function() {
print("No talking, just waving");
}
o.wave();
assert(o.__proto__ === base, "base is still the prototype object");
assert(base === MyObject.prototype, "even through MyObject");
base.wave(); // new function created through MyObject.prototype ref!
var empty_proto = new Object();
MyObject.prototype = empty_proto; // but resetting to something empty
o.goodbye(); // does not change o.__proto__
o.wave(); // which still references base...
// these functions still exist via
// __proto__
showOwn("MyObject", MyObject);
showOwn("base", base);
showOwn("o", o);
//
// 8) But new objects have their __proto__ setup with the new prototype.
//
step("8.");
var p = new MyObject();
assert(p.__proto__ === empty_proto, "assigned from the new .prototype");
assert(p.goodbye === undefined, "p.goodbye is undefined");
assert(p.hello !== o.hello, "these hello functions are unique, created twice by the MyObject constructor, doing the same thing");
p.hello();
o.hello();
showOwn("p", p);
//
// 9) Remember... EVERYTHING is an object, and therefore you can
// inherit from it.
//
step("9.");
// Create dummy function
function F() {
this.this_is_new_F = "";
}
F.prototype = o;
// New object uses o as its __proto__
var q = new F();
showOwn("q, fresh", q);
assert(q.__proto__ === o, "q derives from o");
assert(q.__proto__.hello === o.hello, "q.o.hello done explicitly");
assert(q.__proto__.__proto__.hello === base.hello, "q.o.base.hello done explicitly");
q.hello(); // q.__proto__.hello() comes first in the chain
assert(q.__proto__.__proto__.goodbye === base.goodbye, "the only goodbye in the chain... we can't test this explicitly since q.goodbye follows the chain automatically");
q.goodbye(); // still changed! (q.__proto__.__proto__.goodbye())
// q can hide its prototype
q.hello = function() {
print("q.hello here");
}
q.hello(); // q's version
q.__proto__.hello(); // but the prototype still exists: hello1
q.__proto__.__proto__.hello(); // as well as the base class
showOwn("q, with hide", q);
//
// 10) Use Object.create() to create derived objects more easily, instead
// of the manual method above.
//
step("10.");
var r = Object.create(q);
r.hello(); // q.hello from the prototype
r.__proto__.__proto__.hello(); // hello1
showOwn("r", r);
//
// 11) If you use the var self = this; tactic, note that you will not be
// able to override base class member functions like you could if you
// used this.
//
step("11.");
function ThisBase() {
this.work = function() {
print("ThisBase.work");
}
this.hello = function() {
print("ThisBase.hello");
this.work();
};
}
function SelfBase() {
var self = this;
self.work = function() {
print("SelfBase.work");
};
self.hello = function() {
print("SelfBase.hello");
self.work(); // self never changes, regardless of
// derived class, since it's a closure variable
};
}
function D(){
this.work = function() {
print("D.work");
};
}
D.prototype = new ThisBase();
var d1 = new D();
D.prototype = new SelfBase();
var d2 = new D();
d1.hello(); // work() overridden
d2.hello(); // work() not overridden
//
// Conclusion: constructors create fresh instances of their members and
// functions each time they are called, but __proto__ simply contains
// a reference to the prototype object, copied from .prototype, which is
// the same object for all objects that reference it via their internal
// __proto__ variables; and these __proto__ variables never change,
// although .prototype can change, which only affects the next
// object created with new.
//
</script>
</html>