Monday, June 17, 2013

Understanding "this" and its scope in javascript


This in javascript code is a confusing concept, and needs few rules to clarify it:
1. only use this in function definition, this does not work in object initialization
var obj = {
    a: 5,
    b: this.a + 1 
}
alert(obj.b);//b is undefined
2. even if a method is defined as a property of an object, when the method is called, the this may point to a different object, this different from C++/C# or java.
function test(){
var a ={
 answer : "a",
 func : function(){
  alert(this ==a);
 }
};
a.func();  //this inside func is a
var b = a.func;
b.call(b); //this inside func is b
}

3. Within the function body, if the function is invoked as myfunc();, then the global window object is used as this.
var a = {a:"parent"};
a.func =function(){
alert(this);
}
a.func(); //this is a inside func
var b = a.func;
b(); //this is window inside func, even if it refers a property function of object a

4. if the method is invoked with a dot as myobj.mymMethod();, then the myobj is used as this, except for constructor invocation described in the next two item (item 5 and 6)

5. when function is invoked as constructor with "new", such as myfunct() or new myobject.myfunc(), then this is the new object created for running the function. Basically, it first create a new object and set the new object as this for invoking the function. It also explains the difference between two ways to call a method:
funcA();
with
new funcA() or new objA.funcA();
as the this points to different objects in the two cases.

6. if the method or function is invoked as myMethod.call(yourObj, ...) or myObject.myMethod.call(yourObj) or myMethod.apply(yourobj) or myObject.myMethod.app(yourObj), then the yourObj is used as this, even if myObject is used to invoke the method.

7. Note that although inner function can access all variables defined in the parent function or object, it may not access the variables defined as this.parentvariable, as "this" for inner function may be different from "this" in the parent function.

8. It is obvious to see the difference on html element between
onclick=externaljsfunction;
and
onclick="externaljsfunction();"

when onclick is called, its "this" is the current html element, so in first case, externaljsfunction has the html element as its this. In second case, when externaljsfunction is called, this is set to window, unless change it to onclick="externaljsfunction.call(this)";

9. what is use of the below code
self = this;
The only purpose of this code is keeping track the this object so as to use it later by inner function. As when inner function or callback method is called, this object may be set to any value, the only way to get the parent function's this is assigning it to a variable in parent function, and then get it back using closure

var user = {
    tournament:"The Masters",
    data      :[
         {name:"T. Woods", age:37},
         {name:"P. Mickelson", age:43}
    ],

    clickHandler:function (event) {
    // set "this" to theUserObj variable, so we can use it later
    var self = this;
    this.data.forEach (function (person) {
// this now points to window object, but we can use theUserObj.tournament
console.log (person.name + " is playing at " + self.tournament);
      });
    }
};
user.clickHandler();

10.  bind this with setTimeout
setTimeout can be used to wrap synchronous call to asynchronous call. By default, when a method is called through setTimeout(), the "this" keyword will be set to the window object. In order to keep the original this object when setTimeout is called, you can explicitly bind the original this to the callback function
var a={
name:"myname",
mycall:function(){
console.log(this.name);
},
myasyncCall:function(){
setTimeout(this.mycall.bind(this), 0);
},
myasyncCallWrong:function(){
setTimeout(this.mycall, 0);
},
};
a.mycall();
a.myasyncCallWrong();  //this is window when mycall is invoked
a.myasyncCall(); //this is object a

11.sample code to use constructor parameter
function Person(gender) {
  if (arguments.length == 0) return; // do nothing if no paras for subclass constructor
  this.gender = gender;
}

function Student(gender) {
  Person.apply(this, arguments); //apply paras to parent constructor
}
Student.prototype = new Person(); // make Student inherit from a Person object
Student.prototype.constructor = Student; // fix constructor property

var foo = new Student('male');
foo.gender;             // "male"
foo instanceof Student; // true
foo instanceof Person;  // true

12. Similarly rule also applies to the variable, if it is referred as myObject.myVariable, then myVariable is searched from myObject's properties. if it is referred as myVariable directly, then it will search the global object's properties (i.e the window object)

13. Difference between method declared as arrow expression
When method is defined using arrow expression, this is hardcoded to bind to the this when the method is created, and ignores the this set to it at runtime. 
class myComponent {
  onTempTest2() {
    const obj = {
      name: 'Jonathan',
      func(){
        console.log(this);
      },
      myfunc : () => {
        console.log(this); //this is hard coded to this instance when the method is defined
      }
    };
    obj.func(); //inside func method invocation, this is obj
    obj.myfunc(); //inside myfunc method invocation, this is instance of myComponent

    const t = obj.func;
    const t2 = obj.myfunc;

    t(); // inside func invocation, this is undefined
    t2(); // inside myfunc invocation, this is instance of myComponent


    t.call(window); //this is window object
    t2.call(window); //this is instance of myComponent
    
//current this myComponent instance
t.call(this); // this is instance of myComponent, referred from parameter
    t2.call(this); //this is instance of myComponent referred from arrow function context

setTimeout(() => { t(); //inside func invocation, this is undefined }, 100); setTimeout(() => { t2(); //inside myfunc invocation, this is myComponent }, 100);
}

No comments:

Post a Comment