Skip to main content
Ben Nadel at cf.Objective() 2010 (Minneapolis, MN) with: Justin Mclean
Ben Nadel at cf.Objective() 2010 (Minneapolis, MN) with: Justin Mclean

Circumventing Private Variables In Javascript Using Method Reassignment

By
Published in Comments (18)

Over the weekend, with the help of Julian Aubourg, I continued my exploration of jQuery 1.5, this time looking at how to use $.ajaxPrefilter() in conjunction with $.ajaxSetup() to globally configure AJAX requests. The elegance of this approach makes use of the fact that the new Deferred.promise() method can copy the methods of one deferred object into the properties of another deferred object, leaving the original object references in place. A while back, I used a similar approach to circumvent ColdFusion component security; I suddenly wondered if the same approach could be used to circumvent "private" variables in Javascript.

To explore this concept, I wanted to see if I could take a Person object with a private "Gender" property and reassign the person's gender without breaking any object references. In the following demo, notice that I never lose the object reference to my original Person instance.

<!DOCTYPE html>
<html>
<head>
	<title>Circumventing Private Variables With Method References</title>
	<script type="text/javascript">


		// I am the constructor function for a Person object.
		// None of the variables are directly public - but, they
		// can all be accessed and mutated through method calls.
		function Person( name, age, gender ){

			// Store the private variables. Because these are being
			// declared in the local scope of the constructor
			// function, they will not be directly availalbe from
			// outside this function.
			var name = name;
			var age = age;
			var gender = gender;


			// Define the person object that we are goint to return.
			// This includes public functions for accessing and
			// mutating the private variables.
			//
			// NOTE : In this object definition, notice that there is
			// no way to change the gender of the person once it has
			// been sent.
			var person = {

				// I return the age.
				getAge: function(){
					return( age );
				},

				// I return the gender.
				getGender: function(){
					return( gender );
				},

				// I return the name.
				getName: function(){
					return( name );
				},

				// I say hello.
				sayHello: function(){
					return(
						"Hello, my name is " +
						this.getName() +
						" and, I am a " +
						(
							(this.getGender() == "Female") ?
							"stunning " :
							"handsome "
						) +
						this.getAge() +
						" year old " +
						this.getGender() +
						"."
					);
				},

				// I allow for setting of the age.
				setAge: function( value ){
					age = value;

					// Return this object reference in order to allow
					// for method chaining.
					return( this );
				},

				// I allow for setting of the name.
				setName: function( value ){
					name = value;

					// Return this object reference in order to allow
					// for method chaining.
					return( this );
				}

			};


			// Return the person instance.
			return( person );

		}


		// -------------------------------------------------- //
		// -------------------------------------------------- //
		// -------------------------------------------------- //
		// -------------------------------------------------- //


		// I take a person and change their sex. Welcome to the
		// 21st century!!
		function transgenderfication( person ){

			// To start this gender reassignment, we need to create
			// a new instance of a person with the appropriate
			// properties of the incoming person.
			//
			// When we do this, we need to use the gender opposite of
			// the gender currently used by the incoming person.
			var genderReassignment = new Person(
				person.getName(),
				person.getAge(),
				(
					(person.getGender() == "Female") ?
					"Male" :
					"Female"
				)
			);

			// Now that we have our gender reassignment clone, we
			// have created the foundation of methods that we need
			// to transplant into our gender reassignment patient.
			for (var method in person){

				// Check to make sure that this a direct method of
				// the person; if it is not, then it is part of the
				// prototype chain and should probably be left in
				// place - no need to create a local copy.
				if (person.hasOwnProperty( method )){

					// Copy the method definition from our gender
					// reassignment container into the patient.
					person[ method ] = genderReassignment[ method ];

				}

			}

			// The gender reassignment is complete. Return the
			// updated for convenience - this is not really
			// necessary since the object reference has not changed.
			return( person );

		}


		// -------------------------------------------------- //
		// -------------------------------------------------- //
		// -------------------------------------------------- //
		// -------------------------------------------------- //


		// Create a new girl.
		var sarah = new Person( "Sarah", 32, "Female" );

		// Have the girl say hello.
		console.log( sarah.sayHello() );

		// Now, let's change the gender of the girl. When we do this,
		// note that we are NOT storing and reteurn reference.
		transgenderfication( sarah );

		// Now that sarah has undergone gender reassignemnt, have HIM
		// say hello again.
		//
		// NOTE: We are using the exact same object reference.
		console.log( sarah.sayHello() );


	</script>
</head>
<body>
	<!-- Intentionally left blank. -->
</body>
</html>

As you can see, the name, age, and gender properties of the Person class are all private; that is, they are locally scoped to the object constructor - not a public property [this-scoped] of the object. As such, none of these properties can be accessed or mutated without the use of intermediary methods.

