Avoid Maximum call stack size exceeded in JavaScript

Overview

  • Error Maximum call stack size exceeded  is raised when calling a recursive function in a  loop
  •  This is expected – as every Javascript Engine has a maximum Call Stack Range

Failing Javascript Code

function errorFunc(rc) {
    var runCount = rc;
  var maxCount = 100000;
  if ( (rc % 5000) === 0 ) {
    logMessage("errorFunc():: Recursion Count:  " + runCount );
  } 
  runCount++;
  if (runCount >= maxCount ) {
      logMessage("errorFunc() reached MAX recursion Count :  " + runCount );
    return;
  }
  try {
      errorFunc(runCount);
      } catch (e ) {
        if ( e instanceof RangeError) {
            logMessage("errorFunc():: " + e +  ": " + runCount );
        }
      }
};

  • Output

recursionOK

Fixed Code be using setTimeout() function

  • Workraound : use setTimeout() function
  • setTimeout() function always creates a Top Level Function
function     workingFunc(rc) {
    // logMessage("workingFunc()");
  var runCount = rc;
  var maxCount = 100000;
  if ( (rc % 20000) === 0 ) {
    logMessage("workingFunc():: Recursion Count:  " + runCount );
  } 
  runCount++;
  if (runCount >= maxCount ) {
      logMessage("workingFunc() reached MAX recursion Count :  " + runCount );
    return;
  }
 
  try {
      setTimeout( workingFunc, 0, runCount);
      } catch (e ) {
        if ( e instanceof RangeError) {
            logMessage("workingFunc():: " + e +  ": " + runCount );
        }
      }
  };
  • Output of fixed CoderecursionOK
  • Note: The setTimeout() Workaorund is very slow

References

 

Calling JavaScript function for a top Level Object by using a String

JavaScript Code

var myPanel = null;              /* Our object */
function testFunctions(func) {
    logStatus("Function " + func + " Active !");
    myPanel = createPanel();
    var info = "Calling ANONYMOUS Function" ;
    myPanel.testfunc(info);
    info = "Calling NAMED     Function" ;
    myPanel.testfunc2(info);
    info = "Calling ANONYMOUS Function VIA String" ;
    myPanel.invokeFuncByString(myPanel, "testfunc",info);
    info = "Calling NAMED     Function VIA String" ;
    myPanel.invokeFuncByString(myPanel, "testfunc2",info);
}

function createPanel(vSingleClickInProgess, vClickTimeout) {
    var panel = Object.create(Object.prototype, {
        objectName: {value: "panel", writable: true}      /* just provide a name for our object */
    });
    panel.testfunc  = function (info) {
        logStatus ("__ " + info);
    };
    panel.testfunc2  = function (info) {
        logStatus ("__ " + info);
    };
    panel.invokeFuncByString = function(obj,funcName,info) {
        var fnString= funcName;
        var fn = obj[fnString];
                // is object a function?
        if (typeof fn === "function")
            fn(info + " - ObjectName: " +  obj.objectName  + " - functionName: " + funcName);
        else {
            logErrorT(logHeader, "__ fn IS NOT A Function - : fnString " + fnString + " type: " + typeof fn )
        }
    }
    return panel;
};

Output

17:05:12:740 STATUS::__ Calling ANONYMOUS Function
17:05:12:740 STATUS::__ Calling NAMED     Function
17:05:12:741 STATUS::__ Calling ANONYMOUS Function VIA String - ObjectName: panel - functionName: testfunc
17:05:12:742 STATUS::__ Calling NAMED     Function VIA String - ObjectName: panel - functionName: testfunc2

References

JavaScript: Prototypal Object Inheritance

