SHARE
Facebook X Pinterest WhatsApp

Accessing Private Functions in JavaScript: Nested Functions

Written By
thumbnail
Rob Gravelle
Rob Gravelle
Jan 17, 2013

In the Accessing Private Functions in JavaScript article, we explored a means of gaining access to an object or method’s inner helper functions in order to be able to test them using a framework such as Jasmine. In today’s follow up, we’re going to learn how to fetch nested functions using an iterative solution.

Revisiting the Exposed Instance Creation Code

The technique employed in the Accessing Private Functions in JavaScript article involved converting a function into an object constructor by placing the new keyword in front of the call. The function itself is also modified by injecting code for identifying and exposing private functions:

var funcString = "new ("
               + objectAsString.substring(0, objectAsString.length - 1)
               + ";this._privates = {};"
               + "this._initPrivates = function(pf) {"
               + "  for (var i = 0, ii = pf.length; i < ii; i++)"
               + "  {"
               + "    var fn = pf[i].replace(/(function\s+)/, '').replace('(', '');"
               + "    try { "
               + "      this._privates[fn] = eval(fn);"
               + "    } catch (e) {"
               + "      if (e.name == 'ReferenceError') { continue; }"
               + "      else { throw e; }"
               + "    }"
               + "  }"
               + "}"
               + "nn})()";
 

Evaluating the modified function code would then yield something akin to the following:

new (
function test1() {
      alert('test1');
      var obj = { 
        test3: 'test3',
      bla:   234
      };
       
      function nestedFc() {
        alert('I am nested!');
      }
    
;
this._privates = {};
this._initPrivates = function(pf) {
  //pf contains a list of function signatures
  for (var i = 0, ii = pf.length; i < ii; i++) {
    var fn = pf[i].replace(/(functions+)/, '').replace('(', '');
    try { 
      this._privates[fn] = eval(fn);
    } catch (e) {
      if (e.name == 'ReferenceError') { continue; }
      else { throw e; }
    }
  }
}
})()

The _initPrivates() method could then be called on the newly-created instance to return inner functions.

Two Kinds of Function Declaration

There are typically two ways of declaring a function. These include the forms:

function aFunction() {

}

AND

var aFunction = function() {

}

Other, less common ways to create functions include using the Function() method, as well as using factory methods that return a function. For now, we’re going to consider the latter two to be out of scope, and focus on the first function a Function() {} style. The reason is that the regular expressions required for var type declarations is different.

 

When an Error Is Not Really an Error

In some cases, attempting to evaluate the function name would result in an error. This could occur if the function was not a real function at all, but part of a comment, such as in the case of old code. More likely, the function is simply out of scope of the parent one because it is nested within a child function. In fact, one such function was included in the original test code: the aTest() function contains its own nested function called – appropriately enough – nestedFunction. Calling it from the Person function doesn’t succeed as only aTest() has access to it:

