Ext JS - Learning Center

Tutorial:What is that Scope all about2

From Learn About the Ext JavaScript Library

Jump to: navigation, search
Summary: A few more tidbits on scope.
Author: Michael LeComte
Published: April 29, 2008
Ext Version: 2.0+
Languages: en.png English cn.png Chinese it.png Italian

Contents

Introduction

Just as with the earlier tutorial , firebug will be very handy to test out the concepts shown here.

Nested Functions

When you deal with nested functions there is a somewhat unintuitive scope change to be aware of. You can drop the code below into firebug and monitor the results.

var testvar = 'window property';
var o1 = {testvar:'1', fun:function(){alert('o1: '+this.testvar+'<<');}};
var o2 = {testvar:'2', fun:function(){alert('o2: '+this.testvar);}};
o1.fun();//'1'
o2.fun();//'2'
o1.fun.call(o2);//'2'

The first tutorial got us that far.

var testvar = 'window property';
var o3 = {
   testvar:'3',
   testvar2:'3**',
   fun:function(){
      alert('o3: '+this.testvar);//'obj3'
      var inner = function(){
         alert('o3-inner: '+this.testvar);//'window property'
         alert('o3-inner: '+this.testvar2);//undefined
      };
      inner();
   }
};
o3.fun();

Here we add another function, pretty much the same as before except note the inner function. Notice how the inner function is running in a different scope than the outer function. Ext allows you to specify the scope when you call functions to help with scope issue in called functions.

Variable Declaration

Always initialize variables with 'var' otherwise you create another global variable. For example, in the following code there is a variable wrapped inside a function which you may only intend to use only locally but it actually overwrites the global version of it. Using firebug you can examine all global variables in the 'DOM' tab by inspecting 'window'. After you click on 'window' you see all of the global variables. If you see a 'k' or 'x' or whatever then you'll know that you forgot to properly scope a variable. As an example:

var i = 4;
var j = 5;
var k = 7;
var fn = function(){
   var i = 6;
   k = 8;//notice no var in front so this reassigns global k to 8!
   alert(i);//6
   alert(j);//5
   alert(k+'-1');//8-1
   x = 1;//we just created or overwrote the global variable x
};
fn();
alert(k+'-2');//8-2 (notice this is not 7-2)

Pretty much the same as earlier. Notice that k inside the function does not have a var in front of it, so we actually re-assign the value of the global variable instead of declare a local variable. Also notice that the alert for i finds the locally defined version first and reports that value, but for j there is no locally assigned version so it works up the scope chain until it finds the global version.


Not block-structured

Ref. Variables that are created inside blocks (such as for loops and if statements) persist until the end of the function. For example, if you're used to a block-structured language, you might expect variable in this example to display the value "123".

function foo() {
     var i=123;
     for (var i=0; i<3; i++) {
       ...
     }
     alert(i);
   }

In Javascript, the value displayed is "3". That's because "i" is local to the function, not local to the "block", so the "i" used inside the for is the same as the one declared outside the for loop. Note that Javascript doesn't complain if you "var" the same variable more than once within the same scope.

Specifying the Scope in Ext

As alluded to earlier, Ext offers a few ways to specify the scope when you call functions to help with scope issue. Portions of this come from dj's post.

You can think of this as a special (hidden) parameter to each and every function. JavaScript sets this whenever you call a function. It does that by a very simple rule: If the function is called as a direct member of an object, then this object is set as this. If the function is not a member of an object, then this is set to the global object (i.e. the window object in the browser). This is apparent given our example above with the inner function.

A function, that is a variable of another function is no direct member of any object so it will get the window object as the this parameter (see Inner function example above). Here is an example, that one can follow by pasting in the Firebug console:

var obj = {
    // for better console.log(this) output
    toString:function(){ return 'in scope of obj';}, 
    func: function(){
        // this function is a direct member of "object"
        console.log(this); 
        var innerFunc = function(){
            // this function is not a direct member of an object, it
            // is a variable in another function
            console.log(this); 
        };
        innerFunc();
    }
};
obj.func(); 
// outputs "in scope of obj"
// outputs "Window something"

You can alter this default behavior - you can explicitly set the this parameter for a function call. But you need another syntax for doing this. Alter the last line "obj.func();" to:

obj.func.call(window);
// outputs "Window something"
// outputs "Window something"

So, as we saw in the prior examples, call is actually calling another function. call is a built in method of obj.func. (As you know, in JavaScript functions are objects.).

By explicitly setting the scope of this we can alter the example to fix innerFunc that has the "wrong" this parameter:

