Here I found a pattern of javascript module enabling private members of object. If I got it right, it can be written this way:
var myObject1 = (function(){
var privateMember = 42;
return {
publicMember: function() {
return privateMember;
}
}
})();
But there is more effective way:
var myObject2 = new function() {
var privateMember = 42;
this.publicMember = function() {
return privateMember;
}
}
Is there any difference between those two? Are there any other possibilities to implement private members?
This is the result from Chrome debugger:
There's only a couple of real technical differences between those two (pointed out by Bergi in the comments). The differences aren't likely to matter unless you're doing thousands and thousands of these. There's a style difference, which is subjective, but the technical differences are small. See the breakdown below for the gory details.
At a high level, both forms create a new object, they both rely on a closure for the private member. The second way uses a bit more memory, because the function can't be garbage-collected after use, whereas in the first one, it can be. Similarly, the object created to be the function's prototype
property is reclaimable in the first version, but not the second. The second way may also be less clear to some than the first (witness the questions below from @djechlin), not least because of the implicit return value, but that's a style point.
Re your second question:
Are there any other possibilities to implement private members?
For one-off objects like yours, the way you're doing it is probably best (either form, really). But right now, there's sort of another way, and as of ES.Next (the next version of ECMAScript), there's a new truly private way. In both cases, though, it's more useful for when you're doing classes of objects rather than one-offs.
More in this entry on my blog (which also talks about the stuff coming with ES.Next), but I'll go into the most salient details here.
The typical pattern for private members in classes of objects looks something like this:
function Foo(toBePrivate) {
var privateMember = toBePrivate;
this.method1 = function() {
// ...do something with the truly private `privateMember`
};
}
Foo.prototype.method2 = function() {
// ...doesn't have access to the truly private `privateMember`
};
var f = new Foo("private stuff");
// f.method1() can use the private data
// f.method2() cannot
The problem with it is that each and every object created via new Foo
gets its own method1
function. We don't get the reuse we get with method2
, where there's only one of them shared by all objects created by new Foo
. (This isn't necessarily a big deal with modern engines, which are able to reuse the code of method1
even though a new method1
object is created for each Foo
object. But there are some development patterns that dynamically change the prototype, which are obviously unable to act on method1
above.)
Here's the nearly-private pattern we can do today:
var Foo = (function() {
// Create a private name object for our private property
var privateKey = makeRandomString();
// Our constructor
function Foo(toBePrivate) {
this[privateKey] = toBePrivate;
}
// Define the private property so it's non-enumerable
Object.defineProperty(Foo.prototype, privateKey, {
writable: true
});
// Methods shared by all Foo instances
Foo.prototype.method1 = function() {
// ...use this[privateKey] here...
};
Foo.prototype.method2 = function() {
// ...use this[privateKey] here...
};
return Foo;
})();
var f = new Foo("private stuff");
// f.method1() can use the private data
// f.method2() can too!
// Both `method1` and `method2` are *reused* by all `Foo` objects
...where makeRandomString
does exactly that, it gives us a new random string every time we call it.
The property we create is not private, but it's really obscure. It doesn't show up in for-in
loops (because the property we created is non-enumerable) and its name changes every time the code runs. So any code attempting to use the private data must first figure out the property name, which is a non-trivial exercise as that code can't get a list of the non-enumerable property names of the object. Naturally, though, one glance at the object in a debugger shows you the property and its value. The property is really obscure, but not quite truly private.
This pattern is improved markedly by the stuff coming in ES.Next, the next version of ECMAScript. We'll get private name objects, which are useful on their own and are also used by the the new classses.
Here's how private names apply to the above:
// **ES.Next, not yet available in the wild**
import Name from "@name";
var Foo = (function() {
// Create a private name object for our private property
var privateKey = new Name();
function Foo(toBePrivate) {
this[privateKey] = toBePrivate;
}
Foo.prototype.method1 = function() {
// ...use this[privateKey] here...
};
Foo.prototype.method2 = function() {
// ...use this[privateKey] here...
};
return Foo;
})();
var f = new Foo("private stuff");
// f.method1() can use the private data
// f.method2() can too!
// Both `method1` and `method2` are *reused* by all `Foo` objects
Properties created with private name objects never show up in for-in
enumerations at all, and their names are not strings. Code cannot access a property whose name is a private name object without having that specific name object. Since the privateKey
variable above is completely private to the Foo
class, no other code can use that property. It's completely private. Naturally, these will show up in debuggers, but then, nothing is private from debuggers.
@djechlin asked for a breakdown of exactly how each of your forms for a private member work, and it helps us understand the difference between them that Bergi highlighted:
var myObject1 = (function(){
var privateMember = 42;
return {
publicMember: function() {
return privateMember;
}
}
})();
(This list leaves out a few details that I don't think are relevant.)
A property called myObject1
is created on the current variable binding object (which may be window
if this is global) with the value undefined
.
The function expression is evaluated:
A) A Function
object to be created.
B) A blank object is created and assigned to the new function's prototype
property.
C) A constructor
property is created on that object and given a reference to the function.
D) A reference to the current variable binding object is stored on the function.
The function is called, creating (amongst other things) a variable binding object for the execution context of the call.
A property called privateMember
is created and assigned to the variable binding object from Step 3.
The value 42 is assigned to the privateMember
property of the VBO.
A blank object is created and given a prototype from Object.prototype
.
The inner function expression is evaluated, a Function
object created (with a blank object for its prototype
property and a constructor
property put on that object, and with a reference to the current variable binding object [the one from Step 3]).
That function is assigned to a property on the blank object from Step 5 as publicMember
.
A reference to the object from Step 6 is returned from the main anonymous function.
That object reference is stored in the myObject1
property created in Step 1.
The main anonymous function (from Step 2) has no outstanding references, and so can be reclaimed by GC; and so the object referenced by its prototype
property can also be reclaimed by GC.
var myObject2 = new function() {
var privateMember = 42;
this.publicMember = function() {
return privateMember;
}
}
(Again, some irrelevant details left out.)
A property called myObject2
is created on the current variable binding object (which may be window
if this is global) with the value undefined
.
The function expression is evaluated:
A) A Function
object to be created.
B) A blank object is created and assigned to the new function's prototype
property.
C) A constructor
property is created on that object and given a reference to the function.
D) A reference to the current variable binding object is stored on the function.
A new, blank object is created and assigned its prototype from the anonymous function's prototype
property.
The function is called with the object from Step 3 passed in as this
, creating (amongst other things) a variable binding object for the execution context of the call.
A property called privateMember
is created and assigned to the variable binding object from Step 4.
The value 42 is assigned to the privateMember
property of the VBO.
The inner function expression is evaluated, a Function
object created (with a blank object for its prototype
property and a constructor
property put on that object, and with a reference to the current variable binding object [the one from Step 4]).
That function is assigned to a property on the blank object from Step 5 as publicMember
.
The function returns, and because it doesn't return an object, the result of the new
expression is a reference to the object created in Step 3.
That object reference is stored in the myObject2
property created in Step 1.
The main anonymous function (from Step 2) cannot be reclaimed by GC, because myObject2
's underlying prototype has a reference to it on the constructor
property (and so both the function and the object assigned to its prototype
property remain in memory).
You could release the function (but not the object assigned to its prototype
property) by adding this line inside it:
delete this.constructor.prototype.constructor;
That removes the reference to the function from the object assigned to its prototype
property. That object remains as myObject2
's underlying prototype, but it no longer refers back to the function, so the function is eligible for GC.
But at that point, we're well into obscurity land. :-)
So they're very nearly the same, with the small difference that the main anonymous function and the object on its prototype
property aren't eligible for GC. It would take thousands and thousands of these for that to matter in the real world.
(Side note: Some implementations may well defer some of the steps of creating a function — such as creating a blank object for its prototype
property and setting its constructor
— until/unless that prototype
property is used, since of course in the vast majority of cases, it's never used, because the vast majority of functions are never used as constructor functions. So your first form may be a little tiny teeny bit more efficient because it can skip those steps. It's a difference that is unlikely to matter unless you're doing thousands of these.)
FWIW, the first can also be written like this, if your concern is the number of lines, number of parentheses, or not liking the object literal, etc.:
var myObject1 = function(){
var obj = {};
var privateMember = 42;
obj.publicMember = function() {
return privateMember;
};
return obj;
}();
As a matter of style, I prefer the explicit return, but that's a matter of style.
Is there any difference between those two?
One major difference is that in the second case using new, the returned object has an additional object on it's [[Prototype]]
chain that is the public prototype of the constructor function expression. That object can be accessed in some browsers using __proto__
.
Also, myObject1.constructor
is the built–in Object function, while myObject2.constructor
is the function created by the function expression.
Are there any other possibilities to implement private members?
I suppose you could use the object's (otherwise useless) __proto__
object, but that's more obfuscation than privacy (and not available in some browsers):
var foo = new function() {
this.__proto__.privateMember = 42;
this.publicMember = function() {
return this.__proto__.privateMember;
}
};
alert(foo.publicMember());
Pretty ugly really.
Closures have been used to emulate private members in javascript for quite some time (more than 10 years at least) but there hasn't been any real pressure to include them in ECMA-262 (as far as I'm aware). I suppose that indicates that private members are handy, but not critical to implement functionality required for the type of host environment scripting that javascript is typically used for.
As Bergi says, the ES5 Object.getPrototypeOf method should be used in stead of the (non standard) __proto__
property. My bad for no pointing that out initially.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With