Skip to content

Instantly share code, notes, and snippets.

@chadfurman
Created February 3, 2017 07:23
Show Gist options
  • Select an option

  • Save chadfurman/0c49aa763679a94ac753e1f0b128f260 to your computer and use it in GitHub Desktop.

Select an option

Save chadfurman/0c49aa763679a94ac753e1f0b128f260 to your computer and use it in GitHub Desktop.
h5

Prototypes

  • 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

Prototypes explained (part 1 & 2)

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]])
  • this points 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 .constructor up the prototype chain in the Foo.prototype object
  • 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 this keyword is"
  • On line 17, the this keyword 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

Prototype Linkages

What happens if we call a1.identify()?

  • .identify is not on a1
  • .identify is on Foo.prototype
  • this in Foo.prototype is a1

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 in Foo.prototype
  • now when we call a1.identify() we're calling the identify on the a1 object
  • Foo.prototype.identify.call(this) <-- super via "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

prototype: Objects Linked

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 new will 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 .constructor to 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

Linked Prototype Diagram

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"

Object Prototypes

What is a constructor?

  • A function that is called with the "new" keyword infront of it.
  • .constructor is 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 new keyword

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 proto or 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

Exercise 3 Solution

careful if you do NotesManager.prototype = { ... } because you throw away the original prototype

In NotesManager.prototype.showHelp:

  • in the function handler, this is 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 this to 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment