Jump to content

JavaScript Brainteaser...


d13
 Share

Recommended Posts

Hello smart people!

 

I have a programming puzzle/question/observation that I'd love some comments on.

 

Here's a "class" that takes a function called setup as an argument. The quirky part is that setup references the object that the class is creating.

 

function Class(setup) {  this.test = "Hello!";   this.start = setup;}var instance = new Class(setup);instance.start();function setup() {  console.log(instance.test);}
 
This displays: "Hello!", as you would expect.
 
You can see that the the class creates a reference to the setup function called start. When an instance of the class is created, the instance calls the start method. The method displays the test property on the instance.  So that's all cool. 
 
But ... !
 
What if I want the instance to run the start method automatically when it's created?
I can try calling the start method inside the class's constructor, like this:
 
function Class(setup) {  this.test = "Hello!";   this.start = setup;  this.start(); //<= New!}var instance = new Class(setup);function setup() {  console.log(instance.test);}
 
Now when it runs, it throws an error saying instance is undefined. I know why: because the instance is being invoked before it's been created.
 
Here's my question:
 
Does anyone know if it's possible to wait until an object is instantiated, and then automatically run a method that references that same object?
 
Here's a JSBin for anyone who wants to test this out:
 
Link to comment
Share on other sites

<edit> It's possible per setTimeout but it's a dirty hack, so it's just for demonstration to show that the start function will work later</edit>

function Class(setup) {  this.test = "Hello!";   this.start = setup;  (function(that){    window.setTimeout(function(){that.start()},1)  })(this)}var instance = new Class(setup);//instance.start();function setup() {  console.log(instance.test);}

Solution: create an object inside the function:

function Class(setup) {  var obj = {}  obj.test = "Hello!";   obj.start = setup;  obj.start(); //<= New!  return obj}var instance = new Class(setup);function setup() {  console.log(this.test);} 

But here you need to write your setup function with this.

Link to comment
Share on other sites

Allow me to explain. When you create your Class function this.start couldn't possibly have a value since setup doesn't exist yet, so that would fail. Also if you ever created a Class without passing it a value it would fail, so doing the check on creation is the safe thing to do.

 

Also not that it mattered in your example, but in the future you would probably want to use a reference to this.test in the setup function instead of instance.test, so you could have multiple classes, multiple this.test values, but only need one setup function to display the information. 

 

Hope this helps.

Link to comment
Share on other sites

Thanks so much.. here are my observations!

 

window.setTimeout(function(){that.start()},1)

 

It works! It gives the instance just enough time, 1 millisecond, to instantiate the object before firing the start method.

 

obj.start();

 

I really like this solution, it seems robust and sensible.

 

if (this.start) this.start();

 

Wouldn't this.start always remain undefined when the constructor runs?

Ok, I read your post below, I've got it now!

The main point is that if "instance" is changed to "this" in the setup function the scope is correct.

I think that's the biggest lesson in all this, so thanks for pointing it out!

 

When you create your Class function this.start couldn't possibly have a value since setup doesn't exist yet

 

I believe that function declarations are evaluated at compile time before any other code tuns, so setup should already exist at the time that the class is instantiated. I tested this by using a different version of setup that doesn't reference instance, and it works: 

function setup() {console.log("It works!"}

you would probably want to use a reference to this.test in the setup function

 

Yes, that makes a lot of sense!

 

Thanks so much for your insights, ericbasti and Oatd, I'm really understanding this problem much better now.

Link to comment
Share on other sites

You do realize that you have zero reason to use a timeout for this.  Just doing this :

function Class(setup) {  this.test = "Hello!";   this.start = setup;  this.start();}var instance = new Class(setup);//instance.start();function setup() {  console.log(this.test);}

works. This avoids all of my little checks to make sure something is passed in (which you should be doing anyways, if your going to try running it on creation). By simply changing it to 'this.test' instead of 'instance.test' allows this to work. 

 

Anytime I see a timeout being used like this I cringe and have to say something, its a hack, not a solution. Eventually you'll run into a situation or a browser that takes longer than 1ms, and you'll have to make it 100ms, then 1000ms... 

Link to comment
Share on other sites

Yep, srry for the confusion.

I didn't mean setTimeout a real solution, I just wanted to show it as kind of "waiting" until theres nothing to do. It of course just works in cases where the function didn't effect the following code. I will edit my post to make it clear. Again srry

Link to comment
Share on other sites

What ericjbasti says is true about the timeout, plus it feels clunky.

 

If setup is purely used by the any instance of "Class", I'd put it inside as a prototype, so that it feels more OO.

 

function Class(name) {
  this.test = "Hello! "+name; 
  this.setup();
}
 
Class.prototype = {
  setup : function() {
    console.log(this.test);
  }
}
 
var instance = new Class("one");
var instance2 = new Class("two");
 
However, if setup is used elsewhere, then it has to stay outside of Class.
Link to comment
Share on other sites

window.setTimeout(function(){that.start()},1)

 

It works! It gives the instance just enough time, 1 millisecond, to instantiate the object before firing the start method.

 

Without saying that one method is better than another, I just wanted to note that even setTimeout with a delay of 0 milliseconds would have worked there, and there are no possible timing issues. All you want to do is execute a piece of code after the rest has been executed and an idle state has been reached, which is what a setTimeout(..., 0) would do. I'm not sure whether it's a hack in this particular case, but in lots of situations it's a perfectly legitimate solution

Link to comment
Share on other sites

Yeah, when I said it was a hack, it wasn't because it was setTimeout, it was because of how setTimeout was being used to do something that should never require a setTimeout. The issue wasn't with timing, the issue in this case was understanding how the scope and order of operations work. 

 

The script didn't fail because the function couldn't run, it failed because instance couldn't possibly exist before setup, yet setup requires instance to exists (thus the error saying instance is undefined).

Link to comment
Share on other sites

Thanks everyone!

 

> setTimeout

 

Yes, a kind-of-hack, but cool because we can learn from it.

It's science :)

 

> this.start() ... this.test();

 

This clarifies the scope, which I think is the most important key to understanding this problem.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...