javascriptajaxthis

Please help me understand this JavaScript code from "Ajax in Action"


I saw this code in Ajax in Action book and there are two things I'm not able to understand (Keep in mind I just started web programming and I'm still trying to understand how JavaScript works).

  1. On line 37 or in function loadXMLDoc, why did the author declared a local variable var loader=this; and then used it in the call net.ContentLoader.onReadyState.call(loader); instead of just using net.ContentLoader.onReadyState.call(this);

  2. Why did the author used net.ContentLoader.onReadyState.call(loader);, instead of this.onReadyState();

/*
url-loading object and a request queue built on top of it
*/

/* namespacing object */
var net=new Object();

net.READY_STATE_UNINITIALIZED=0;
net.READY_STATE_LOADING=1;
net.READY_STATE_LOADED=2;
net.READY_STATE_INTERACTIVE=3;
net.READY_STATE_COMPLETE=4;


/*--- content loader object for cross-browser requests ---*/
net.ContentLoader=function(url,onload,onerror,method,params,contentType){
  this.req=null;
  this.onload=onload;
  this.onerror=(onerror) ? onerror : this.defaultError;
  this.loadXMLDoc(url,method,params,contentType);
}

net.ContentLoader.prototype.loadXMLDoc=function(url,method,params,contentType){
  if (!method){
    method="GET";
  }
  if (!contentType && method=="POST"){
    contentType='application/x-www-form-urlencoded';
  }
  if (window.XMLHttpRequest){
    this.req=new XMLHttpRequest();
  } else if (window.ActiveXObject){
    this.req=new ActiveXObject("Microsoft.XMLHTTP");
  }
  if (this.req){
    try{
      var loader=this;
      this.req.onreadystatechange=function(){
        net.ContentLoader.onReadyState.call(loader);
      }
      this.req.open(method,url,true);
      if (contentType){
        this.req.setRequestHeader('Content-Type', contentType);
      }
      this.req.send(params);
    }catch (err){
      this.onerror.call(this);
    }
  }
}


net.ContentLoader.onReadyState=function(){
  var req=this.req;
  var ready=req.readyState;
  var httpStatus=req.status;
  if (ready==net.READY_STATE_COMPLETE){
    if (httpStatus==200 || httpStatus==0){
      this.onload.call(this);
    }else{
      this.onerror.call(this);
    }
  }
}

net.ContentLoader.prototype.defaultError=function(){
  alert("error fetching data!"
    +"\n\nreadyState:"+this.req.readyState
    +"\nstatus: "+this.req.status
    +"\nheaders: "+this.req.getAllResponseHeaders());
}

Solution

  • A try/catch statement in ECMA-/Javascript creates a new Context. Technically, this is similar to an eval statement and therefore an eval Context.

    The current Scope chain is extended by that newly created "eval Context" and therefore, the Context variable this, would point to a wrong context when just invoked by this.onReadyState();.

    By calling net.ContentLoader.onReadyState.call(loader); the author explicitly calls the method onReadyState with the context of the loaded object (and that is what this within the callee is referencing then). A callee is a function (-context...) with was called by a caller (-context).


    Long story short, ECMAscripts .call() and .apply() methods allow you to set a specific Context for a function when invoked. This is necessary here, because try/catch creates a new Context and the value of this within the called method would be wrong.


    While the above statement is true, it's not responsible here. It's not the Context from try / catch which is the problem , it's furthermore the Context by the created anonymous function

    this.req.onreadystatechange=function(){
        net.ContentLoader.onReadyState.call(loader);
    }
    

    Using this within that anonymous method would "again" reference a different Context. That is why the author cached the value from this in loader and invokes the method with that cached Context.