There are several approaches to your problem, and which one you choose depends on a few factors, including (1) target browser support, (2) project goals, and (3) personal preference. I'll highlight a few approaches on which I have opinions in particular.
Symbols
I highlight this one first because it is the most robust solution, and it's the future of JavaScript: it will be included in the upcoming version of the ECMAScript standard. It is possible today with a shim in ES5 compliant browsers. That means it works in basically all A-grade browsers released in the past 2 years. The main down-side is it's not supported in IE 8, but it is supported in IE 9 and 10 (and of course all modern versions of FF, Chrome, and Safari). If IE8 still matters to you, this isn't an option for you yet.
The shim can be downloaded here: SymbolsForES5. This library makes it possible for you to start using this feature, and when Symbols are natively included in browsers in the future, your code should be able to transition nicely.
Here's an example of using Symbols for private members:
var x = { };
var a = new Symbol();
x[a] = 5;
console.log(x[a]); // => 5
As long as you have access to the a
Symbol object, you can read the property from the x
object, but if anyone who doesn't have access to a
can't read it. This allows you to really have private members:
var Person = (function() {
var firstName = new Symbol(),
lastName = new Symbol();
function Person(first, last) {
this[firstName] = first;
this[lastName] = last;
}
Person.prototype.getFullName = function() {
return this[firstName] + ' ' + this[lastName];
};
return Person;
})();
var john = new Person('John', 'Smith');
john.getFullName(); // => 'John Smith'
Object.getOwnPropertyNames(john); // => [ ]
Using a DON'T ACCESS Character
As you mentioned, you can always prefix a property with a character (such as underscore) to indicate that it shouldn't be accessed by external code. The main drawback is the property is still accessible. There are some good benefits, however: (1) efficient memory (2) full ability to use prototypal inheritance (3) easy to use (4) easy to debug (since the properties show up in a debugger) (5) works in all browsers.
I have used this method extensively in the past to great success. As an alternative to the underscore, in one framework I developed (joi), I used the #
symbol because it makes the property harder to access (you have to access it with square bracket notation instead), serving as a gentle reminder to anyone trying to access it that it really probably should be left alone:
function Person(first, last) {
this['#firstName'] = first;
this['#lastName'] = last;
}
Person.prototype.getFullName = function() {
return this['#firstName'] + ' ' + this['#lastName'];
};
var john = new Person('John', 'Smith');
john.getFullName(); // => 'John Smith'
I've used this technique for around 7 years now to great success, and would highly recommend it if Symbols don't suit your needs.
Privates Inside the Constructor
I know you said you decided not to go with this technique due to memory/performance reasons, but if something close to true privates are what you want and you can't use Symbols because you need legacy browser support, it's a good option. The company I work for uses this model on large-scale applications, and the memory consumed really isn't a problem. In addition, I have heard of studies which have discovered ways to optimize for memory and performance in this model, and I believe modern browsers (at least V8/Chrome, if not others) are starting to implement these optimizations. (This information comes from a talk I heard from Brendan Eich; I'm sorry I don't know which talk it was off the top of my head.)
In the end, there are experts which advocate the use of this technique, and while it's not my preference, my company has had success with it, and I can vouch for it as a reliable model.
For completeness, it looks like this:
function Person(first, last) {
this.getFullName = function() {
return first + ' ' + last;
};
}
var john = new Person('John', 'Smith');
john.getFullName(); // => 'John Smith'
A JavaScript engine can optimize this by not really creating a new function for each getFullName
method (even though, by the book, it's supposed to), since in this case there's no way to determine whether it actually creates a new function object each time. The tricky part is once code is introduced which would be able to determine whether a new function object is created each time, the engine needs to switch over to actually do that.
WeakMaps
In another answer benvie (mentioned WeakMaps)[http://stackoverflow.com/a/12955077/1662998]. I would consider this alternative as well if IE8 support is needed and you want the properties to really simulate private members.