Using the transgenderfication() function, however, I attempt to circumvent the use of private variables though method reassignment. With the transgenderfication() function, I create a completely new instance of the Person class which mirrors the incoming person instance in every way except gender. Then, I copy all of the methods from the newly created instance into the existing instance.

When I use this approach and run the above code, I get the following output:

Hello, my name is Sarah and, I am a stunning 32 year old Female.
Hello, my name is Sarah and, I am a handsome 32 year old Male.

Since I never reassigned my "sarah" variable, it's safe to say that the gender reassignment was a success! I was able to change the private variables of the existing sarah instance by swapping in new instance methods with an alternate lexical binding. Essentially, the new methods are bound to the private variables of the temporary person instance created within the transgenderfication() function; however, since all the methods were swapped, this change of the private context is completely transparent.

Well - not completely. Since method references can be passed around like any object (hint: they are objects!), it's possible that there are still references to the original getGender() function that references the original private scope of the initial sarah instance. As such, even with the new private variable context, the old private scope can still be exposed. This kind of behavior would quickly pop-up if one were to use jQuery's proxy() method to statically bind function references to an object invocation context.

Is there a value to runtime method swapping? Well, clearly there is when it comes to Deferred objects in jQuery 1.5; but, is there value in using method swapping as a means to circumvent private variables? Due to the lexical nature of method binding, this approach clearly leaves some lose ends, especially when it comes to static method binding. If anything, I find myself wondering - yet again - if private variables are really worth the cost in a language like Javascript?

Want to use code from this post? Check out the license.

Reader Comments

15,841 Comments

@Garrett,

Glad you enjoyed - I figure it was an example was tangible enough to facilitate the understanding of private variable scopes.

5 Comments

This is a decent abstraction around overriding the definition of an object's method(s), but I'm not sure it should be referred to as circumventing an object's private variables -- while the getGender method now returns a different value than it originally would have, the value of the private gender variable is unchanged, and other methods that use it would be unaffected.

15,841 Comments

@Rebecca,

It depend on how the object is being used. At the end of the blog post, I tried to explain that any original function reference that's been captured *will* still point to the original private variables scope. However, assuming for the sake of argument that that has not been done, since all methods have been swapped, you are function-speaking, dealing with a new private scope which YOU have redefined.

15,841 Comments

@Rebecca,

... my point wasn't to show that individual private variables could be access - due to lexical binding, that simply isn't possible in Javascript (like how it is in ColdFusion); my point was more to show that private variables don't ensure much of anything.

4 Comments

Most languages have some method of changing private variables, whether through a reflection api, or through hacks like yours above - which is even more dangerous than reflection, as some, not all references to that variable are switched.

Making variables "private" is generally about keeping exposed scoped of an object as small as possible. It's not about true privacy - everything in memory is generally readable and changeable somehow - but more about, especially in the context of a library, saying - "this is what it's safe for you to use - internals are the responsibility of this closure/object, & may change in future releases so don't touch them please". I think of the scope of members more as documentation like that than anything else.

Obviously as a programmer, especially one using javascript, you have plenty of rope to hang yourself with. Have fun.

15,841 Comments

@Tim,

I will agree with you on that - that private variables are more about documentation than anything else. What rubs me the wrong way, however, is when a language requires a *different* syntax for referencing public and private variables. This to me feels very wrong.

ColdFusion is guilty of this as well, putting public and private variables in different scopes; which is why I typically don't use private variables in CF.

I'd definitely be more comfortable with something that implements private variables by "convention" rather than a twisting of lexical binding:

var this._private = value;
var this.public = value;

At least this way, the variables can be accessed using uniform scoping. And, it allows for the language to manipulate the objects in more complex ways, whether building them up through some sort of Dependency Injection or what not.

Plus, when you use this approach, you allow for the use of the Prototype to factor out class methods which allow reuse of function references (which seems to be what the essence of a Prototypal language is).

Of course, I am not saying that lexical binding is bad in any way; in fact, it's awesome and allows us to use it when it adds value. Recently, I discovered that the Deferred objects in jQuery 1.5 make use of closures in order to allow deferred methods to be passed around without any explicit binding:

www.bennadel.com/blog/2125-The-Power-Of-Closures-Deferred-Object-Bindings-In-jQuery-1-5.htm

Now that's cool stuff - but, it's definitely something done for a very specific reason.

Annnnnnnyway - all to say, I am pro-private variables when they can be accessed in uniform ways.

4 Comments

dunno what's so obtuse about

closure...{
var privateVar;
obj.publicVar;
return obj;
};

the underscore convention is definitely a matter of personal taste, like hungarian notation, both of which i dislike.

The fun comes when, in the middle of your closure, the scope of "this" is not what you expected. Seen lots of folk moving to javascript from classical OO languages trip over this one.

15,841 Comments

@Tim,

I am not sure what you mean by "obtuse"? Are you referring to the technical difficulty of using locally-scoped variables? My dislike is not based on difficulty, but rather on consistency. It's my personal taste that I would like to reference public and private variables in the same scope. I can't come up with any reason for that other than personal taste and an enjoyment in consistency :)

