Reputation: 4186
The first couple paragraphs describe what I'm trying to achieve, the actual question is at the end. Thanks
Previously, I've simply been using new
keyword to create objects, and prototypes to assign methods and handle inheritance. Recently, however, (partially inspired by CoffeeScript-generated JS) I decided to play with an object-creating function that would look something like this:
var Test = function(a) {
function Test(a) {
this.a = a;
}
var numCalls = 0;
Test.prototype.output = function() {
alert('I was initialized with ' + this.a);
numCalls++;
};
Test.prototype.called = function() {
alert('You called the output ' + numCalls + ' times');
};
return new Test(a);
};
I would then create a new object like this:
var obj = Test('string');
This approach has a couple of advantages over the typical approach that uses new
for every instance. First, I'm much less likely to forget using the word new
(I know there are other ways of avoiding new
, but from what I've seen they have similar problems to the one I describe below), and second, I can easily see 'private' variables that constructor sees in any function that's now part of the class. I did run into a caveat when testing it though. instanceof
no longer works because it doesn't see the inner-scoped object Test. To work around that, I tried to use constructor property instead:
var a = Test('one');
var b = Test('two');
a.constructor == b.constructor // false
a.constructor.name == b.constructor.name // true
And this is what got me confused. Creating the same objects without using an object-creating function would cause their constructor to be equal, but in this case they differ. My guess is that what's happening is that a brand new object type gets generated every time the function runs (the object structure is the same, but the instance of the prototype is different).
If my understanding of the problem is correct, does that also mean that the code is allocating additional memory per object instance to my JavaScript for functions that should be shared between instances by tricking it to create an identical object prototype for each instance (defeating the entire purpose of using prototype)? If so, is there a good way to avoid this while still keeping the benefits of this pattern (ability to share private variables between internal functions and not having to use new
keyword)?
If I'm misunderstanding the problem, can someone enlighten me on what's actually happening?
Upvotes: 6
Views: 321
Reputation: 1232
Now I searched hard to get this done: The perfect class with full encapsulation and no "new" needed to instantiate it. After searching a while I came up with this:
function Test(x){
var innerFunction = function(y){
var variable = y;
this.getA = function(){
return variable;
}
this.setA = function(x){
variable = x;
}
}
return new innerFunction(x);
}
But the test results proved it wrong:
var a = Test("foo");
var b = Test("baz");
alert(a.constructor == b.constructor); //false, not good!
alert(a.constructor.name == b.constructor.name); //true
So there seemed to be the wrong scope, so I used a public inner function:
function Test(x){
function innerFunction(y){
var variable = y;
this.getA = function(){
return variable;
}
this.setA = function(x){
variable = x;
}
}
return new innerFunction(x);
}
And running some extensive tests proved that it is correct:
var a = Test("foo");
var b = Test("baz");
alert(a.constructor == b.constructor); //true, made it!
alert(a.constructor.name == b.constructor.name); //true
alert(a.getA()); //"foo" as expected
alert(a.getA() == b.getA()); //false as expected
a.variable = "whatever";
alert(a.getA()); //"foo" as expected
alert(a.variable); //"whatever", doesn't seem preventable
a.setA("somewhere");
alert(a.getA()); //"somewhere", as expected
alert(a.variable); //"whatever", doesn't seem preventable
But, can we use several functions in this way? This was my first approach:
function Test(x){
function innerFunction(y){
var variable = y;
this.getA = function(){
return variable;
}
this.setA = function(x){
variable = x;
}
}
return new innerFunction(x);
}
function TestToo(x){
function innerFunction(y){
var variable = y;
this.getA = function(){
return variable;
}
this.setA = function(x){
variable = x;
}
}
return new innerFunction(x);
}
var a = Test("foo");
var b = Test("baz");
var c = TestToo("foo");
var d = TestToo("baz");
alert(a.constructor == b.constructor); //true, as expected
alert(a.constructor.name == b.constructor.name); //true, as expected
alert(c.constructor == d.constructor); //true, as expected
alert(c.constructor.name == d.constructor.name); //true, as expected
alert(a.constructor == c.constructor); //false, as expected
alert(a.constructor.name == c.constructor.name); //true, as NOT expected
So this is it? Do we really always need to know the inner class structure for comparations of a.constructor.name
with a string? Nooooo, because in Javascript you can literally do everything (you just need to know how, not why), and I found this final solution:
function Test(x){
function Test(y){
var variable = y;
this.getA = function(){
return variable;
}
this.setA = function(x){
variable = x;
}
}
return new Test(x);
}
function TestToo(x){
function TestToo(y){
var variable = y;
this.getA = function(){
return variable;
}
this.setA = function(x){
variable = x;
}
}
return new TestToo(x);
}
var a = Test("foo");
var b = Test("baz");
var c = TestToo("foo");
var d = TestToo("baz");
alert(a.constructor == b.constructor); //true, as expected
alert(a.constructor.name == b.constructor.name); //true, as expected
alert(c.constructor == d.constructor); //true, as expected
alert(c.constructor.name == d.constructor.name); //true, as expected
alert(a.constructor == c.constructor); //false, as expected
alert(a.constructor.name == c.constructor.name); //false, q.e.d.!
I'm serious, I don't know why this works. But it works 100% for sure, with 100% object encapsulation, and 1:1 equality to Java classes. ;-)
Upvotes: 0
Reputation: 94121
If so, is there a good way to avoid this while still keeping the benefits of this pattern...
Try using a module approach instead:
var Test = (function TestModule() {
function Test(a) {
this.a = a;
}
Test.prototype = {
};
return function(a) {
return new Test(a);
};
}());
var a = Test('foo');
var b = Test('baz');
a.constructor == b.constructor; // true
a.constructor.name == b.constructor.name; // true
Upvotes: 3