Skip to content
Browse files

Add back in article now that it's done!.

  • Loading branch information...
1 parent e4a9219 commit d857e25ae92825838ad9e7bb98cd685b25f1f855 @creationix committed Oct 11, 2010
View
83 articles/object-graphs-2.markdown
@@ -0,0 +1,83 @@
+Title: Learning Javascript with Object Graphs (Part II)
+Author: Tim Caswell
+Date: Mon Oct 11 2010 08:24:25 GMT-0700 (PDT)
+Node: v0.2.3
+
+The first article using graphs to describe JavaScript semantics was so popular that I've decided to try the technique with some more advanced ideas. In this article I'll explain three common techniques for creating objects. They are constructor with prototype, pure prototypal, and object factory.
+
+My goal is that this will help people understand the strengths and weaknesses of each technique and understand what's really going on.
+
+## Classical JavaScript Constructors
+
+First let's create a simple constructor function with a prototype. This is the closest thing to a class you're going to find in native JavaScript. It's extremely powerful and efficient, but doesn't quite work like you would expect if coming from a language with classes.
+
+<object-graphs-2/classical.js#rectangle>
+
+Now let's define a new class of objects called Squares that inherit from Rectangles. To do inheritance, the constructor's `prototype` has to inherit from the parent constructor's `prototype`. Here we're overriding `getPerimeter` to make it slightly more efficient and to show how to override functions.
+
+<object-graphs-2/classical.js#square>
+
+Usage is straight-forward. Just create an instance of each and call a function on each.
+
+<object-graphs-2/classical.js#test*>
+
+This is the resulting data structure. Dashed lines mean object inheritance.
+
+![classical](object-graphs-2/classical.dot)
+
+<br style="clear:left"/>
+
+Notice that there is little difference between the `rect` instance and `Square.prototype`. They are both simply objects that inherit from `Rectangle.prototype`. JavaScript is just a series of linked objects when you get down to it. The only objects that are special are functions in that they take parameters and can hold executable code and point to scopes.
+
+## Pure Prototypal Objects
+
+Let's do the same example, but without using constructor functions. This time we'll just use plain prototypal inheritance.
+
+Let's define a Rectangle prototype that the base pattern for all our objects.
+
+<object-graphs-2/prototypal.js#rectangle>
+
+Now let's define a sub-object called Square that overrides some of the properties to change the behavior.
+
+<object-graphs-2/prototypal.js#square>
+
+To create actual instances of these prototypes, we simply create new objects that inherit from the prototype objects and then set their local state manually.
+
+<object-graphs-2/prototypal.js#test*>
+
+Here is the resultant graph of objects.
+
+![classical](object-graphs-2/prototypal.dot)
+
+<br style="clear:left"/>
+
+This isn't quite as powerful as the constructor + prototype method, but is often much easier to understand since there is less indirection. Also if you come from a language that has pure prototypal inheritance, you'll be happy to know it's possible in JavaScript too.
+
+## Object Factories
+
+One of my favorite methods for creating objects is to use a factory function. The difference is that instead of defining a prototype object with all my shared functions and then creating instances of those, I simply call a function that returns a new object every time.
+
+This example is a super simple MVC system. The controller function takes in as parameters the model and view objects and outputs a new controller object. All state is stored in the closure via the scope.
+
+<object-graphs-2/factory.js#controller>
+
+To use this, simple call the function with the desired parameters. Notice how we can use these directly as event handlers without having to first bind the function to the object. Since it doesn't use `this` internally, there is no need to mess with the value of `this`.
+
+<object-graphs-2/factory.js#usage>
+
+ // Output
+ View now has 5
+ View now has 6
+ View now has 5
+ Saving value 5 somewhere
+ Now hiding view
+
+Here is the object graph that results from this code. Notice that we have access to the two passed in anonymous objects via the hidden `[scope]` property of the functions. Or in other words, we have access to `model` and `view` from the closure created by the factory function.
+
+![classical](object-graphs-2/factory.dot)
+
+<br style="clear:left"/>
+
+## Conclusion
+
+There is so much more I want to explore, but I like to keep these articles somewhat short and bite-size. If there is demand, I'll write a part three explaining how to do ruby-style mixins and other advanced topics.
View
45 articles/object-graphs-2/classical.dot
@@ -0,0 +1,45 @@
+digraph finite_state_machine {
+ size="8.3,8"
+ rankdir = "LR"
+
+ /* Execution Contexts */
+ node [shape=Mrecord, fillcolor=beige, style=filled];
+ top [label="<__proto__>[ Scope ]|<Rectangle>Rectangle|<rect>rect|<Square>Square|<sqr>sqr"];
+
+ /* Normal Objects */
+ node [shape = Mrecord, fillcolor=lightskyblue, style=filled];
+ ob_RectProto [label = "<__proto__>[ Object ]|<constructor>constructor|<getArea>getArea|<getPerimeter>getPerimeter|<toString>toString"];
+ ob_SquareProto [label = "<__proto__>[ Object ]|<constructor>constructor|<getPerimeter>getPerimeter"];
+ ob_rect [label = "<__proto__>[ Object ]|<width>width=6|<height>height=4"];
+ ob_sqr [label = "<__proto__>[ Object ]|<width>width=5|<height>height=5"];
+
+ /* Function Objects */
+ node [shape = Mrecord, fillcolor=orange, style=filled];
+ fn_Rectangle [label="<__proto__>[Rectangle(width, height)]|\{this.width = width…\}|<prototype>prototype"];
+ fn_Square [label="<__proto__>[Square(width)]|\{this.width = width…\}|<prototype>prototype"];
+ fn_getArea [label="<__proto__>[getArea()]|\{return this.width * this.height\}"];
+ fn_getPerimeter [label="<__proto__>[getPerimeter()]|\{return 2 * (this.width + this.height)\}"];
+ fn_toString [label="<__proto__>[toString()]|\{return this.constructor.name…\}"];
+ fn_getPerimeter2 [label="<__proto__>[getPerimeter()]|\{return 4 * this.width\}"];
+
+ /* References */
+ top:Rectangle -> fn_Rectangle:__proto__;
+ top:Square -> fn_Square:__proto__;
+ top:rect -> ob_rect:__proto__;
+ top:sqr -> ob_sqr:__proto__;
+ fn_Rectangle:prototype -> ob_RectProto:__proto__;
+ fn_Square:prototype -> ob_SquareProto:__proto__;
+ ob_RectProto:constructor -> fn_Rectangle:__proto__;
+ ob_RectProto:getArea -> fn_getArea:__proto__;
+ ob_RectProto:getPerimeter -> fn_getPerimeter:__proto__;
+ ob_RectProto:toString -> fn_toString:__proto__;
+ ob_SquareProto:constructor -> fn_Square:__proto__;
+ ob_SquareProto:getPerimeter -> fn_getPerimeter2:__proto__;
+
+ /* Inheritance Chains */
+ edge [style=dashed]
+ ob_rect:__proto__ -> ob_RectProto:__proto__;
+ ob_sqr:__proto__ -> ob_SquareProto:__proto__;
+ ob_SquareProto:__proto__ -> ob_RectProto:__proto__;
+
+}
View
28 articles/object-graphs-2/classical.js
@@ -0,0 +1,28 @@
+//rectangle
+function Rectangle(width, height) {
+ this.width = width;
+ this.height = height;
+}
+Rectangle.prototype.getArea = function getArea() {
+ return this.width * this.height;
+};
+Rectangle.prototype.getPerimeter = function getPerimeter() {
+ return 2 * (this.width + this.height);
+};
+Rectangle.prototype.toString = function toString() {
+ return this.constructor.name + " a=" + this.getArea() + " p=" + this.getPerimeter();
+};
+//square
+function Square(side) {
+ this.width = side;
+ this.height = side;
+}
+Square.prototype.__proto__ = Rectangle.prototype;
+Square.prototype.getPerimeter = function getPerimeter() {
+ return this.width * 4;
+};
+//test
+var rect = new Rectangle(6, 4);
+var sqr = new Square(5);
+console.log(rect.toString())
+console.log(sqr.toString())
View
44 articles/object-graphs-2/factory.dot
@@ -0,0 +1,44 @@
+digraph finite_state_machine {
+ size="8.3,8"
+ rankdir = "LR"
+
+ /* Execution Contexts */
+ node [shape=Mrecord, fillcolor=beige, style=filled];
+ top [label="<__proto__>[ Scope ]|<Controller>Controller|<on>on"];
+ closure [label="<__proto__>[ Scope ]|<view>view|<model>model"];
+
+ /* Normal Objects */
+ node [shape = Mrecord, fillcolor=lightskyblue, style=filled];
+ ob_view [label="<__proto__>[ Object ]|<update>update|<close>close"]
+ ob_model [label="<__proto__>[ Object ]|<value>value=5|<save>save"];
+ ob_on [label="<__proto__>[ Object ]|<up>up|<down>down|<save>save"];
+
+ /* Function Objects */
+ node [shape = Mrecord, fillcolor=orange, style=filled];
+ fn_update [label="<__proto__>[update(value)]|\{ console.log(…) \}|<__scope__>[scope]"]
+ fn_close [label="<__proto__>[close()]|\{ console.log(…) \}|<__scope__>[scope]"]
+ fn_save [label="<__proto__>[save()]|\{ console.log(…) \}"];
+ fn_Controller [label="<__proto__>[Controller(model, view)]|\{ view.update(model.value) … \}"]
+ fn_onUp [label="<__proto__>[onUp()]|\{ model.value++ … \}|<__scope__>[scope]"];
+ fn_onDown [label="<__proto__>[onDown()]|\{ model.value-- … \}|<__scope__>[scope]"];
+ fn_onSave [label="<__proto__>[onSave()]|\{ model.save() … \}|<__scope__>[scope]"];
+
+ /* References */
+ ob_view:update -> fn_update:__proto__;
+ ob_view:close -> fn_close:__proto__;
+ ob_model:save -> fn_save:__proto__;
+ top:Controller -> fn_Controller:__proto__;
+ top:on -> ob_on;
+ ob_on:up -> fn_onUp:__proto__;
+ ob_on:down -> fn_onDown:__proto__;
+ ob_on:save -> fn_onSave:__proto__;
+ fn_onUp:__scope__ -> closure;
+ fn_onDown:__scope__ -> closure;
+ fn_onSave:__scope__ -> closure;
+ closure:view -> ob_view:__proto__;
+ closure:model -> ob_model:__proto__;
+
+ /* Inheritance Chains */
+ edge [style=dashed]
+ closure -> top:__proto__;
+}
View
40 articles/object-graphs-2/factory.js
@@ -0,0 +1,40 @@
+//controller
+function Controller(model, view) {
+ view.update(model.value);
+ return {
+ up: function onUp(evt) {
+ model.value++;
+ view.update(model.value);
+ },
+ down: function onDown(evt) {
+ model.value--;
+ view.update(model.value);
+ },
+ save: function onSave(evt) {
+ model.save();
+ view.close();
+ }
+ };
+}
+//usage
+var on = Controller(
+ // Inline a mock model
+ {
+ value: 5,
+ save: function save() {
+ console.log("Saving value " + this.value + " somewhere");
+ }
+ },
+ // Inline a mock view
+ {
+ update: function update(newValue) {
+ console.log("View now has " + newValue);
+ },
+ close: function close() {
+ console.log("Now hiding view");
+ }
+ }
+);
+setTimeout(on.up, 100);
+setTimeout(on.down, 200);
+setTimeout(on.save, 300);
View
43 articles/object-graphs-2/prototypal.dot
@@ -0,0 +1,43 @@
+digraph finite_state_machine {
+ size="8.3,5"
+ rankdir = "LR"
+
+
+ /* Execution Contexts */
+ node [shape=Mrecord, fillcolor=beige, style=filled];
+ top [label="<__proto__>[ Scope ]|<Rectangle>Rectangle|<rect>rect|<Square>Square|<sqr>sqr"];
+
+ /* Normal Objects */
+ node [shape = Mrecord, fillcolor=lightskyblue, style=filled];
+ ob_Rectangle [label = "<__proto__>[ Object ]|<name>name=\"Rectangle\"|<getArea>getArea|<getPerimeter>getPerimeter|<toString>toString"];
+ ob_Square [label = "<__proto__>[ Object ]|<name>name=\"Square\"|<getArea>getArea|<getPerimeter>getPerimeter"];
+ ob_rect [label = "<__proto__>[ Object ]|<width>width=6|<height>height=4"];
+ ob_sqr [label = "<__proto__>[ Object ]|<width>width=5"];
+
+ /* Function Objects */
+ node [shape = Mrecord, fillcolor=orange, style=filled];
+ fn_getArea [label="<__proto__>[getArea()]|\{return this.width * this.height\}"];
+ fn_getPerimeter [label="<__proto__>[getPerimeter()]|\{return 2 * (this.width + this.height)\}"];
+ fn_toString [label="<__proto__>[toString()]|\{return this.name + \" a=\" + …\}"];
+ fn_getArea2 [label="<__proto__>[getArea()]|\{return this.width * this.width\}"];
+ fn_getPerimeter2 [label="<__proto__>[getPerimeter()]|\{return 4 * this.width\}"];
+
+ /* References */
+ top:Rectangle -> ob_Rectangle:__proto__;
+ top:Square -> ob_Square:__proto__;
+ top:rect -> ob_rect:__proto__;
+ top:sqr -> ob_sqr:__proto__;
+ ob_Rectangle:getArea -> fn_getArea:__proto__;
+ ob_Rectangle:getPerimeter -> fn_getPerimeter:__proto__;
+ ob_Rectangle:toString -> fn_toString:__proto__;
+ ob_Square:getArea -> fn_getArea2:__proto__;
+ ob_Square:getPerimeter -> fn_getPerimeter2:__proto__;
+
+ /* Inheritance Chains */
+ edge [style=dashed]
+ ob_rect:__proto__ -> ob_Rectangle:__proto__;
+ ob_sqr:__proto__ -> ob_Square:__proto__;
+ ob_Square:__proto__ -> ob_Rectangle:__proto__;
+
+
+}
View
32 articles/object-graphs-2/prototypal.js
@@ -0,0 +1,32 @@
+//rectangle
+var Rectangle = {
+ name: "Rectangle",
+ getArea: function getArea() {
+ return this.width * this.height;
+ },
+ getPerimeter: function getPerimeter() {
+ return 2 * (this.width + this.height);
+ },
+ toString: function toString() {
+ return this.name + " a=" + this.getArea() + " p=" + this.getPerimeter();
+ }
+};
+//square
+var Square = {
+ name: "Square",
+ getArea: function getArea() {
+ return this.width * this.width;
+ },
+ getPerimeter: function getPerimeter() {
+ return this.width * 4;
+ },
+};
+Square.__proto__ = Rectangle;
+//test
+var rect = Object.create(Rectangle);
+rect.width = 6;
+rect.height = 4;
+var square = Object.create(Square);
+square.width = 5;
+console.log(rect.toString());
+console.log(square.toString());

0 comments on commit d857e25

Please sign in to comment.
Something went wrong with that request. Please try again.