I can't speak from traditional programming languages as I don't have much experience in them. ColdFusion, while OO, functions in a similar manner - the THIS scope is bound at invocation time and can change depending on where the method reference is being used. So, for ColdFusion developers, having THIS change in Javascript is nothing new.

4 Comments

Which of these "inconsistencies" would you like to replace with

var myObj.publicVar = 42?

All of them?

myObj.publicVar = 42;

Object.defineProperty(myObj, "publicVar", { value: 42});

myObj["publicVar"] = 42;

myObj = {
internal : 42,
get publicVar () {
return internal;
},
set publicVar(val) {
this.internal = val;
}
}

Or do they all have their place?

15,841 Comments

@Tim,

Sorry, I think I miscommunicated what I mean when I referred to inconsistencies. My apologies. I didn't mean to imply that I dislike syntactic variations. In fact, just the opposite - it's a flexible syntax that provides a language like Javascript with so much power. Being able to invoke things using dot and brackets notation is something I think we can all agree are very very nice:

obj[ "method" ]()
obj.method()

Being able to accomplish the same thing in many ways is definitely an enjoyable and powerful feature of the language.

When I referred to inconsistencies, I suppose I was referring to those of "intent", for lack of a better term. When it comes to syntax variation, they are all a means to the same intent. But, when it comes to public vs. private, the intent of the developer is still to access a variable. BUT, the implementation of the language requires us to actually change our intent to be explicit - I want to access this *private* variable or I want to access this *public* variable.

There's something about a language that requires that intent to diverge on the behalf of the developer that simply rubs me the wrong way .... again, on a personal level.

I believe in more traditional languages like Java and .NET (I may be completely wrong), but public and private variables can be uniformly accessed using "this" internal to the object instance. The intent of the of the developer is consistent.

Perhaps even arguing this point is useless as I suppose it boils down to a matter that is strictly personal :)

4 Comments

"Perhaps even arguing this point is useless as I suppose it boils down to a matter that is strictly personal :)"

Yeah.

You're right, in Java and .Net "this" is predictable, and a class can refer to it's public and private variables consistently.

In Javascript, as you say, "this" is not predictable, so the language provides var's in function scope to help deal with this.
Which is why you often see "var that=this" or similar at the beginning of a function. This helps get round the changing nature of "this".
Then, if you are returning "that" you can choose to not return scoped variables by not making them part of "that", by declaring them, as you did "that", as "var privateVar = 42" within the same scope.

To me the intent is clear, and not divergent. That's my personal opinion. Suppose we can disagree.

16 Comments

This is a fun way to do things, but let's not forget that you can do this in Coldfusion as well!

components/mikestest.cfc:

<cfcomponent>
	<cfscript>
		function init () {
			this.int1 = 0;
			this.int2 = 0;
			 
			return this;
		}
		 
		function config(int1, int2) {
			this.int1 = arguments.int1;
			this.int2 = arguments.int2;
			 
			return;
		}
		 
		function killme() {
			return this.int1+this.int2;
		}
	</cfscript>
</cfcomponent>

test.cfm:

<cfscript>
	foo = createObject("component", "components.mikestest").init();
	foo.config(2, 3);
	 
	function replacement () {
		return this.int1 * this.int2;
	}
	 
	first = foo.killme(2, 3);
	foo.killme = replacement;
	second = foo.killme(2, 3);
</cfscript>
<cfoutput>
<h1>First: #first#</h1>
<h1>Second: #second#</h1>
</cfoutput>

The output is:
<h1>First: 5</h1>
<h1>Second: 6</h1>

15,841 Comments

@Tim,

Agreed to disagree :) And, who knows - I might come around eventually. I've definitely changed my mind on things before. I used to uppercase all my scopes and use Hungarian notation and now, I've seen the light and use headless-camel-case for everything... and I've never looked back :D

Although I went to school from computer science, I wouldn't in any way say that I was classically trained. I had no idea what I was doing while in school and then I went directly into web development. I am sure I have yet to get comfortable with many of the core concepts of software engineering. Sometimes I just follow my gut and my gut is sometimes wrong.

Don't get me started on single-quotes vs. double-quotes ... and white-space... forget about it :P

@Themanchicken,

Yeah, ColdFusion allows for very similar stuff; although ColdFusion doesn't go the other direction - it doesn't have the closure option that Javascript presents.

16 Comments

@Ben,

True, though I think what I put up might be the closest thing to a closure you can get... which isn't actually very close at all. *sigh*

15,841 Comments

@Themanchicken,

Ha ha - it would be interesting to see closures in ColdFusion. I think it would need a slightly different syntax otherwise it would break a looooot of code. Like, instead of a "function" keyword, it might need to use an actual "closure" keyword:

function(){ ... }

vs.

closure(){ ... }

Who knows. I bet if they were there, we'd find a way to leverage them.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel