4

Having a Java-background, when I switched to Javascript, I (lazily) tried to stick with what I knew regarding oop, i.e. a classical inheritance. I'm working on a web-app (that I made), and used this kind of inheritance. However, I'm considering to change my code and rewrite the OOP parts to do prototypal inheritance (two reasons: I read a lot that it's better, and secondly, I need to try the other way in order to have a better understanding of it).

My app creates data visualisations (with D3js, but it's not the topic), and organised my code this way:

function SVGBuilder( dataset ) {
  this.data = dataset;
  this.xCoord;
  this.startDisplaying = function() {
    // stuff
    this.displayElement();
  }
}

The displayElement() method is defined in "classes" inheriting from SVGBuilder (which is more or less an abstract class). I then have:

function SpiralBuilder( dataset ) {
  SVGBuilder.call( this, dataset );
  this.displayElement = function() {
    // stuff
  };
}
SpiralBuilder.inheritsFrom( SVGBuilder );

I have several other "builders" based on the same structure.

The script calling the builder looks like this (it's a bit more complicated since it select the correct constructor based on the user input):

var builder = new SpiralBuilder( data );
builder.startDisplaying();

Now come the "conversion part". I read quite a lot about this, from Douglas Crockford's article, to parts of the Eloquent Javascript. In Aadit M Shah's comment, he proposes a structure which would look like this:

var svgBuilder = {
  data: [],
  xCoord: 0,  // ?
  create: function( dataset ) {
    var svgBuilder= Object.create(this);
    svgBuilder.data = dataset;
    return svgBuilder;
  },
  startDisplaying: function() {
    // stuff
  }
}

However, at this point, I'm stuck. Firstly (technical question), can I declare variables (data, xCoord) without initialising them in this pattern? Just like a this.data;? Secondly, how am I then suppose to create the inheritances? I simply manually put the corresponding functions in the prototype? Something like:

var spiralBuilder = builder.create( dataset );
spiralBuilder.prototype.displayElements = function() {
  // Code to display the elements of a spiral
};
spiralBuilder.displayElements();

If I am correct, this mean that in the script calling the builder, instead of selecting the correct constructor (which won't exist anymore), I'll have to add/modify the methods in the prototype of a single builder instance. Is it how things should be done?

Or should I try to design my code in a completly different way? If it's the case, could you give me some advices/references for this?

Community
  • 1
  • 1
Morphilos
  • 157
  • 8
  • In the svgBuilder the member xCoord suggest that this should be an instance member (unique for every instance you create) yet it'll add this on the prototype so it's shared. If this member is primitive (number, string, boolean) then it's immutable and you can only change it by re assigning so it basically creates an extra worthless variable. If this member is an array then you're in trouble `aInstance.someArray.push("fromA");` bInstance.someArray will be `["fromA"]` – HMR Nov 13 '13 at 16:55
  • Some of the builders and helper functions are there so you can mix up inheritance when creating instances. This kind of defies the point of OOP because when something changes you have many points in your code that needs to be looked at when your Object changes. If you understand the basics of constructor functions then using factory methods may be better than having the code that creates instances decide what the object will be. I have updated my answer to address your question about instance variables in svgBuilder. – HMR Nov 14 '13 at 02:12

1 Answers1

1

can I declare variables (data, xCoord) without initialising them in this pattern?

var svgBuilder = {
  //removed data here as it's only going to shadowed
  // on creation, defaults on prototype can be done
  // if value is immutable and it's usually not shadowed later
  create: function( dataset, xcoord ) {
    var svgBuilder= Object.create(this);
    svgBuilder.data = dataset;//instance variable
    svgBuilder.xcoord = xcoord;//instance variable
    return svgBuilder;
  },
  startDisplaying: function() {
    // stuff
  },
  constructor : svgBuilder.create
};

I know I rarely do this in my samples but when creating instances or invoking functions it is generally better to pass parameter objects.

At some point in time you may change things here or there and you don't want to change many places in your code.

In the first couple of examples you're not using prototype at all. Every member is declared as this.something in the constructor function so is an instance specific member.

A builder can be used but when you're comfortable declaring your constructor functions, your prototype, mix ins and maybe statics then all you need is a helper function for inheritance and mix ins.

Introduction to prototype can be found here. It also goes into inheritance, mix ins, overriding, calling super and the this variable. A copy of the introduction follows:

Constructor function introduction

You can use a function as a constructor to create objects, if the constructor function is named Person then the object(s) created with that constructor are instances of Person.

var Person = function(name){
  this.name = name;
};
Person.prototype.walk=function(){
  this.step().step().step();
};
var bob = new Person("Bob");

Person is the constructor function as it's an object (as most anything else in JavaScript) you can give it properties as well like: Person.static="something" this is good for static members related to Person like:

 Person.HOMETOWN=22;
 var ben = new Person("Ben");
 ben.set(Person.HOMETOWN,"NY");
 ben.get(Person.HOMETOWN);//generic get function what do you thing it'll get for ben?
 ben.get(22);//maybe gets the same thing but difficult to guess

When you crate an instance using Person you have to use the new keyword:

var bob = new Person("Bob");console.log(bob.name);//=Bob
var ben = new Person("Ben");console.log(bob.name);//=Ben

The property/member name is instance specific, it's different for bob and ben

The member walk is shared for all instances bob and ben are instances of Person so they share the walk member (bob.walk===ben.walk).

bob.walk();ben.walk();

Because walk() could not be found on bob direcly JavaScript will look for it in the Person.prototype as this is the constructor of bob. If it can't be found there it'll look on Function.prototype because Person's constructor is Function. Function's constructor is Object so the last thing it will look is on Object.prototype. This is called the prototype chain.

Even though bob, ben and all other created Person instances share walk the function will behave differently per instance because in the walk function it uses this. The value of this will be the invoking object; for now let's say it's the current instance so for bob.walk() "this" will be bob. (more on "this" and the invoking object later).

If ben was waiting for a red light and and bob was at a green light; then you'll invoke walk() on both ben and bob obviously something different would happen to ben and bob.

Shadowing members happens when we do something like ben.walk=22, even though bob and ben share walk the assignment of 22 to ben.walk will not affect bob.walk. This is because that statement will create a member called walk on ben directly and assign it a value of 22. There will be 2 different walk members: ben.walk and Person.prototype.walk.

When asking for bob.walk you'll get the Person.prototype.walk function because walk could not be found on bob. Asking for ben.walk however will get you the value 22 because the member walk has been created on ben and since JavaScript found walk on ben it will not look in the Person.prototype.

So assignment of a member would cause JavaScript to not look it up in the prototype chain and assign value to that. Instead it would assign the value to an already existing member of the object instance or create it and then assign he value to it.

The next part (More about prototype) will explain this with sample code and demonstrate how to inherit.

Community
  • 1
  • 1
HMR
  • 37,593
  • 24
  • 91
  • 160