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
() {
var
_age = 0,
_name =
'John Doe'
;
var
socialSecurity =
'444 555 666'
;
var
bloodType =
'O negative'
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"
+ " }\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:
1
|
[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:
1
|
alert(rob._privates[ 'nestedFc' ]());
|
Here is the demo code in its entirety:
Untitled
<script type=
"text/javascript"
>
var
Reflection = {};
Reflection.createExposedInstance =
function
(objectConstructor)
{
var
instance = objectConstructor;
var
objectAsString = objectConstructor.toString();
var
aPrivateFunctions = objectAsString.substr(objectAsString.indexOf(
'{'
)+1).match(/\s*(((
var
)|(,))\s*[^=\s]+?\s*=\s*)?
function
\s*?(\w.*?)*\([^{]+\{/g);
if
(aPrivateFunctions) {
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"
+ " }\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!)"
// ]]></script>
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.