Overview and Key Facts

  • According to Douglas Crockford and  Eric Elliot Prototypal Inheritance should be preferred over Classical  Inheritance
  • Objects in Javascript inherit their Properties/Methods from what is called a prototype instead of a class definition
  • Prototypes are standard objects themselves and builds up a Prototype Chain      
  • The Prototype Chain top level object is Object.prototype and delegates methods like toString() to each JavaScriot Object with a working Prototype Chain
  • Same prototype means same object instance
  • Every JavaScript Object should get its behavior from functions in its prototype
  • ECMAScript 5 introduced a new method: Object.create() which simplifies creating a Prototype Chain without Douglas Crockfords “WorkAround” simulating Object.create()

 Inherit from a Object Literal using Object.create

  • Object.create(Mammal, …  uses the Mammel Object  created from am  Object Literal  as its Prototype
  • MammelObj inherits Properties type and objectName from Mammel
  • MammelObj inherits Function getDetails from Mammel
  • MammelObj adds _objectName Data Descriptor
  • MammelObj adds an Accessor Descriptor objectName which works on _objectName property
JavaScript Code:

var Mammal = {
    type: 'Mammal',
    objectName : 'Mammal BASE Object',
    getDetails: function () {
        return("Type: " + this.type + " - ObjectName:  "  + this.objectName);
    }

};

function runObjectTest9() {
    logMessage('--- Prototypal Inheritance via Object.create [ for details check JavaScript console ] ---');
    logMessage("Global Mammal Object created via String Literals:: type: " + Mammal.type + " - getDetails(): " + Mammal.getDetails());
    var MammalObj = Object.create(Mammal, {
        _objectName : { value: 'INITIAL MammelObj', writable: true},
        objectName: {
            configurable: true,
            get: function getObjectName () {
                logMessage('[MammalObj objectName get]:: ' + this._objectName );
                return this._objectName;
            },
            set: function setObjectName (value) {
                oldVal = this._objectName;
                this._objectName = value;
                logMessage("[MammalObj objectName set]:: NEW Value: " + this._objectName + " - Old Value: " + oldVal );
            }
         }
    });

    logMessage('[MammalObj.objectName]:: ' + MammalObj.objectName);
    MammalObj.objectName = "MammalObj";
    MammalObj.isWalking = false;
    logMessage("[MammalObj.getDetails()]:: " + MammalObj.getDetails());
    logMessage("[MammalObj.type]:: " + MammalObj.type);
    checkProperties(MammalObj);
    logMessage('------------------------------------------------------------------------');
}

Output: 
--- Prototypal Inheritance via Object.create [ for details check JavaScript console ] ---
Global Mammal Object created via String Literals:: type: Mammal - getDetails(): Type: Mammal - ObjectName:  Mammal BASE Object
[MammalObj objectName get]:: INITIAL MammelObj
[MammalObj.objectName]:: INITIAL MammelObj
[MammalObj objectName set]:: NEW Value: MammalObj - Old Value: INITIAL MammelObj
[MammalObj objectName get]:: MammalObj
[MammalObj.getDetails()]:: Type: Mammal - ObjectName:  MammalObj
[MammalObj.type]:: Mammal
[MammalObj objectName get]:: MammalObj
  • Output and Code for checkProperties(MammalObj) can be found here 

What happens with undefined properties and Methods  ?

  • Note we are uisng the above Objects Mammal and MammalObj
  • Referencing a NON existent function raises an Error:  TypError xxx.xxx  is not a function
  • Referencing a a NON existent property returns undefined
JavaScript Code:

logMessage('--- Testing TypeError: Object.function  is not a function ---');
try {
    logMessage("MammalObj.getAge():: " + MammalObj.getAge());
}   catch (e) {
    logMessage("FATAL ERROR:: Catching Exception running MammalObj.getAge() :: " + e);
}
logMessage("MammalObj.age :: " + MammalObj.age);
checkProperties(MammalObj);
logMessage('------------------------------------------------------------------------');

Output: 
--- Testing TypeError: Object.function  is not a function ---
FATAL ERROR:: Catching Exception running MammalObj.getAge() :: TypeError: MammalObj.getAge is not a function
MammalObj.age :: undefined
[MammalObj objectName get]:: MammalObj

 

Overwrite Object Methods

  • Note we still use  the object definition from our first sample for Mammal and MammalObj
JavaScript Code:

logMessage('--- Testing Overwrite Methodes ---');
var CatObj =  Object.create(MammalObj);
CatObj.legs =4;
CatObj.canWalk = function ()  {
    return("I've " + this.legs + " legs and can walk" );
};
CatObj.getDetails = function ()  {
    var myType = this.type;
    var myLegs = this.legs;
    return("[CatObj] - Hi, I'm a " + myType + " and I've " + myLegs + " legs and can walk" );
};
logMessage("catObj.canWalk():: " + CatObj.canWalk());
logMessage("catObj.getDetails():: " + CatObj.getDetails());
checkProperties(CatObj);
logMessage('------------------------------------------------------------------------');

Output: 
--- Testing Overwrite Methodes ---
catObj.canWalk():: I've 4 legs and can walk
catObj.getDetails():: [CatObj] - Hi, I'm a Mammal and I've 4 legs and can walk
[MammalObj objectName get]:: MammalObj


 Stripped Output from checkProperties(CatObj);

--- Detailed ProtoType Function walking down the Prototype Chain - Level:  1---
    ...
--- Detailed ProtoType Function walking down the Prototype Chain - Level:  2---
    ...
--- Detailed ProtoType Function walking down the Prototype Chain - Level:  3---
    ...
      
       [Mammal BASE Object, level:3][Found a FUNCTION] - Function Name: getDetails - Type: function - getPrototypeOf: function () {} - Constructor: function Function() { [native code] }
       [Mammal BASE Object, level:3] - Data Descriptor: Configurable: true - Enumerable: true - Writable: true - Value: function () {
        return("Type: " + this.type + " - ObjectName:  "  + this.objectName);
    }
   
   [CatObj, level:1][Found a FUNCTION] - Function Name: getDetails - Type: function - getPrototypeOf: function () {} - Constructor: function Function() { [native code] }
   [CatObj, level:1] - Data Descriptor: Configurable: true - Enumerable: true - Writable: true - Value: function ()  {
        var myType = this.type;
        var myLegs = this.legs;
        return("[CatObj] - Hi, I'm a " + myType + " and I've " + myLegs + " legs and can walk" );
    } 
  • There is a getDetails methode implemented by CatObj and Mammal BASE Object
  • The first method from catObj [ CatObj, level 1 ] is picked up when walking down the Prototype Chain

Modifying an underlying Object down in the Prototype Chain

  • Let’s create 2 cats named Fini and Felicitas based on CatObj
  • Change the type to Fish and modify their number of legs to 2
JavaScript Code : 

var CatObj =  Object.create(MammalObj);
CatObj.legs =4;
CatObj.objectName = 'CatObj';
CatObj.type = 'CAT';
CatObj.canWalk = function ()  {
    return("I've " + this.legs + " legs and can walk" );
};
CatObj.getDetails = function ()  {
    return("[CatObj] - Hi, I'm a " + this.type + " and I've " +  this.legs + " legs and can walk" );
};
logMessage("CatObj.getDetails():: " + CatObj.getDetails());
checkProperties(CatObj);
logMessage('------------------------------------------------------------------------');

logMessage('--- Modifying an underlying Object down in the Prototype Chain ----');
var felicitas =  Object.create(CatObj);
felicitas.age = 11;
felicitas.name = 'Felicitas';
logMessage("felicitas.getDetails():: " + felicitas.getDetails());

var fini = Object.create(CatObj);
fini.age = 9;
fini.name = 'Fini';
logMessage("fini.getDetails():: " + fini.getDetails());

logMessage("  ------ Lets modify all Cats to become a FISH type with 2 Legs ! ----- ")
CatObj.type = 'FISH';
CatObj.legs = 2;

logMessage("felicitas.getDetails() after morphing  Cats to a Fish :: " + felicitas.getDetails());
logMessage("fini.getDetails() after morphing Cats to a Fish:: " + fini.getDetails());
logMessage('------------------------------------------------------------------------');
 
Output : 
--- Modifying an underlying Object down in the Prototype Chain ----
felicitas.getDetails():: [CatObj] - Hi, I'm a CAT and I've 4 legs and can walk
fini.getDetails():: [CatObj] - Hi, I'm a CAT and I've 4 legs and can walk
  ------ Lets modify all Cats to become a FISH type with 2 Legs ! ----- 
felicitas.getDetails() after morphing  Cats to a Fish :: [CatObj] - Hi, I'm a FISH and I've 2 legs and can walk
fini.getDetails() after morphing Cats to a Fish:: [CatObj] - Hi, I'm a FISH and I've 2 legs and can walk

 

Directly Calling Methods from a Prototype Object

  • Calling Methods from a Prototype Object may return undefined
  • You should use call() or apply() to provide the correct THIS context [ felicitat Object ]
  • felicitas.getDetailsFromCat() fails because THIS context is pointing to the CatObj Object which doesn’t have properties this.name and  this.age
JavaScript Code:

    logMessage('--- Directly Calling Methods from a Prototype Object---');
    CatObj.type = 'CAT';
    CatObj.legs = 4;
    CatObj.getDetails = function ()  {
        return("Hi, my name is " +  this.name + " and I'm " + this.age + " years old. I'm a " + this.type
        + " and I've " + this.legs + " legs and can walk" );
    };
    var felicitas =  Object.create(CatObj);
    felicitas.age = 11;
    felicitas.name = 'Felicitas';
    felicitas.fObjectName = 'felicitas';

    felicitas.getDetailsFromCat = function () {
        CatObj.getDetails();     // this context is pointing to CatObj having NO this.name and this.age property
    };
    logMessage("felicitas.getDetailsFromCat() :: " +  CatObj.getDetails() );

    felicitas.getDetailsFromCat2 = function () {
        // this context now pointing to felicitas having this.name and this.age property
        logMessage("felicitas.getDetailsFromCat2() :: " +  CatObj.getDetails.call(this) );
    };
    felicitas.getDetailsFromCat2();
    logMessage("felicitas.getDetails():: " + felicitas.getDetails());
    logMessage('------------------------------------------------------------------------');

Output:  
  --- Directly Calling Methods from a Prototype Object---
felicitas.getDetailsFromCat() :: Hi, my name is undefined and I'm undefined years old. I'm a CAT and I've 4 legs and can walk
felicitas.getDetailsFromCat2() :: Hi, my name is Felicitas and I'm 11 years old. I'm a CAT and I've 4 legs and can walk
felicitas.getDetails():: Hi, my name is Felicitas and I'm 11 years old. I'm a CAT and I've 4 legs and can walk


 

References

Object Reflection in JavaScript by Sample

Overview

  • Javascrript has an API to use Reflection similar to  JAVA
  • You can follow the Prototypal Chain to figure out the Object Properties and Functions
  • Of course you can use Chrome Dev Tools  but if you need a programatically approach continue reading
  • The code may not be prefect – any comments/improvements are appreciated
  • checkProperties(MammalObj) is used to walk down the Prototype Chain

JavaScript Code for Object inheritance

var Mammal = {
    type: 'Mammal',
    objectName : 'Mammal BASE Object',
    getDetails: function () {
        return("Type: " + this.type + " - ObjectName:  "  + this.objectName);
    }
};

function runObjectTest9() {
    logMessage('--- Prototypal Inheritance via Object.create [ for details check JavaScript console ] ---');
    logMessage("Global Mammal Object created via String Literals:: type: " + Mammal.type + " - getDetails(): " + Mammal.getDetails());
    var MammalObj = Object.create(Mammal, {
        _objectName : { value: 'INITIAL MammelObj', writable: true},
        objectName: {
            configurable: true,
            get: function getObjectName () {
                logMessage('[MammalObj objectName get]:: ' + this._objectName );
                return this._objectName;
            },
            set: function setObjectName (value) {
                oldVal = this._objectName;
                this._objectName = value;
                logMessage("[MammalObj objectName set]:: NEW Value: " + this._objectName + " - Old Value: " + oldVal );
            }
         }
    });

    logMessage('[MammalObj.objectName]:: ' + MammalObj.objectName);
    MammalObj.objectName = "MammalObj";
    MammalObj.isWalking = false;
    logMessage("[MammalObj.getDetails()]:: " + MammalObj.getDetails());
    logMessage("[MammalObj.type]:: " + MammalObj.type);
    checkProperties(MammalObj);
    logMessage('------------------------------------------------------------------------');
}
Output: 
--- Prototypal Inheritance via Object.create [ for details check JavaScript console ] ---
Global Mammal Object created via String Literals:: type: Mammal - getDetails(): Type: Mammal - ObjectName:  Mammal BASE Object
[MammalObj objectName get]:: INITIAL MammelObj
[MammalObj.objectName]:: INITIAL MammelObj
[MammalObj objectName set]:: NEW Value: MammalObj - Old Value: INITIAL MammelObj
[MammalObj objectName get]:: MammalObj
[MammalObj.getDetails()]:: Type: Mammal - ObjectName:  MammalObj
[MammalObj.type]:: Mammal
[MammalObj objectName get]:: MammalObj

 

Implementation of Reflection Code

function checkProperties(o) {
    level =1 ;
    checkPropertiesPrototype(o,level);
}
var endOfPrototypeChainReached = false;

function checkPropertiesPrototype(oProto,level) {
        // limit the Prototype Chain depth
    if ( level === 10) {
        logMessage("Prototype Chain > 10 : termination - Check for CodeBUGS !");
        return;
    }
    /*
     * Abort walking down the Property Chain action when we reach the  Object.prototype object
     * Note:  Object.getPrototypeOf returns null when we reach the TOP Level Object.prototype object
     */
    var o = oProto;
    var prefix = getPrefix(level*2) + " [" + o.objectName + ", level:" + level + "]";
    var oProtoNext = Object.getPrototypeOf(oProto); 
    if ( !oProtoNext) {
            // o.objectName is undefined for  Object.prototype  - Fix this
        prefix = getPrefix(level*2) + " [Object.prototype, level:" + level + "]";
           // logMessage(prefix + " End of protoype Chain reached: Level: " + level);
           // Mark that we have reached the END of our Prototype Chain
        endOfPrototypeChainReached = true;
        return;
       }     
    logMessage("--- Detailed ProtoType Function walking down the Prototype Chain - Level:  " + level + '---');
    Object.getOwnPropertyNames(o).forEach(function(val) 
    {
        var objRef =  o[val];
        var foundIt = "";
        var p =   Object.getPrototypeOf(objRef);
        /*
            JavaScript has 5 primitives: Number, String, Boolean ,Undefined, Null [  Anything else is considered an object ]
            Object.getPrototypeOf(objRef) returns NULL for Primitives like  Number, String, Boolean ,Undefined, Null
        */
        var foundFunc = false;
        if ( typeof(objRef) === 'function') {
            foundIt = "[Found a FUNCTION]";
            foundFunc = true;
        }
        else if ( typeof(objRef) === 'object') {
            foundIt = "[Found an OBJECT]";
        }

        /*
        Object Properties:
            Data Descriptor     : configurable, enumerable, value, writable
            Accessor descriptor : configurable, enumerable, set, get
        */
        var propRes = "Oops- check getOwnPropertyDescriptor Code";
        d = Object.getOwnPropertyDescriptor(o, val);
        if ( d ) {
            if ( !d.get  || !d.set ) {
                propRes = " - Data Descriptor: Configurable: " +  d.configurable + " - Enumerable: " + d.enumerable +
                    " - Writable: " + d.writable + " - Value: " + d.value;
            }
            else {
                propRes = " - Accessor Descriptor: Configurable " +  d.configurable + " - Enumerable " + d.enumerable +
                    " - Set: " + d.set + " - Get: " +  d.get;
            }
        }
        if (foundFunc) {
            logMessage(prefix + foundIt + " - Function Name: " + val + " - Type: " + typeof(objRef)
                + " - getPrototypeOf: " + p  + " - Constructor: " + objRef.constructor + "\n" + prefix +  propRes);
        }
        else {
            logMessage(prefix + foundIt + " - Property Name: " + val + " - Value: " + objRef + " - Type: " + typeof(objRef)
                + " - getPrototypeOf: " + p  + " - Constructor: " + objRef.constructor + "\n" + prefix +  propRes);
        }
        
        var oProto = Object.getPrototypeOf(o);
            // Dont walk down the Prototype Chain more than ONCE
        if ( !endOfPrototypeChainReached)
            checkPropertiesPrototype(oProto,level+1);
    });
   level++;
}

function getPrefix(len)
{
var eStr = '';
while( eStr.length < len )
    {
        eStr = eStr + ' ';
    }        
return eStr;
}

 

Output from Reflection Script

--- Detailed ProtoType Function walking down the Prototype Chain - Level:  1---

   [MammalObj, level:1] - Property Name: _objectName - Value: MammalObj - Type: string - getPrototypeOf:  - Constructor: function String() { [native code] }
   [MammalObj, level:1] - Data Descriptor.: Configurable: false - Enumerable: false - Writable: true - Value: MammalObj

--- Detailed ProtoType Function walking down the Prototype Chain - Level:  2---

     [Mammal BASE Object, level:2] - Property Name: type - Value: Mammal - Type: string - getPrototypeOf:  - Constructor: function String() { [native code] }
     [Mammal BASE Object, level:2] - Data Descriptor.: Configurable: true - Enumerable: true - Writable: true - Value: Mammal

     [Mammal BASE Object, level:2] - Property Name: objectName - Value: Mammal BASE Object - Type: string - getPrototypeOf:  - Constructor: function String() { [native code] }
     [Mammal BASE Object, level:2] - Data Descriptor.: Configurable: true - Enumerable: true - Writable: true - Value: Mammal BASE Object

     [Mammal BASE Object, level:2][Found a FUNCTION] - Function Name: getDetails - Type: function - getPrototypeOf: function () {} - Constructor: function Function() { [native code] }
     [Mammal BASE Object, level:2] - Data Descriptor.: Configurable: true - Enumerable: true - Writable: true - Value: function () {
                                     return("Type: " + this.type + " - ObjectName:  "  + this.objectName);  }
                                     
   [MammalObj, level:1] - Property Name: objectName - Value: MammalObj - Type: string - getPrototypeOf:  - Constructor: function String() { [native code] }
   [MammalObj, level:1] - Accessor Descriptor: Configurable true - Enumerable false - Set: function setObjectName(value) {
                oldVal = this._objectName;
                this._objectName = value;
                logMessage("[MammalObj objectName set]:: NEW Value: " + this._objectName + " - Old Value: " + oldVal );
            } - Get: function getObjectName() {
                logMessage('[MammalObj objectName get]:: ' + this._objectName );
                return this._objectName;
            }
   [MammalObj, level:1] - Property Name: isWalking - Value: false - Type: boolean - getPrototypeOf: false - Constructor: function Boolean() { [native code] }
   [MammalObj, level:1] - Data Descriptor.: Configurable: true - Enumerable: true - Writable: true - Value: false

JavaScript: Tackling THIS Object, Nested Functions/Closures by Samples

Overiew  – Some key Facts You Should Know

  • JavaScript has lexical scoping with function scope.
  • JavaScript looks like it should have block scope because it uses curly braces { },
    BUT a new scope is created only when you create a new function !
  •  JavaScript loses scope of THIS when used inside of a function that is contained inside of another function [ Nested Function, Closure ]
  • When Scope gets lost, by default, THIS will be bound to the global window object
  • The way  lexical scoping works in JavaScript works can’t be modified
  • Only the control of the context in which functions are called can be modified
  • Nested Function Definition : The nested (inner) function is private to its containing (outer) function. It also forms a closure.
  •  A closure is an expression (typically a function) that can have free variables together with an environment that binds those variables (that “closes” the expression).

This and Using String Literals for Object Creation

var john1 = 
{
    firstName: "John",
    sayHi:     function()
    {
        console.log("  Hi " + this.firstName + " !" );
        checkOBJ(this);
    }
};

function runThisTest1()
{
    console.log("--- Testing Object Literal with Dot Notation: john1.sayHi()  ---");
    john1.sayHi();
    console.log("--- Testing Object Literal with Bracket Notation:  john1['sayHi']() ---");
    john1['sayHi']();
    console.log("----------------------------------------------------");
}

Console.log 

js_Obj1

  • JavaScript methods can be called with Dot and Bracket Notation
  • The Constructor of Objects created via  Object Literals is the Object Type

This and Objects created with the NEW Operator

function User(name) 
{
    this.name = name;
    this.sayHi = function() 
    {
        console.log(" Hi I am "  + this.name);
        checkOBJ(this);
    };
}

function runThisTest3()
{
    console.log("--- New Operator:  var john = new User('John'); ---");
    var john = new User("John");
    john.sayHi();
    console.log("----------------------------------------------------");
}

js_Obj2

  • The constructor for our john object is the  User() Type
  • __proto__ points onto the Object type   [  == Prototypal inheritance ]

–> So far nothing is very special and things are straight forward

Understand the Lexical Environment – This step is needed for NESTED FUNCTIONS

var i = 99; 

var createAdders = function() 
{
    var fns = [];
    for (var i=1; i<4; i++) { 
        fns[i] = (function(n) {
            return i+n;
        });
    }
    i = 10;
    return fns;
};

function runThisTest5()
{
    console.log("--- Lexical Envrironment gives us Strange Results --- ");    
    var adders = createAdders();
    for ( var i = 1; i<4; i++  )
    {
        console.log("Result: " + adders[i](7)  + " - Function  adders[" + i + "] : " + adders[1] ); //17 ??
    }
    console.log("----------------------------------------------------");
}

Console.log

js_Obj3

  • Function createAdders() creates 3 different Adder Functions
  • All of these function references variable i – no obvious erorr
  • You may expect that these functions return 8,9 and 10 but they don’t do that
  • Instead all of these three functions returns 17 Why ?

Lexical environments

  • When a function is called, an NEW Lexical environment is created for the new scope that is entered.
  • Each new Lexical Evironment has a field called outer that points to the outer scope’s environment and is set up via [[Scope]]
  • There is always a chain of environments, starting with the currently active environment, continuing with its outer environment
  • Every chain ends with the global environment (the scope of all initially invoked functions). The field outer of the global environment is null.
  • An environment record records the identifier bindings that are created within the scope of this lexical environment
  • That is, an environment record is the storage of variables appeared in the context
  •  A lexical environment defines the association of identifiers to the values of variables and functions based upon the  lexical nesting structures of ECMAScript code.
globalEnvironment = { 
  environmentRecord: {
    // built-ins:
    Object: function,
    Array: function,
    // etc ...
    // our bindings:
    i: 99
  },
  outer: null // no parent environment
};

// environment of the "runThisTest5" function
runThisTest5 = {
  environmentRecord: {
    i :   1 [ 2,3 ] 
    adders:  Reference  to createAdders fn[]
  },
  outer: globalEnvironment

// environment of the "createAdders" function 
createAdders = {
  environmentRecord: {
    i: 10
    fn[] = [1] function (n) { return i+n; }
           [2] function (n) { return i+n; }
           [3] function (n) { return i+n; }
  },
  outer: runThisTest5
};

     Note:

  • After Function creation i = 10 gets executed and this data is saved in our LEXICAL Function Environment before returning the function array
  •  Later on the Functions “createAdders[i]” where executed and >> i << gets resolved from our LEXICAL Function Environment where i=10 !
  • This is the reason why all of our functions returns the same result
  • As we have different lexical Environments for every new function the THIS scope changes too !
  • Nested Functions are one well know sample for this behavior .

This and Nested Functions a potential problem

var catname = "Im a REAL PROBLEM";
function Cat6 ( name, color, age)
{
   this.catname = name;
   this.color = color;
   this.age = age;
   this.printInfo =  function() 
   {
      var that = this;
      nestedFunction = function() 
      {
         console.log("   Object Properties: Name:", this.catname, " - Color:", this.color, " - Age:", this.age );
         checkOBJ(this);
      }; 
      nestedFunction2 = function() 
      {
         "use strict";
        try
        {
            console.log("   Object Properties: Name:", this.catname, " - Color:", this.color, " - Age:", this.age );
        }  catch ( err )
        {
            console.log(" Error getting Object details: Error : " + err );
        }
      };
      
      nestedFunction3 = function() 
      {
         console.log("   Object Properties: Name:", that.catname, " - Color:", that.color, " - Age:", that.age );
         checkOBJ(that);
      }; 
      
      console.log('--- Call nestedFunction() - Not VALID THIS object generates a huge PROBLEM ---');
      nestedFunction();  
      console.log("----------------------------------------------------");
      console.log('--- Call nestedFunction() using a saved THIS Context armed by "use strict"; ---');
      nestedFunction2();
      console.log("----------------------------------------------------");
      console.log('--- Call nestedFunction() using a saved THIS Context ---');
      nestedFunction3(); 
    };
};

function runThisTest6()
{     
    console.log('--- THIS behavior with Nested Functions and NEW Operator  [ test6 ] ---');    
    var myCat6 = new Cat6('Fini', 'black', 7);
    myCat6.printInfo();   
    console.log("----------------------------------------------------");
}

 

Console.log

js_Obj4

  • nestedFunction()  has lost the original THIS context and is working with Window context
  • This can be quite dangerous as we may pick up wrong object properties [ like this.catname from the window object ]. All other properties become undefined.
  • nestedFunction2()  shows how we can detect this error by using “use strict”;  directive
  • nestedFunction3() shows a solution for the problem by storing  the current this object reference in the lexical Environment [ var that = this; ]. Later on this object reference is used to read object details.

THIS behavior with Nested Functions and NEW Operator using call(), apply(), bind()

  • To Fix the problem with an INVALID THIS context use call(), apply(), bind()
var name = "This SHOULD NEVER be printed !";
function Cat9 ( name, color, age)
{
   this.name = name;
   this.color = color;
   this.age = age;
   this.printInfo =  function() 
   {
      
      console.log("   Contructor Name:", this.name, " - Color:" + this.color, " - Age:" + this.age );
      nestedFunction = function() 
      {  
        // Window object alway will raise Error : caught TypeError: Converting circular structure to JSON 
        console.log("   Object Properties: Name:", this.name, " - Color:", this.color, "- Age:", this.age  );
        checkOBJ(this);
      };
      
      console.log('--- Using call() by providing this context as first parameter ---' );
      nestedFunction.call(this,  'Using call() by providing this context as first parameter' );
      
       console.log('--- Using apply() by providing this context as first parameter ---');
      nestedFunction.apply(this, []);
 
      var storeFunction = nestedFunction.bind(this);
      console.log('---  Using bind to save this context ---');
      storeFunction();
      
      console.log('---  Call nestedFunction() - SHOULD FAIL with undefined as THIS points now to WINDOW object---');
      nestedFunction();      
    };
};

function runThisTest9()
{   
    console.log('--- THIS behavior with Nested Functions and NEW Operator using call(), apply(), bind() [ test9 ] ---');    
    var myCat9 = new Cat9('Fini', 'black', 7);
    console.dir(myCat9);
    myCat9.printInfo();   
    console.log("----------------------------------------------------");
}

Console.log

js_Obj5

  • Using call(), apply() and bind() fixes the problem with a lost THIS context
  • The last test stresses again the fatal error that can happen when loosing THIS context

Real Sample: Borrow an Object Function Methode via call(), apply()

function Adder2 () 
{
    this.add = function(a,b) {  return a + b;  };
}

function runThisTest11()
{     
    var adder2 =  new Adder2;
    var res = adder2.add(2,3);
    console.log("--- Borrow an Object Function Methode via call(), apply()");
    console.log("    result of original Adder  created by NEW operator: " + res);
    
    console.log("    Adder called via call(): " + adder2.add.call( this, 2,3));
    console.log("    Adder called via apply(): " + adder2.add.apply( this, [2,3] ));
    console.log("----------------------------------------------------");
}

Console.log

js_Obj6

  • In this sample we borrow the add() function from the adder2 object by using call(), apply()

Real Sample: Using bind() within an AJAX request

function AjaxLoad2(url) 
{
    this.url = url;
    this.loadDoc = function(topLevelObject) 
    {
      var xhttp = new XMLHttpRequest();
      console.log("--- AJAX testing with bind() : Loading HTML page - URL: " + this.url);
     
      xhttp.onreadystatechange = function() 
        {        
         // When readyState is 4 and status is 200, the response is ready:
        // checkOBJ(this);
        if (xhttp.readyState === 4 && xhttp.status === 200) 
        {
            console.log("Return from AJAX Request:: HTML Page Loaded - URL: " + this.url +  " - status: " + xhttp.status);  
        }
        else if (  xhttp.readyState === 4 && xhttp.status === 0 )
        {
          console.log("Return from AJAX Request:: Error Loading Page - URL: " + this.url +  " - status: " + xhttp.status + " - readyState " + xhttp.readyState);  
          checkOBJ(this);
        }
      }.bind(topLevelObject);
           // .bind(this); should work too 
      
      xhttp.open("GET", url, true);
      xhttp.send();
    };
}

function runThisTest13()
{  
    /* 
     * Note: This Ajax request is expected to fail with  
     *        XMLHttpRequest cannot load https://google.com/. 
     *        No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8080' is therefore not allowed access.
     */
console.log("--- Real Sample using bind() in an AJAX request --- ");    
var ajax = new AjaxLoad2("https://google.com");    
ajax.loadDoc(ajax);
console.log("----------------------------------------------------");
}

Console.log

js_Obj7

  • The XHR failure is expected
  • .bind(topLevelObject);  line allows us to pick up the URL via this.URL when printing the error text

 Function checkOBJ() to display Object Details

function checkOBJ(origThis)
{
    var construtorName = origThis.constructor.name;
    var objectDetails = "";
    try
    {
        objectDetails = JSON.stringify(origThis);
    }  catch ( err )
    {
       objectDetails = " Error to stringify Object " + err; 
    }
    console.dir(origThis); 
    console.log("Constructor Name: " + construtorName + " - this Object Details: " + objectDetails);
}

Reference

ObjectOriented JavaScript Programming by Samples

Creating New Objects using Object Literals

function initObjectsTest()
{
feli.sayMiaow();
}

var feli = 
{
    name: 'Felicitas',
    typ: 'Cat',
    sayMiaow: function ()
    {
        logMessageNEW("Miaow !");
    }  
};

Output:
8:28:35:254 Miaow !
  • Use Object Literals for Simple Objects
  • Objects created via Object Literals are Singelton Objects [ no need to implement a specific design pattern like in Java ]

 

Creating New Objects using a Constructor Function

 

JavaScript Features: Ajax and JavaScript Timeout

What is AJAX?

  • AJAX = Asynchronous JavaScript and XML
  • AJAX is a technique for creating fast and dynamic web pages
  • AJAX allows web pages to be updated asynchronously by exchanging small amounts of data with the server behind the scenes
  • This means that it is possible to update parts of a web page, without reloading the whole page
  • Classic web pages, (which do not use AJAX) must reload the entire page if the content should change
  • Examples of applications using AJAX: Google Maps, Gmail, Youtube, and Facebook tabs
  • AJAX is based on Internet Standards, and uses a combination of:
    • XMLHttpRequest object (to exchange data asynchronously with a server)
    • JavaScript/DOM (to display/interact with the information)
    • CSS (to style the data)
    • XML (often used as the format for transferring data)

Ajax Request used in this sample application

  • Javascript code loadXMLDoc() to trigger an Ajax Request
function loadXMLDoc()
{
var xmlhttp;
var xmlf=document.getElementById('xmlf').value;
var txt,x,i;
if (window.XMLHttpRequest)
  {// code for IE7+, Firefox, Chrome, Opera, Safari
  xmlhttp=new XMLHttpRequest();
  }
else
  {// code for IE6, IE5
  xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
  }

/*
 * Only a specific devision named "myDiv" of our HTTP page will be updated 
 */        
document.getElementById("myDiv").innerHTML="Inside loadXMLDOC -> XML File to load: " + xmlf;  

/*
 *  For details using onreadystatechange Event please read :  
 *  http://www.w3schools.com/ajax/ajax_xmlhttprequest_onreadystatechange.asp 
 */
xmlhttp.onreadystatechange=function()
  {     
  if (xmlhttp.readyState === 4 )
     {
        if ( xmlhttp.status === 200)
            {
            xmlDoc=xmlhttp.responseXML;
            txt="";
            x=xmlDoc.getElementsByTagName("ARTIST");
            for (i=0;i<x.length;i++)
                {
                txt=txt + x[i].childNodes[0].nodeValue + "<br>";
                }
            document.getElementById("myDiv").innerHTML=txt;
            }
         else
            {
            document.getElementById("myDiv").innerHTML='Error loading XML file: ' + xmlf + ' HTTP Status Code: ' + xmlhttp.status;            
            }
     }      
  };
xmlhttp.open("GET",xmlf,true);
xmlhttp.send();
}

Function Details:

  • The function loadXMLDoc() starts an asyncronous  Ajax request
  • Parameter xmls points to XM file cd.xml which gets loaded during this request
  • xmlhttp.open(“GET”,xmlf, true) means we use an HTTP GET request to load cd.xml. True means the load request is asyncronous.
  •  xmlhttp.send() actually sends the HTTP request
  •  xmlhttp.onreadystatechange function() gets invoked when HTTP response is ready
  • x=xmlDoc.getElementsByTagName(“ARTIST”) scans the XML document for ARTISTS attribute
  • txt=txt + x[i].childNodes[0].nodeValue + “<br>” adds the artists to our result string
  •  Finally   document.getElementById(“myDiv”).innerHTML=txt updates the only myDiv part of our WEB page
  •  In case of an error [ You may just change cd.xml to cdd.xml  for testing ]
      document.getElementById(“myDiv”).innerHTML=’Error loading XML file: ‘ + xmlf + ‘ HTTP Status Code: ‘ + xmlhttp.status;
      returns some error like:    Error loading XML file: cdd.xml HTTP Status Code: 404
  • For further details you may read following article :

Implementing a Page TimeOut using JavaScript

  • This is quite simple – For details please read function checkdelay() in JavaScript Code published below.
  • JavaScript Code :
JavaScript Code 
<!DOCTYPE html>
<html>
<head>
<script>
    
function redirect()
{
    window.location="https://google.de";
}

function redirecttohome()
{
    window.location="http://localhost:8180/JavaScriptandAjax-1.0/";
}

function redirectdelay()
{   
   actdelay = delay;
   checkdelay();
}
   
function checkdelay()
{
    /*
     * As long we don't reach the timeout value we need to reschedule checkdelay()
     * every second !
     */
    document.getElementById("myDiv").innerHTML="This page wil be redirected in " + actdelay + " seconds !"; 
    actdelay = actdelay - 1 ;
    if ( actdelay >= 0 )
        setTimeout('checkdelay()', 1000);
    else 
        redirecttohome();
}            


function clean()
{
   document.getElementById("myDiv").innerHTML="";    
}

function loadXMLDoc()
{
var xmlhttp;
var xmlf=document.getElementById('xmlf').value;
var txt,x,i;
if (window.XMLHttpRequest)
  {// code for IE7+, Firefox, Chrome, Opera, Safari
  xmlhttp=new XMLHttpRequest();
  }
else
  {// code for IE6, IE5
  xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
  }

/*
 * Only a specific devision named "myDiv" of our HTTP page will be updated 
 */        
document.getElementById("myDiv").innerHTML="Inside loadXMLDOC -> XML File to load: " + xmlf;  

/*
 *  For details using onreadystatechange Event please read :  
 *  http://www.w3schools.com/ajax/ajax_xmlhttprequest_onreadystatechange.asp 
 */
xmlhttp.onreadystatechange=function()
  {     
  if (xmlhttp.readyState === 4 )
     {
        if ( xmlhttp.status === 200)
            {
            xmlDoc=xmlhttp.responseXML;
            txt="";
            x=xmlDoc.getElementsByTagName("ARTIST");
            for (i=0;i<x.length;i++)
                {
                txt=txt + x[i].childNodes[0].nodeValue + "<br>";
                }
            document.getElementById("myDiv").innerHTML=txt;
            }
         else
            {
            document.getElementById("myDiv").innerHTML='Error loading XML file: ' + xmlf + ' HTTP Status Code: ' + xmlhttp.status;            
            }
     }      
  };
xmlhttp.open("GET",xmlf,true);
xmlhttp.send();

}
    // global Javascript values 
var delay=10;
var actdeley;
</script>
</head>

<body>

<h2>My CD Collection:</h2>
<input name="XMLFile" type="text" maxlength="16" id="xmlf" value="cd.xml" />
<div id="myDiv"></div>
<button type="button" onclick="loadXMLDoc()">Get my CD collection</button>
<button type="button" onclick="clean()">Clean Display</button>
<button type="button" onclick="redirect()">Page Redirect to Google </button>
<button type="button" onclick="redirectdelay()">Delayed Page Redirect to our Home Page </button>
</body>
</html>

XML File used in this sample : cd.xml

<?xml version="1.0" encoding="ISO-8859-1"?>
<CATALOG>
    <CD>
        <TITLE>Empire Burlesque</TITLE>
        <ARTIST>Bob Dylan</ARTIST>
        <COUNTRY>USA</COUNTRY>
        <COMPANY>Columbia</COMPANY>
        <PRICE>10.90</PRICE>
        <YEAR>1985</YEAR>
    </CD>
    <CD>
        <TITLE>Hide your heart</TITLE>
        <ARTIST>Bonnie Tyler</ARTIST>
        <COUNTRY>UK</COUNTRY>
        <COMPANY>CBS Records</COMPANY>
        <PRICE>9.90</PRICE>
        <YEAR>1988</YEAR>
    </CD>
    <CD>
        <TITLE>Greatest Hits</TITLE>
        <ARTIST>Dolly Parton</ARTIST>
        <COUNTRY>USA</COUNTRY>
        <COMPANY>RCA</COMPANY>
        <PRICE>9.90</PRICE>
        <YEAR>1982</YEAR>
    </CD>
</CATALOG>

Reference

Most Info of this POST are just  copied from: