1

After coding in JS for a while I decided to make my own framework. Something similar to jQuery. But a very very stripped down version. After some googling I put together this code:

function $elect(id) {
    if (window === this) {
        return new $elect(id);
    }
    this.elm = document.getElementById(id);
}

$elect.prototype = {
    hide:   function () { this.elm.style.display = 'none';  },
    show:   function () { this.elm.style.display = '';      },
    toggle: function ()
            {
                if (this.elm.style.display !== 'none') {
                    this.elm.style.display = 'none';
                } else {
                    this.elm.style.display = '';
                }
            }
};

So far this seems to work. But I'm not interested in the functionality. I want to understand the logic. Adding methods part is understandable. Though I didn't understand the function of

    if (window === this) {
        return new $elect(id);
    }

If I remove it, function breaks. Since this is an if statement there are 2 results. True or false. So I tried to remove the if statement and just use return new $elect(id); assuming window === this returns true, but that didn't work. Then I thought it might return false, so removed the whole if statement. That also didn't work. Can someone enlighten me? Also is this code valid? I'm probably missing some stuff.

Just tested on jsfiddle and it doesn't work. Though it works on jsbin o.O

jsfiddle jsbin

EDIT: using $elect(id).toggle(); to call it. Though you can check the demos.

Community
  • 1
  • 1
akinuri
  • 10,690
  • 10
  • 65
  • 102
  • Where did you get that snippet from when you didn't understand it? – Bergi Sep 18 '13 at 16:14
  • Show how you call it. I think that is the clue I am missing. – Lee Meador Sep 18 '13 at 16:15
  • @Bergi somewhere in google? Should I post the url? – akinuri Sep 18 '13 at 16:16
  • Yes please. Not only because it's wrong, but also because the author might have some explanation. And of course for correct attribution. – Bergi Sep 18 '13 at 16:19
  • This doesn't work at all for me in Chrome 29. – Trendy Sep 18 '13 at 16:20
  • 1
    @Bergi [This](http://www.mikedoesweb.com/2012/creating-your-own-javascript-library/) was the website. – akinuri Sep 18 '13 at 16:21
  • All the functions need to `return this` so you can use them like this `$elect('theId').hide()` – Lee Meador Sep 18 '13 at 16:27
  • @LeeMeador Originally the constructor and the methods had `return this` but I removed them after seeing it still works whether `return this` is there or not. So should I put them back? Knowing that it still works without them, what do they really do? – akinuri Sep 18 '13 at 16:35
  • @akinuri: Thank you. Indeed, it is badly explained there (both what the problem is and how the solution works) – Bergi Sep 18 '13 at 16:38
  • The functions will work without returning `this`, but you will not be able to chain them together as @LeeMeador described in the above comment. The constructor doesn't need to return `this` as it already does so implicitly when called as as a constructor and, in the case of calling as a function it is explicitly returning the result of calling as a constructor. – dc5 Sep 18 '13 at 16:45
  • @dc5 Oh. Just tested `$elect(id).toggle().bgColor('red');` with `return this`, it works. I didn't think about chaining the methods, but this will help a lot! :) – akinuri Sep 18 '13 at 16:56

6 Answers6

1

I want to understand the logic.

$elect is a constructor function that is supposed to be called with the new keyword (new $select). If it is not ($elect()), what will happen then? There is no instance constructed, and the this keyword will point to the global object (window) - which we do not want. So this snippet is a guard against that occasion, when it detects that it invokes itself correctly with new and returns that.

If I remove it, function breaks

When you are calling it like $elect(id) without the guard, it will add a elm property to the global object (a global variable in essence) and return nothing. Trying to call the .toggle() method on that will yield the exception undefined has no method 'toggle'.

I tried to remove the if statement, assuming window === this returns true

Well, then you just created an infinite recursive function that will lead to a stack overflow exception when called.


Btw, the proper way to guard against new-less invocation is not to compare against window (the global object might have a different name in non-browser environments) and assume everything else to be OK, but to check for the correct inheritance. You want the constructor only to be applied on instances of your "class", i.e. objects that inherit from $elect.prototype. This is guaranteed when called with new for example. To do that check, you will use the instanceof operator:

function $elect(id) {
    if (! (this instanceof $elect)) {
        return new $elect(id);
    }
    this.elm = document.getElementById(id);
}

This also makes the intention of the guard explicit.

Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
1

To understand how that condition works, you must first know that in the global scope this refers to the window object. In local scope, such as within an object, this refers to the object itself.

Your $elect function is a constructor for your framework. If you call it directly, like so:

$elect('some-id-blah')

It will first realize that you are trying to create an instance (because the condition window === this will evaluate to true), and it will recursively create a new instance of itself. Once it does that, this will no longer refer to the window object, it will refer to the new instance of your library, thus the condition will not be met.

I hope that's somewhat understandable.

Mark
  • 1,376
  • 9
  • 16
  • I'm not sure what you mean, the code IS returning a new instance using `return new $elect(id);`. – Mark Sep 18 '13 at 16:36
  • Indeed, you are right. My mistake :) He should be returning `this` after setting `this.elm`. – Mark Sep 18 '13 at 16:51
  • @LeeMeador - there is no reason to return `this` from a constructor - it already does so implicitly. Using it this was works as expected without returning this: `(new $elect('textarea')).toggle();` – dc5 Sep 18 '13 at 16:59
  • 1
    Up in the top comments someone says this, being a constructor, will return 'this' anyway. This explains it [Mozilla.org](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new) – Lee Meador Sep 18 '13 at 16:59
  • 1
    I deleted all my comments that were incorrect so any future reader would not have to process them in their mind. Sorry. – Lee Meador Sep 18 '13 at 17:08
0

This logic:

if (window === this) {
    return new $elect(id);
}

Ensures that, if your constructor function is called as a function:

var foo = $elect(id);

rather than as a constructor:

var fo = new $elect(id);

it will return the correct result - a new $elect object. When called as a function, the default context will be the global context or window when running in the browser, triggering the if clause and returning the result of calling it as a constructor.


Why it isn't working in your fiddle

In the linked fiddle, it is setup to wrap your code in a onload handler. The result of which looks like this:

window.onload=function(){
function $elect(id) {
    if (window === this) {
        return new $elect(id);
    }
    this.elm = document.getElementById(id);
}

$elect.prototype = {
    hide:   function () { this.elm.style.display = 'none';  },
    show:   function () { this.elm.style.display = '';      },
    toggle: function ()
            {
                if (this.elm.style.display !== 'none') {
                    this.elm.style.display = 'none';
                } else {
                    this.elm.style.display = '';
                }
            }
};
}

Because of that, your $elect variable isn't visible outside of the onload handlers scope.

Generally, you would want to add one variable to the global scope to enable access to your framework. Something like this added at the end of your code above will make it visible:

 window.$elect = $elect;

Demo Fiddle

dc5
  • 12,341
  • 2
  • 35
  • 47
0

First thing to understand is that this function is calling itself:

function $elect(id) {
    if (window === this) {
        return new $elect(id);
    }
    this.elm = document.getElementById(id);
}

First time you call the function window is going to be === this. Because there you're invoking the function as a method. When invoked as a method, functions will be bound to the object the function/method is a part of. In this example, this will be bound to the window, the global scope.

But then with the new keyword, you're invoking the function as a constructor. When invoked as a constructor, a new Object will be created, and this will be bound to that object, so this no longer refers to window the second time around.

With the new instance of $elect, you're then also setting a local variable elm correctly to the instance, allowing your other functions to call on that elm later.

kenttam
  • 740
  • 4
  • 9
0

Your function is a constructor. By default the this in any function not called explicitly ($elect(), not foo.$elect()) on any other object as a method, will be passed the global object (the window) in this. However the if checks that if the this indeed refers to the window object, the return new $elect(id); is executed. The new $elect(id) does the following:

  • make a new instance of the $elect.prototype
  • put the newly created instance in the this and call the method again.

On the second pass, the this === window evaluates to false.

0

If you call it like this:

$elect("theId").hide();

The first time in it is called as a function and will find window === this and do this:

return new $elect("theId")

which creates a new one and call the function again. (Whatever the 2nd invocation of the function returns will get returned.)

The second time in, it is called as a constructor, so window !== this and it will pass down to find the element and store it internally.

Constructor calls that don't return anything from the function itself will return the instance created.

That way the .hide() part will get executed with the proper value of this (the instance created the first time in). And the element will disappear from the screen.

Note that if you call it like this:

var bob = {};
bob.$select("theId").hide();

It will not work because the this is set to bob which gets and 'elm' property set and there is no 'hide' property to be called because its not a $elect kind of object.

NOTE

If all the other methods in the other functions return this you will be able to chain them:

$elect('theId').show().red();

assuming you added a function to color the element red.

Lee Meador
  • 12,829
  • 2
  • 36
  • 42