Breaking Private Scope in JavaScript
A common way to simulate private variables in JavaScript object "namespaces" is by something like the following:
var someObj = (function () {
var privateCount = 0;
return {
incCount : function() { return ++privateCount; },
decCount : function() { return --privateCount; }
};
}
privateCount
is within the scope of the returned object (and its contents), so this works as expected:
someObj.incCount(); // returns 1
someObj.decCount(); // returns 0
Likewise, because privateCount
itself is not one of the properties, these will fail:
someObj.privateCount; // returns undefined
someObj.queryCount = function() { return privateCount; };
someObj.queryCount(); // Exception: ReferenceError: count is not defined
One might think that something like this would work:
someObj.privateCount = 3;
someObj.incCount(); // returns 1 ???
The newly defined property of privateCount
is completely irrelevant to the previously defined functions.
So how can new functions be added to access the so-called "private" variables?
Introducing eval
eval
is a function that evaluates its string argument as code. This is an incredibly powerful capability, but at first glance it doesn't help...
eval("someObj.queryCount = function() { return privateCount; };");
someObj.queryCount(); // same as before
What happens, though, if the original definition looks something like this?
var otherObj = (function () {
var privateCount = 0;
return {
incCount : function() { return ++privateCount; },
decCount : function() { return --privateCount; },
newFunc: function(n,e) { eval("this." + n + " = " + e + ";"); }
};
})();
These work as before, of course:
otherObj.incCount(); // returns 1
otherObj.decCount(); // returns 0
Let's change the definition of queryCount
:
otherObj.newFunc("queryCount", "function() { return privateCount; }");
otherObj.incCount(); // returns 1
otherObj.queryCount(); // returns 1 !!!
This works (even in strict mode)! Why? eval
evaluates its arguments in the local scope of its caller. Since the eval
call itself is within scope of the so-called "private" variables, the evaluated code string can reference them.
Introducing with
Note that this only addresses the concept of breaking into "privately" scoped variables with new functions. It does not allow the arbitrary addition of new "private" variables, so this will fail:
var errObj = (function() {
var privateCount = 0;
return {
incCount : function() { return ++privateCount; },
decCount : function() { return --privateCount; },
errCount : function() { return privateErr; }, // look here
newFunc: function(n,e) { eval("this." + n + " = " + e + ";"); }
};
})();
errObj.errCount(); // error: privateErr is not defined
Let's try the previous trick:
errObj.newFunc("privateErr", "0");
errObj.errCount(); // undefined: privateErr is not defined ???
The property added is in a scope lower than that of the errCount
function, so it still fails.
However, we can do a full redefinition, carefully:
var errObj = (function () {
var wFunc = function(s) { eval("with (this) { " + s + "};"); };
var pCount = 0;
return {
incCount : function () { return ++pCount; },
decCount : function () { return --pCount; },
errCount : function () { return rCount; },
errInc : function() { return ++rCount; },
newFunc : function (n,s) { eval("this." + n + " = " + s + ";"); },
withFunc : wFunc
};
})();
errObj.withFunc("var rCount = 0; this.errCount = function() { return rCount; };");
errObj.errCount(); // returns 0 !
This will return 0
, as expected. But what of the errInc()
function?
errObj.errInc(); // Exception: ReferenceError: rCount is not defined
What is going on? with
adds its argument to the top of the lookup chain when evaluating its argument. So, essentially, the withFunc()
is adding the top level object into the chain, which then defines a scope with the rCount
variable and a new (overridden) definition of errCount()
. While rCount
is in scope for errCount()
, and errCount()
redefines the errCount()
function found later through the explicit this
, rCount
will still not be visible to errInc()
, being defined in a separate scope.
By playing with the power of eval
in conjunction with with
, we have a solution:
var errObj = (function () {
var wFunc = function(s) { eval("with (this) { " + s + "};"); };
var pCount = 0;
return {
incCount : function () { return ++pCount; },
decCount : function () { return --pCount; },
errCount : function () { return rCount; },
incErr : function () { return ++rCount; },
newFunc : function(s) { eval(s); },
withFunc : wFunc
};
})();
errObj.newFunc("this.withFunc(\"rCount = 0;\");");
errObj.errCount(); // returns 0
errObj.incErr(); // returns 1 !!
errObj.errCount(); // returns 1 !!
Let's trace what's going on. newFunc()
, in the scope of the return object, is evaluating withFunc("rCount = 0")
. So the this
object is pointing to the returned object of errObj
, which is the top scope in which this new variable (rCount
) is defined. The new scope is block-visible to the returned errObj
. That it is a new scope is important; it is NOT visible in or to the environment of pCount
. Essentially, we've just tacked on a new private environment to the errObj
, distinct from its normal "private" enclosing environment.
We can demonstrate that it is indeed still private with a simple call:
errObj.rCount; // undefined
And we can demonstrate that new functions can be added to it in the same way as before:
errObj.newFunc("this.decErr = function() { return --rCount; };");
errObj.decErr(); // returns 0
As far as I am aware, it is not possible to add new private variables to an existing private scope. Please note in the comments below if there is a solution of which I am not aware or if the explanation was unclear.
Conclusion - Safety and Uses
NEVER use this in production code. This is precisely the concept of arbitrary code execution with the hook already open and free for use. Why mention it, then? This can give some amazing debugging, testing and augmentation functionality. This lets QA figure out solutions to the problems they find. This lets coders figure out their own bugs and be able to test a fix on the live (development) environment without constant trivial commits. This lets optimisation geeks be able to hook into the running system and time and test alternatives, again, without pushing commits. It's incredibly powerful. Don't misuse it, and again, NEVER use it in production code - don't let anyone else misuse it either.
For more detailed information on eval and with, please look at the MDN documentation or the ECMAscript standard.
Please feel free to add other solutions and uses below in the comments. Thanks!
Recent Stories
Top DiscoverSDK Experts
Compare Products
Select up to three two products to compare by clicking on the compare icon () of each product.
{{compareToolModel.Error}}
{{CommentsModel.TotalCount}} Comments
Your Comment