var Person = function() { 
    //defaults
    var _age  =  0,
        _name = 'John Doe';
     
    var socialSecurity = '444 555 666';
    var bloodType      = 'O negative'
    //this is a global variable
    hatSize            = 'medium';
    var noValue;
     
    var aTest = function() {
      var nestedVar = 'nestedVar';
      var nestedFunction = function() {
        return 'nestedFunction';
      };
   //...
};     

The Iterative Solution

Since we already have all the code we need to ferret out inner functions, it stands to reason that we can exploit it to delve into each function in turn. All that’s required is to include a variable to track the previous function name (lastFn), and a call to the Reflection.createExposedInstance() method, passing in the previous nested function. createExposedInstance() returns an instantiated instance of the function which includes the _initPrivates() method, as well as the _privates function holder. The latter can be iterated over to retrieve the nested functions:

var funcString = "new (n"
               + objectAsString.substring(0, objectAsString.length - 1) + 'n'
               + ";n"
               + "this._privates = {};n"
               + "this._initPrivates = function(pf) {n"
               + "  for (var i = 0, ii = pf.length; i < ii; i++) {n"
               + "    var lastFn, fn = pf[i].replace(/(function\s+)/, '').replace('(', '');n"
               + "    try { n"
               + "      lastFn = this._privates[fn] = eval(fn);n"
               + "    } catch (e) {n"
               + "      if (  e.name == 'ReferenceError'n" 
               + "         || e.name == 'TypeError') {n"
               + "      var nestedFunctions = Reflection.createExposedInstance(lastFn);n"
               + "      for (fn in nestedFunctions._privates) { this._privates[fn] = nestedFunctions._privates[fn]; }n" //continue;n"
               + "    }n"
               + "    }n"
               + "      else { throw e; }n"
               + "    }n"
               + "  }n"
               + "}n"
               + "})()";

Note that the TypeError has been included in the if test because scope problems don’t always come up consistently as ReferenceErrors, depending on the browser used. For instance, Internet Explorer 8 reports them as TypeErrors instead.

With the above modifications, the Reflection.createExposedInstance() method now contains the following nested functions:

[test1, nestedFc, anothernNestedFc, test2, test3, aFunction]

As such, calling the nestedFc or anotherNestedFn functions is now simply a matter of going through the _privates() collection:

alert(rob._privates['nestedFc']());  //displays "nestedFc"

Here is the demo code in its entirety:

Untitled

var Reflection = {}; 
 
Reflection.createExposedInstance = function(objectConstructor)
{
  var instance = objectConstructor;
  // get the functions as a string
  var objectAsString    = objectConstructor.toString();
  var aPrivateFunctions = objectAsString.substr(objectAsString.indexOf('{')+1).match(/s*(((var)|(,))s*[^=s]+?s*=s*)?functions*?(w.*?)*([^{]+{/g);  // /functions*?(w.*?)(/g);

  if (aPrivateFunctions) {
    // To expose the private functions, we create
    // a new function that goes trough the functions string
    // we could have done all string parsing in this class and
    // only associate the functions directly with string
    // manipulation here and not inside the new class,
    // but then we would have to expose the functions as string
    // in the code, which could lead to problems in the eval since
    // string might have semicolons, line breaks etc.
    var funcString = "new (n"
                   + objectAsString.substring(0, objectAsString.length - 1) + 'n'
                   + ";n"
                   + "this._privates = {};n"
                   + "this._initPrivates = function(pf) {n"
                   + "  for (var i = 0, ii = pf.length; i < ii; i++) {n"
                   + "    var lastFn, fn = pf[i].replace(/(function\s+)/, '').replace('(', '');n"
                   + "    try { n"
                   + "      lastFn = this._privates[fn] = eval(fn);n"
                   + "    } catch (e) {n"
                   + "      if (  e.name == 'ReferenceError'n" 
                   + "         || e.name == 'TypeError') {n"
                   + "      var nestedFunctions = Reflection.createExposedInstance(lastFn) || {};n"
                   + "      for (fn in nestedFunctions._privates) { this._privates[fn] = nestedFunctions._privates[fn]; }n" //continue;n"
                   + "    }n"
                   + "      else { throw e; }n"
                   + "    }n"
                   + "  }n"
                   + "}n"
                   + "})()";
   
    instance = eval(funcString);
    instance._initPrivates(aPrivateFunctions);
   
    // delete the initiation functions
    delete instance._initPrivates;
  }
  return instance;
}
 
var Person = function() { 
    //defaults
    var _age  =  0,
        _name = 'John Doe';
     
    var socialSecurity = '444 555 666';
    var bloodType      = 'O negative'
    //this is a global variable
    hatSize            = 'medium';
    var noValue;
     
    var aTest = function() {
      var nestedVar = 'nestedVar';
      var nestedFunction = function() {
        return 'nestedFunction';
      };
       
      alert('aTest');
    },
      anotherTest = function() {
        alert('anotherTest');
    };
     
    function test1() {
      alert('test1');
      var obj = { 
        test3: 'test3',
      bla:   234
      };
       
      function nestedFc() {
        alert('I am nested!');
      }
      function anothernNestedFc() {
        return 'anotherNestedFc';
      }
    }
     
    function test2() {
      alert('test2');
    }
     
    function test3() {
      alert('test3');
      
      return { 
        test3: 'test3',
        bla:   234
      };
    }
     
    this.initialize = function(name, age) {
      _name = _name || name;
      _age  = _age  || age;
    };
     
    if (arguments.length) this.initialize();
     
    //public properties. no accessors required
    this.phoneNumber = '555-224-5555';
    this.address     = '22 Acacia ave. London, England';
     
    //getters and setters
    this.getName     = function()      { return _name; };
    this.setName     = function (name) { _name = name; };
     
    //private functions
    function aFunction( arg1 ) {
      alert('I am a private function (ha!)');
    }
     
    //public methods
    this.addBirthday = function()      { _age++; };
    this.toString    = function()      { return 'My name is "+_name+" and I am "_age+" years old.'; };
}; 
 
//create an instance of a person
var rob = Reflection.createExposedInstance(Person); //new Person('Rob', 29); //still 29! (I wish!)
 
//document.write
rob._privates['aFunction']();  //alerts "I am a private function (ha!)"

Conclusion

To remove even more false positives, we should remove comments from the source code before running the RegEx. However, as we'll see in an upcoming article, that's a little easier said than done. We'll examine what's involved in making that work along with capturing var type function declarations at that point.

Recommended for you...

The Revolutionary ES6 Rest and Spread Operators
Rob Gravelle
Aug 23, 2022
Ahead of Time (AOT) Compilation in Angular
Tariq Siddiqui
Aug 16, 2022
Converting a JavaScript Object to a String
Rob Gravelle
Aug 14, 2022
Understanding Primitive Type Coercion in JavaScript
Rob Gravelle
Jul 28, 2022
HTML Goodies Logo

The original home of HTML tutorials. HTMLGoodies is a website dedicated to publishing tutorials that cover every aspect of being a web developer. We cover programming and web development tutorials on languages and technologies such as HTML, JavaScript, and CSS. In addition, our articles cover web frameworks like Angular and React.JS, as well as popular Content Management Systems (CMS) that include WordPress, Drupal, and Joomla. Website development platforms like Shopify, Squarespace, and Wix are also featured. Topics related to solid web design and Internet Marketing also find a home on HTMLGoodies, as we discuss UX/UI Design, Search Engine Optimization (SEO), and web dev best practices.

Property of TechnologyAdvice. © 2025 TechnologyAdvice. All Rights Reserved

Advertiser Disclosure: Some of the products that appear on this site are from companies from which TechnologyAdvice receives compensation. This compensation may impact how and where products appear on this site including, for example, the order in which they appear. TechnologyAdvice does not include all companies or all types of products available in the marketplace.