- object oriented JS has lots of complexity -- going to try to simplify common OO patterns prototypes "Inheritance" vs "behavior delegation" "There are no Classes in JS" -- we need a different syntax & design pattern for our software "OO vs OLOO"?
var Router = function() {
// Singleton!
if (Router.__instance__) {
return Router.__instance__;
}
Router.__instance__ = this;
this.routes = {};
};
Router.prototype.setRoute = function(match,fn) {
this.routes[match] = fn;
};
var myrouter = new Router();
var another = new Router();
myrouter === another;
var Router = function() {
// singleton
if (Router.__instance__) {
return Router.__instance__;
}
function setRoute(match,fn) {
routes[match] = fn;
}
var routes = {};
var publicAPI = Router.__instance__ = {
setRoute: setRoute
};
return publicAPI;
};
var myrouter = new Router();
var another = new Router();
myrouter === another;
Observer:
function PageController(router) {
this.router = router;
this.router.on("navigate", this.fetchPage.bind(this));
}
PageController.prototype.fetchPage = function(d) {
$.ajax({
url: d.page_url
}).done(this.loaded.bind(this, d.page_url));
};
PageController.prototype.loaded = function(d,u) {
// display the page content from `d`
// ...
this.router.emit("pageLoaded", u);
};
var router = new Router();
var thepage = new PageController(router);
- Any OO discussion requires discussion of Prototype.
- Every object is built by a constructor function/call
- When we call "new" with a function, it creates an object.
- It's not the same as instantiating a class -- it just constructs objects
Object-Oriented langauges like Java should be called Class-oriented. Only JS and Lua let you create objects without classes
A constructor makes an object "based on" its own prototype The phrase "based on" implies we take the prototype and we stamp out a copy of it. This isn't what happens in JS Rather than "based on", we should say a constructor makes an object "linked to" its own prototype This is like #2 from the things "new" does
function Foo(who) {
this.me = who;
}
Foo.prototype.identify = function() {
return "I am " + this.me;
};
var a1 = new Foo("a1");
var a2 = new Foo("a2");
a2.speak = function() {
alert("Hello, " + this.identify() + ".");
};
a2.constructor === Foo;
a2.constructor === a2.constructor;
a1.__proto__ === Foo.prototype;
a1.__proto__ === a2.__proto__;
note [[Prototype]] is the name given to a prototype link
What occurs when this code is interpreted by the JS engine? Before we even get to line 1:
- There is a function called "Object"
- There is an object that "Object" is linked to -- this object doesn't have a name, just Object.prototype
- Object.prototype has toString(), valueOf(), etc...
on line 1:
- we get a function called Foo
- we also get an object that Foo is linked to called Foo.prototype
- Foo.prototype is linked to Object.prototype (i.e. [[Prototype]])
- Foo.prototype has a property called Foo.prototype.constructor which points to Foo
Skipping line 2 for now... On line 4:
- We add "identify" property to Foo.prototype
Skipping line 5 Line 8, "a1 = new Foo('a1')":
- Brand new object gets created
- new object gets linked to Foo.prototype (i.e. [[Prototype]])
thispoints to a1- returns
this
Line 9, same as line 8:
- A new object, also linked to Foo.prototype, with label a2
Line 11, a2.speak:
- only the a2 object gets the speak property
Line 15:
- a1.constructor, which doesn't exist on a1
- we go up the prototype chain, or the
prototype links, i.e. [[Prototype]] - we then find
.constructorup the prototype chain in theFoo.prototypeobject - This does not mean that
Foo"constructed" a1
line 16:
- a2.constructor also points to
Foo
line 17:
- proto <-- "dunder" proto
- is there proto on a1? no.
- is there a proto on Foo.prototype? no.
- is there a proto on Object.prototype? yes.
- Object.prototype.proto is a getter which returns the "internal prototype binding of whatever the
thiskeyword is" - On line 17, the
thiskeyword is "a1" - the internal prototype binding of a1 is Foo.prototype
- a1.proto === Foo.prototype
Note proto was not a standard until ES6. Everyone but IE adopted it anyways. It's now in IE11 Note Object.getPrototypeOf(a1) was standardized in ES5, so it's in IE9. This is a standard form of proto
Line 18 a2.constructor.prototype:
- This will also get us to Foo.prototype
- The downside: a2.constructor and Foo.prototype are both writable properties
What happens if we call a1.identify()?
.identifyis not ona1.identifyis on Foo.prototypethisin Foo.prototype isa1
What if we change things a little:
function Foo(who) {
this.me = who;
}
Foo.prototype.identify = function() {
return "I am " + this.me;
};
var a1 = new Foo("a1");
a1.identify(); // "I am a1"
a1.identify = function() { // <-- Shadowing
alert("Hello, " + Foo.prototype.identify.call(this) + ".");
};
a1.identify(); // alerts: "Hello, I am a1."
The new a1.identify:
- exists in
a1, not inFoo.prototype - now when we call
a1.identify()we're calling theidentifyon thea1object - Foo.prototype.identify.call(this) <--
supervia "explicit polymorphism"
What if we did this?
function Foo(who) {
this.me = who;
}
Foo.prototype.identify = function() {
return "I am " + this.me;
};
Foo.prototype.speak = function() {
alert("Hello, " +
this.identify() + // super unicorn magic, no shadowing
".");
};
var a1 = new Foo("a1");
a1.speak(); // alerts: "Hello, I am a1."
We're trying to get to "Delegation" as a design pattern rather than classes
function Foo(who) {
this.me = who;
}
Foo.prototype.identify = function() {
return "I am " + this.me;
};
function Bar(who) {
Foo.call(this,who);
}
// Bar.prototype = new Foo(); // OR...
Bar.prototype = Object.create(Foo.prototype);
// Note: .constructor is borked here, need to fix
Bar.prototype.speak = function() {
alert("Hello, " + this.identify() + ".");
}
var b1 = new Bar("b1");
var b2 = new Bar("b2");
b1.speak(); // alerts "Hello, I am b1."
b2.speak(); // alerts "Hello, I am b2."
Bar.prototype = new Foo(); or Bar.prototype = Object.create(Foo.prototype)
- "Bar" is a child class of "Foo":
- using
newwill actually call Foo() function - Object.create will do the first two steps of "new" but not the last two -- Create an object and link it.
What is b1.constructor?
- Well, b1 doesn't have a constructor
- Bar.prototype doesn't have a constructor either...
- Foo.prototype does have a constructor. It's Foo
- We could add a
.constructorto Bar and point it to Bar, if we really want to - If we really wanted to do this, we'd want to use Object.define and make a non-enumerable property
This is all a big mess, and we're trying to get to something better. b1 [[Prototype]] Bar.prototype [[Prototype]] Foo.prototype
Functions are objects with a prototype that delegate up to Function.prototype This gives us .call, .apply, etc We should just focus on the objects, i.e. the "prototypes"
What is a constructor?
- A function that is called with the "new" keyword infront of it.
.constructoris just a property
What is [[Prototype]] and where does it come from?
- A linkage from one object to another object
- We can get it from Object.create
- We can also get it indirectly from step 2 of the 4-steps of the
newkeyword
How does [[Prototype]] affect how we deal with an object?
- We can call a property or method on an object reference
- if that object does not have that method or property, it delegates up the [[Prototype]] links
How do we find out where an object's [[Prototype]] points to? There's 3 ways
dunder protoor proto- Object.getPrototypeOf()
- obj.constructor.prototype
Remember how this can get unassigned?
function Foo(who) {
this.me = who;
}
Foo.prototype.speak = function() {
alert("Hello, I am " + this.me + ".");
};
var a1 = new Foo("a1");
$('#speak').click(a1.speak); // jquery will force `this` to be the button rather than the `a1` object
this is solved with .bind / hard bindings
careful if you do NotesManager.prototype = { ... } because you throw away the original prototype
In NotesManager.prototype.showHelp:
- in the function handler,
thisis the button - we could do hard binding with .bind(this) at the end of the function
- if we do hard binding, _handler is no longer the name of the function
- we can use var self = this; instead
- var self tends to be a code-smell, but this is an exception
In NotesManager.prototype.init:
- this.$open_help.bind -- we have to manually bind
thisto the handlers - this.$open_help.bind("click", this.handleOpenHelp.bind(this));
we can get rid of all the private variables also, make an instance of NotesManager: i.e. nm = new NotesManager();
we should also initialize this.notes = []; in the NotesManager constructor