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.
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.
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.
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.
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.
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
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.
Related
Hello I am having an issue with self invoking a function that belongs to an object's prototype. My goal is to create a method for a custom object that invokes a function it has upon creating an instance.
function Message(text) {
this.text = text;
}
Message.prototype.send = function() {
alert(this.text);
}();
var testMessage = new Message("Hello Goodbye");
The issue here is I receive undefined when I create an instance of the Message object. It may have to do with the way the keyword, "this" is defined as, although I am not completely sure. However if instead of adding the method to the prototype and just put it in the function, it works fine like the one below.
function Message(text) {
this.text = text;
this.send = function() {
alert(text);
}();
}
var testMessage = new Message("Hello Goodbye");
The send method runs exactly when you invoke it with () - examine your control flow.
In your first case, you define a function on Message's prototype attribute, then invoke it. It alerts undefined, because Message.prototype (this in that context) does not contain a text attribute.
In your second case you add text to this before invoking send, thus making it defined when the alert runs.
While this issue occurred to me specifically with KnockoutJS, my question is more like a general javascript question.
It is good to understand however that ko.observable() and ko.observableArray() return a method so when assigning a value to them, you need to call the target as method instead of simply assigning a value to them. The code that I'm working with should also support plain objects and arrays, which I why I need to resolve to a method to call to assign a value to the target.
Think of these 2 examples:
Non-working one (this context changed in called method):
// Assigning value to the target object
var target;
// target can be of any of thr following types
target = ko.observableArray(); // knockout observable array (function)
// target = ko.observable(); // knockout observable (function)
// target = {}; // generic object
// target = []; // generic array
//#region resolve method to call
var method;
if (isObservable(target)) {
// if it is a knockout observable array, we need to call the target's push method
// if it is a konckout observable, we need to call the target as a method
method = target.push || target;
} else {
// if target is a generic array, we need to use the array's push prototype
// if target is a generic object, we need to wrap a function to assign the value
method = target.push || function(item){ target = item; };
}
//#endregion
// call resolved method
method(entity);
Working one (this context is fine):
if (isObservable(target)) {
if (target.push) {
target.push(entity);
} else {
target(entity);
};
} else {
if (target.push) {
target.push(entity);
} else {
target = entity;
};
}
Now, to the actual question:
In the first approach, later in the execution chain when using a knockout observable knockout refers to this context within itself, trying to access the observable itself (namely this.t() in case someone is wondering). In this particular case due to the way of callin, this has changed to window object instead of pointing to the original observable.
In the latter case, knockout's this context is just normal.
Can any of you javascript gurus tell me how on earth my way of calling can change the 'this' context of the function being called?
Ok, I know someone wants a fiddle so here goes :)
Method 1 (Uncaught TypeError: Object [object global] has no method 'peek')
Method 2 (Works fine)
P.S. I'm not trying to fix the code, I'm trying to understand why my code changes the this context.
UPDATE:
Thanks for the quick answers! I must say I hate it when I don't know why (and especially how) something is happening. From your answers I fiddled up this quick fiddle to repro the situation and I think I got it now :)
// So having an object like Foo
function Foo() {
this.dirThis = function () {
console.dir(this);
};
};
// Instantiating a new Foo
var foo = new Foo();
// Foo.dirThis() has it's original context
foo.dirThis(); // First log in console (Foo)
// The calling code is in Window context
console.dir(this); // Second log in console (Window)
// Passing a reference to the target function from another context
// changes the target function's context
var anotherFoo = foo.dirThis;
// So, when being called through anotherFoo,
// Window object gets logged
// instead of Foo's original context
anotherFoo(); // 3rd log
// So, to elaborate, if I create a type AnotherFoo
function AnotherFoo(dirThis){
this.dirThis = dirThis;
}
// And and instantiate it
var newFoo = new AnotherFoo(foo.dirThis);
newFoo.dirThis(); // Should dir AnotherFoo (4th in log)
If you're after a way to choose the 'this' that will get used at the time of call,
you should use bind, that's exactly done for that.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
So if SomeObject has a push method, then storing it like this won't work :
var thePushMethod = someObject.push;
since you loose the context of the function when writing this.
Now if you do :
var thePushMethod = someObject.push.bind(someObject);
the context is now stored inside thePushMethod, that you just call with
thePushMethod();
Notice that you can bind also the arguments, so for instance you might write :
var pushOneLater = someObject.push.bind(someObject, 1 );
// then, later :
pushOneLater(); // will push one into someObject
Consider this example,
function Person () {
this.fname = "Welcome";
this.myFunc = function() {
return this.fname;
}
};
var a = new Person();
console.log(a.myFunc());
var b = a.myFunc;
console.log(b());
Output
Welcome
undefined
When you make a call to a.myFunc(), the current object (this) is set as a. So, the first example works fine.
But in the second case, var b = a.myFunc; you are getting only the reference to the function and when you are calling it, you are not invoking on any specific object, so the window object is assigned. Thats why it prints undefined.
To fix this problem, you can explicitly pass the this argument with call function, like this
console.log(b.call(a));
So, for your case, you might have to do this
method.call(target, entity);
Check the fixed fiddle
I have a difficulty in understanding, how my current JavaScript code works. I've managed to solve a problem in accessing private object method from event handler closure, but I'd like to know why does it work so.
The code utilizes the well-known module/plugin metaphor:
(function(module, $, undefined)
{
function myPrivateCode(e){ /*...*/ }
module.myPublicCode = function(e) { /*...*/ }
module.init = function()
{
var that = this;
$('.clickable').click(function(e)
{
if($(e.target).hasClass('classX'))
{
that.myPublicCode(e.target); // requires 'that' to work
}
else
{
// that.
myPrivateCode(e.target); // will fail if 'that' uncommented
}
});
}
}(window.module = window.module || {}, jQuery ));
In the code I set a click handler which invokes either public or private method. It's perfectly conceivable that we need to pass an object reference into the event handler closure, which is done by that local variable. What is strange to me is that myPrivateCode does neither require that as a refernce, nor fails due to its "privacy". This makes me think that myPrivateCode accesses not the appropriate object, and works somehow differently to expected way. Could someone explain what happens? Certainly I'm missing something.
Both that and myPrivateCode are available to your event handler through a closure. In short, what's going on is that every variable and function you declare inside of another function has access to the outer scope.
myPublicCode, on the other hand, is not available through closures, because it's being assigned to your module object specifically. So the only way to call it is by using module.myPublicCode() (or that.myPublicCode() as you did – but you don't actually need that there, since module is also available).
Your call to myPrivateCode(e.target); is running in the context of the anonymous function that you pass as a handler to the click function.
For more information, read up on closures.
For a simpler example, try out this code:
var foo = function () {
var a = 1;
return function (b) {
return a+b;
}
};
var bar = foo();
bar(1); // 2
bar(1) will always always gives 2, because a = 1 was in scope when the function was created. In your case, a is your that and your handler is the closed function.
http://jsfiddle.net/Fh8d3/
If I have a JavaScript constructor function, and I set a destroy method on its prototype. Is it possible to delete (or at least unset) the instance from the destroy method? Here's an example of what I'm trying to do.
Klass.prototype = {
init: function() {
// do stuff
},
destroy: function() {
// delete the instance
}
};
k = new Klass
k.destroy()
console.log(k) // I want this to be undefined
I understand that I can't simply do this = undefined from with the destroy method, but I thought I could get around that by using a timeout like so:
destroy: function() {
var self = this;
setTimeout( function() {
self = undefined
}, 0)
}
I thought the timeout function would have access to the instance via self from the closure (and it does), but that doesn't seem to work. If I console.log(self) from inside that function it shows up as undefined, but k in the global scope is still an instance of Klass.
Does anyone know how to make this work?
k is a reference that points out to an instance of Klass. when you call destroy as a method of Klass the this inside the function gets bound to the object you called a destroy method on. It now is another reference to that instance of Klass. The self that you close on in that little closure is yet another reference to that instance. When you set it to undefined you clear that reference, not the instance behind it. You can't really destroy that instance per se. You can forget about it (set all the references to undefined and you won't find it again) but that is as far as you can go.
That said, tell us what you want to accomplish with this and we'll be glad to help you find a solution.
Though deleting its own object instance is possible, it is very tacky. You might want to check out this article.
What you are trying to do is impossible. Even if you overwrite this it will not affect any variable holding a reference to that instance. And calling a function on it will still have the correct this.
The only thing you could do is setting a variable in your destroy function that will make any other function throw an exception when called. But that would be a bad idea since it would slow things down (ok, that's negligible) and you can just put in the docs of your class that it is not supposed to be used anymore after destroy() has been called.
This works, but it requires that you know what the name of variable to which the new Klass() is instantiated:
function Klass() {};
Klass.prototype = {
init: function() {
//
},
destroy: function() {
// Delete the variable that references the instance of the constructor.
delete window.k;
}
};
k = new Klass();
k.destroy();
console.log(k);
If the k variable is named anything else it doesn't work:
// Won't work.
u = new Klass();
u.destroy();
It also assumes that the local variable k is really in the global scope. If it were inside a function it would not work either. For instance:
// Won't work.
var fn = function() {
k = new Klass();
k.destroy();
};
Finally it assumes a browser environment for the window object.
I'm building a fairly complex web app that begins with a main menu where the user makes his initial selections. This is the first time I've tried a true OOP approach using inheritance in JavaScript and I've run into my first problem with the "this" keyword not referring to what I expect it to. I'm guessing that it's the result of a broader problem with my OOP/inheritance approach, so I would appreciate an answer that not only tells me how to solve this individual issue, but also provides deeper feedback and advice on my general approach.
I'm only going to post the JS code because I don't think the HTML is relevant, but I can certainly post that as well if necessary.
The following code defines the main class Select. It then creates a subclass of Select called SelectNum (look towards the end of the code). In SelectNum, I'm trying to override the mouseover method of Select, but not entirely -- I want to first call the super's (Select's) method, and then run some additional code. But when this subclass's mouseover method runs, I immediately get the following error:
"Uncaught TypeError: Cannot call method 'stop' of undefined"
Basically, this.shine is undefined.
To start with, I'm using the following code from O'Reilly's JavaScript: The Definitive Guide:
function inherit(p) {
if (Object.create){ // If Object.create() is defined...
return Object.create(p); // then just use it.
}
function f() {}; // Define a dummy constructor function.
f.prototype = p; // Set its prototype property to p.
return new f(); // Use f() to create an "heir" of p.
}
And my code:
Select = function(el){
return this.init(el);
}
Select.prototype = {
init: function(el){
var that = this;
this.el = el;
this.shine = el.children('.selectShine');
el.hover(function(){
that.mouseover();
},function(){
that.mouseout();
});
return this;
},
mouseover: function(){
this.shine.stop().animate({opacity:.35},200);
},
mouseout: function(){
var that = this;
this.shine.stop().animate({opacity:.25},200);
}
}
//Sub-classes
SelectNum = function(el){
this.init(el);
this.sup = inherit(Select.prototype); //allows access to super's original methods even when overwritten in this subclass
return this;
}
SelectNum.prototype = inherit(Select.prototype);
SelectNum.prototype.mouseover = function(){
this.sup.mouseover(); //call super's method... but this breaks down
//do some other stuff
}
EDIT
The response from Raynos worked. this.sup.mouseover() no longer threw the error, and the correct code was run. However, I actually need to create a SelectNum subclass called SelectLevel. Unlike SelectNum that overrides its superclass' mouseover() method, SelectLevel does NOT need to override SelectNum's mouseover() method:
SelectLevel = function(el){
this.init(el);
this.sup = inherit(SelectNum.prototype); //allows access to super's original methods even when overwritten in this subclass
for(var k in this.sup){
this.sup[k] = this.sup[k].bind(this);
}
}
SelectLevel.prototype = inherit(SelectNum.prototype);
With this code, the mouseover() method simply gets called continuously. I believe that's because this is now bound to the SelectLevel object, so this.sup in the line this.sup.mouseover() in SelectNum always refers to SelectNum, so it just keeps calling itself.
If I remove the this.sup[k] = this.sup[k].bind(this); binding in SelectLevel, then I get the error Uncaught TypeError: Cannot call method 'mouseover' of undefined. It appears that this.sup.mouseover() gets called continuously, calling the mouseover method on each object in the prototype chain. When it gets up to Object, that's when this error gets thrown because, of course, Object doesn't have a sup property.
It seems like I can solve this by removing the this.sup[k] = this.sup[k].bind(this); binding in SelectLevel, and then wrapping the this.sup.mouseover() in an if statement that checks first for the sup property before calling the mouseover() method on it: i.e. if(this.sup !== undefined), but this really just doesn't feel right.
Ultimately, I think I'm missing something fundamental about how to subclass in JavaScript. While solutions to these particular issues do shed some light on how prototypal inheritance works in JS, I really think I need a better understanding on a broader level.
this.sup.mouseover();
calls the .mouseover method on the object this.sup. What you want is
this.sup.mouseover.call(this)
You don't want to call it on this.sup you want to call it on this.
If that's a pain in the ass then you can do the following in your constructor
this.sup = inherit(Select.prototype);
for (var k in this.sup) {
if (typeof this.sup[k] === "function") {
this.sup[k] = this.sup[k].bind(this);
}
}
That basically means override every method with the same function but hard bind the value of this to what you expect/want.