Reputation: 12037
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
EDIT: using $elect(id).toggle();
to call it. Though you can check the demos.
Upvotes: 1
Views: 126
Reputation: 12985
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.
Upvotes: 0
Reputation: 12431
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;
Upvotes: 0
Reputation: 665485
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.
Upvotes: 1
Reputation: 740
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.
Upvotes: 0
Reputation: 1386
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.
Upvotes: 1
Reputation: 134066
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:
$elect.prototype
this
and call the method again.On the second pass, the this === window
evaluates to false
.
Upvotes: 0