3

I have a rather academic question that doesn't particularly apply to anything I'm doing, I just really want to know the answer!

Say we have a simple object definition in the global namespace as such:

TestObject = function(){};

It has a method added to it's prototype that can be instantiated into a new object itself:

TestObject.prototype.AnotherObject = function() {};

Instantiate the first object:

var myObject = new TestObject();

Now my question is this:

How does

myObject.myProperty = new myObject.AnotherObject();

differ to

myObject.myProperty = new TestObject.prototype.AnotherObject();

Or are they exactly the same?

The difference I see is this: I could use the second method to instantiate objects within the TestObject context without knowing the name of the instantiated object itself, i.e.

TestObject.prototype.createAnObject = function() {
    this.anotherProperty = new TestObject.prototype.AnotherObject();
}

And finally:

What are the implications of using a prototype method to instantiate an object of the same name? Why do this result in an infinite loop? (What actually happens inside..)

TestObject.prototype.AnotherObject = function () {
    this.AnotherObject = new TestObject.prototype.AnotherObject();
};
myObject.AnotherObject();

Yet this does not...

TestObject.AnotherObject = function() {};

TestObject.prototype.createAnObject = function() {
    this.AnotherObject = new TestObject.prototype.AnotherObject();
};
myObject.createAnObject();

...

I have a deep desire to understand the relationships between objects here! Thank you!

The reason I ask these questions is because I want to make something like so where there is a 1:1 relationship between objects:

ClientObject = function () {
    this.objectname = "a client class";
}

ClientObject.prototype.loginUser = function(name) {
    this.loggedin = true;
    if (typeof this.User === 'undefined') {
        this.User = new ClientObject.User(name);
    }
}
ClientObject.User = function (name) {
    this.username = name;
}

ClientObject.User.prototype.getProfile = function() {
    return 'user profile';
}

var testClient = new ClientObject();

console.log('testClient.User = ' + (typeof testClient.User)); // should not exist
testClient.loginUser('Bob'); // should login 'bob'
console.log('testClient.User = ' + (typeof testClient.User)); // should exist now Bob is logged in
console.log(testClient.User.username); // should be bob
testClient.loginUser('Tom'); // should not do anything as User object already created
console.log(testClient.User.username); // bob still
console.log(testClient.User.getProfile()); // new functionality available

I am just not sure if I'm breaking any best practises or conventions here unwittingly.

Ross
  • 472
  • 1
  • 4
  • 11

1 Answers1

4

myObject.myProperty = new myObject.AnotherObject();

differ to

myObject.myProperty = new TestObject.prototype.AnotherObject();

There's no difference at all. Remember, objects in JavaScript have a prototype chain. When you call new myObject.AnotherObject(); the engine will first check for a AnotherObject on myObject itself. Failing to find it, it will check on myObject's prototype, which it will find. The second version

myObject.myProperty = new TestObject.prototype.AnotherObject();

Just goes right to the place where AnotherObject is defined.


TestObject.prototype.AnotherObject = function () {
    this.AnotherObject = new TestObject.prototype.AnotherObject();
}
myObject.AnotherObject();

Just walk through the code. When you say: myObject.AnotherObject();, AnotherObject will be called, with this set to myObject. The first line of that will attempt to create a new property on myObject (which is this) by setting it to the result of

new TestObject.prototype.AnotherObject(); 

which will then re-enter the very same AnotherObject function, but this time with this set to a new object whose prototype is set to TestObject.prototype.AnotherObject's prototype. And so on ad infinitum


Finally,

TestObject.prototype.createAnObject = function() {
    this.AnotherObject = new TestObject.prototype.AnotherObject();
}
myObject.createAnObject();

Will not cause an infinite loop, so far as I can tell, and as far as I can test: FIDDLE

Basically, createAnObject will enter with this set to myObject. Inside of which a brand new property called AnotherObject will be created on myObject, which will be set to a new invocation of the AnotherObject function you previously set up.

Note that after this call is made, the AnotherObject function will still exist, but, it will be shadowed by the AnotherObject property you just created. So now you'll never ever be able to say

var f = new myObject.AnotherObject()

Because you now have a AnotherObject property sitting right on myObject, which will be found and returned before anything on the prototype is ever checked.

Well, I mean, you could always say delete myObject.AnotherObject and remove that property from the object, which would then open you up to the AnotherObject being found on the prototype, but really, you should avoid name conflicts like this to begin with.


Regarding your last bit of code

A) Why not make User its own function?
B) Why not set up this.User = new ...() right in the ClientObject constructor function? That way you wouldn't need the undefined check C) ClientObject should be defined as

function ClientObject(){...` 

the you have it now seems to be creating an implicit global.

Adam Rackis
  • 82,527
  • 56
  • 270
  • 393
  • I appreciate the response so far, I guess the setup was to find out whether my method of referencing objects within objects using the prototype definition is valid. – Ross Jun 03 '13 at 02:24
  • It's valid more of less, but you're doing some strange things that would make debugging a nightmare - like the last part of my answer, coming shortly. – Adam Rackis Jun 03 '13 at 02:29
  • The reason I have structured the last bit of code as displayed is because I want to have a bunch of self contained objects. For example, if I am writing an API to interact with a whole bunch of backend services, I'd like to split them into modules in the same context namespace and store state inside a main object. It does not make sense for a user object to exist before one is "logged in", as certain functionality should not be available unless a user is logged in. – Ross Jun 03 '13 at 02:46
  • @Ross - the revealing module pattern might help: http://stackoverflow.com/questions/5647258/how-to-use-revealing-module-pattern-in-javascript – Adam Rackis Jun 03 '13 at 02:49
  • Its time for you to consider using [require.js](http://requirejs.org/) for modularity and other MVC frameworks like [backbone.js](http://backbonejs.org/), [canjs](http://canjs.com/), etc... Good luck! – ManKum Jun 03 '13 at 04:18
  • @ManKum : require.js is great when building an app but I have found it very confusing when building an API/library that will be consumed by a javascript application. There is no documentation on how to configure it as such, but I do use require.js for my test-suite and expose my library with a define regardless. – Ross Jun 03 '13 at 04:42
  • One final note, my intention in the last bit is to create an implicit global. It would be a library that is pulled into a javascript app to expose an object at the global level which houses all the functionality to interact with backend web services. For example: var myClient = new ClientObject(); myClient.loginUser(userid); myClient.User.getProfile(); – Ross Jun 03 '13 at 05:19
  • Could anyone please reevaluate the last section of code? I've updated it to make my intentions clear. ClientObject would be my library, the code underneath is a consumer (a javascript web app for example). I want to make certain functionality available based on internal state, this seems like a good way to manage it. – Ross Jun 03 '13 at 06:09
  • A demonstration of the prototype chaining described here, that I think illustrates a lot about how prototypes work in Javascript: http://jsfiddle.net/b9chris/MWDrK/ – Chris Moschini Jun 03 '13 at 14:32