Skip to content Skip to sidebar Skip to footer

Understanding Better Javascript Oop Architecture

As i read through some examples of Angularjs' UI add-on, i've stumbled over some code that showed me that my knowdledge of Javascript is quite improvable: The following is a class

Solution 1:

Consider these 2 cases:

Dialog.prototype.open = function...

Dialog.open = function....

First case - every object created by calling new Dialog() will have this open function

Second case has nothing to do with dialog objects, consider it as static function.

EDIT

found a great answer here : javascript-class-method-vs-class-prototype-method

Solution 2:

function open will be shared by all objects create using new Dialog().. and handleLocationChange will be different for different objects.

Solution 3:

I think handleLocationChange is called from event triggering object that registers listeners but doesn't register the this context so when it's triggered you can't use this as it refers to handleLocationChange. To overcome this they have chosen to set a closure reference to this (=the self variable) and call other instance functions using self. Basically it's storing a value known at creation but not known when handleLocationChange is executing.

Here is some code showing the problem:

var eventSystem={
  events:{},
  add:function(eventname,fnCallback){
     if(!this.events[eventname]){
      this.events[eventname]=[];
     }
     this.events[eventname].push(fnCallback);
  },
  trigger:function(eventname){
    if(!this.events[eventname]){
      return;
    }
    var i=0;
    for(i=0;i<this.events[eventname].length;i++){
      this.events[eventname][i]();
    }
  }
};

var person=function(name){
  this.name=name;
};
person.prototype.sayName=function(){
  console.log("this is now:",this.toString());
    // logs this is now: function (){ console.log("this is now:...// so this is now the sayName function not the person instanceconsole.log(this.name);//undefined: sayName doesn't have a name property
}
var jon=newperson("jon");
eventSystem.add("sayname",jon.sayName);//add event and listener function
eventSystem.trigger("sayname");//trigger the event

Here is how it's solved setting a closure reference

var eventSystem={
  events:{},
  add:function(eventname,fnCallback){
     if(!this.events[eventname]){
      this.events[eventname]=[];
     }
     this.events[eventname].push(fnCallback);
  },
  trigger:function(eventname){
    if(!this.events[eventname]){
      return;
    }
    var i=0;
    for(i=0;i<this.events[eventname].length;i++){
      this.events[eventname][i]();
    }
  }
};

var person=function(name){
  var self=this;// set closure ref to thisthis.name=name;
  this.sayName=function(){
    console.log(self.name);//use closure ref to get this// logs jon
  }
};
var jon=new person("jon");
eventSystem.add("sayname",jon.sayName);//add event and listener function
eventSystem.trigger("sayname");//trigger the event

Here is a fix to the event system to take care of the this context:

var eventSystem={
  events:{},
  add:function(eventname,fnCallback,thisRef){
     if(!this.events[eventname]){
      this.events[eventname]=[];
     }
     this.events[eventname].push({
       "callback":fnCallback,//store the event handler"thisRef":thisRef//store the this context
     });
  },
  trigger:function(eventname){
    if(!this.events[eventname]){
      return;
    }
    var i=0;
    for(i=0;i<this.events[eventname].length;i++){
      this.events[eventname][i].callback.call(
        this.events[eventname][i].thisRef);
    }
  }
};

var person=function(name){
  this.name=name;
};
person.prototype.sayName=function(){
  console.log("this is now:",this);//referring to person instance// with the name jon
  console.log(this.name);//logs jon
  console.log(this instanceof person);//true
}


var jon=new person("jon");
eventSystem.add("sayname",jon.sayName,jon);//add extra parameter for this ref
eventSystem.trigger("sayname");//trigger the event

The pattern used above is not an event system (think it's pulisher subscriber) as an event usually get triggered on or invoked from an object (button, input, dialog) but in case of a more event system like implementation it would be easy to get the correct this context since you trigger the event on or from an instance (like myButton or myDialog).

See following code for event system like implementation:

var eventSystem={
  add:function(eventname,fnCallback){
     if(!this.events[eventname]){
      this.events[eventname]=[];
     }
     this.events[eventname].push(fnCallback);
  },
  //change in trigger as it's passing the event object nowtrigger:function(event){
    if(!this.events[event.type]){
      return;
    }
    var i=0;
    for(i=0;i<this.events[event.type].length;i++){
      this.events[event.type][i](event);
    }
  },
  initES:function(){//set the instance variables neededthis.events=this.events||{};
  }
};
functionaddProtos(o,protos){
  for(item in protos){
    o.prototype[item]=protos[item];
  }
}
var person=function(name){
  this.name=name;
  this.initES();//needed to initialeze eventsystem
};
// make person capable of storing event handlers// and triggering themaddProtos(person,eventSystem);
person.prototype.askQuestion=function(){
  //asking a question will trigger an "answer" eventthis.trigger({type:"answer",target:this});
}
// handler for when jon will fire an answer eventfunctionanswerHandler(event){
  console.log("answer from:",event.target);
  console.log("name of the person:",event.target.name);
}

var jon=newperson("jon");
jon.add("answer",answerHandler);//add event listener
jon.askQuestion();//triggers the answer event from within jon
jon.trigger({type:"answer",target:jon});//trigger the event externally

Not sure why Angular choose to "break" prototype by using closures as the examples show there are other alternatives. Maybe someone can explain that who is more familiar with Angular.

Post a Comment for "Understanding Better Javascript Oop Architecture"