var obj = {
    // for better console.log(this) output
    toString:function(){ return 'in scope of obj';}, 
    func: function(){
        // this function is a direct member of "object"
        console.log(this); 
        var innerFunc = function(){
            // this function is not a direct member of an object,
            // it is a variable in another function
            console.log(this); 
        };
        innerFunc.call(this);
    }
};
obj.func(); 
// outputs "in scope of obj"
// outputs "in scope of obj"

Ext's scope config

So as we have seen, functions do not have a scope. "this" will be the browser window object for inner functions (like event handlers, etc) unless we find a way to specify the pointer for this. *As alluded to previously, Ext has some helpers (more here) that we could use. Several classes in Ext have a scope configuration that you can specify to help with binding the this pointer. For example, see the API for Ajax.request.

Ext.Ajax.request({
    scope:this,
    //inside populateGrid 'this' will be the scope specified in the line above
    success: this.populateGrid
    failure: function(){...}.createDelegate(that),
    ...
});

So if you use "scope: whatever" (usually this), all inner functions from that point on will use that scope. If for some reason you didn't want the failure callback to operate in the this scope, then you might use createDelegate in such a situation so that the scope didn't get applied to all of the inner functions.

Ext's createDelegate

createDelegate is another way to bind a function with a this pointer. Whenever you call createDelegate, it is ensured that the this parameter will be the one that you provided as parameter to createDelegate. An example:

var obj = {
    toString:function(){ return 'in scope of obj';},
    // for better console.log(this) output
    func: function(){
        // this function is a direct member of "object"
        console.log(this); 
        var innerFunc = function(){
            // this function is not a direct member of an object,
            // it is a variable in another function
            console.log(this); 
        };
        // here we overwrite the function with the delegate
        innerFunc = innerFunc.createDelegate(this);
        // now you can use the function as one normally would think
        innerFunc(); 
    }
};
obj.func(); 
// outputs "in scope of obj"
// outputs "in scope of obj"

This example was fairly basic. In the real world, one can easily get lost, but it always follows the rules described above.

createDelegate is also useful when you want to set a handler to a named function with a series of arguments:

var menu = new Ext.menu.Menu(
{
   items:
   [
      text: 'Item 1',
      //here we append 3 arguments to original arguments
      handler: myHandler.createDelegate(someScope, ['Item1', 'Is', 'Clicked'], true)
   ]
}
);
 
function myHandler(e, text1, text2, text3)
{
   //e, was originally available as a parameter
   //we appended 3 more arguments to the function
   //It also lets me set the scope easier.
}

Working with Functions

When you pass a function either as a parameter, or as a property of an object, you pass a reference to a function. There is no way for the receiver of that parameter, or object to infer which object (if any) the caller had referring to it. Consider this:

// display an integer
function showInt(int) {
    console.log(int);
}
var myObject = {
    value: 1
};
showInt(myObject.value);

In the above snippet the showInt function can not infer what object was referring to (pointing to) that integer in the caller's context. It could even be several references:

var myObject = {
    value: 1
};
var myOtherObject = {
    // This property refers to (points to) the same Number object
    anotherValue: myObject.value 
};
showInt(myObject.value);
showInt(myOtherObject.anotherValue);

Inside showInt, all you get is a reference to a Number object. The same principle applies when your function is passed a function as a parameter:

function callAFunction(fn) {
    //inside callAFunction, there is no way of knowing
    //what to use as the "this" for executing this function.
    //all you get is a reference to a Function object
    fn.call(<the "this" object to use. What should we use?>);
}
 
var myObject = {
    fn: function(){ alert(1); }
};
var myOtherObject = {
    fn: myObject.fn // This property refers to (points to) the same Function object
};
 
callAFunction(myObject.fn);


Also note that in the prior example, the objects were storing a primitive value, so when the value is updated the value is not updated. If you store an object, the other object maintains a reference to the value:

function showInt(i) {
    console.log(i);
}
 
var myObject = {
    value: 1,
    obj: {val: 2}
};
var myOtherObject = {
    otherValue: myObject.value,
    otherObj:   myObject.obj
};
showInt(myObject.value);//1
showInt(myOtherObject.otherValue);//1
 
myObject.value = 3;
myObject.obj.val = 4;
showInt(myOtherObject.otherValue);//1 (not updated)
showInt(myOtherObject.otherObj);//obj val 4 (updated)

Further reading:

  • This page was last modified on 30 July 2009, at 08:38.
  • This page has been accessed 24,205 times.