Lately I've been trying to make an object in JavaScript with the following structure:
function colorDiv(div){
this.div=div;
this.div.bind("click",this.changeColor)
this.changeColor(){
this.div.css("background", "#FF0000");
}
}
The problem is that the changeColor method cannot be called from the jQuery environment because this must refer to the current colorDiv object, so the bind method cannot work as expected.
How can this be solved?
There are a couple ways. The simplest is as follows:
function ColorDiv(div) {
var that = this;
that.div = div;
that.div.bind("click", that.changeColor);
that.changeColor = function () {
that.div.css("background", "#FF0000");
};
}
var colorDiv = new ColorDiv($("#my-div"));
$("#something-else").click(colorDiv.changeColor);
You save a reference to this in the variable that, which is just the name commonly used in the JavaScript world for exactly this purpose. Then you refer to that instead of this inside your changeColor method. (Note that I used that everywhere, just for consistency, even though the only place it actually makes a difference is inside the changeColor method.)
Another is to use the Function#bind method. You can either use it every time you call changeColor, like so:
var colorDiv = new ColorDiv($("#my-div"));
$("#something-else").click(colorDiv.changeColor.bind(colorDiv));
or you can use it in the ColorDiv class to ensure that all methods are bound correctly whenever they are called:
this.changeColor = (function () {
this.div.css("background", "#FF0000");
}).bind(this);
As noted in the linked article, Function#bind is not supported in all browsers, so you'll need a shim like the one they give, or possibly a full-fledged ES5 shim.
Underscore.js has a bindAll function that could be useful here, too, especially with multiple methods:
_.bindAll(this);
Finally, it's worth noting you don't really need to do any of this in your particular example: just do
this.changeColor = function () {
div.css("background", "#FF0000");
};
instead of what you have, i.e. reference the div variable passed in, instead of the reference stored in this.div, since they are the same thing.
Try setting as the first line of the method:
var self = this;
and then using self as needed.
this.div.bind("click",self.changeColor)
Related
I have a model js file which looks like
goog.provide('model.ErrorLogger');
/**
* #constructor
*/
model.ErrorLogger = function() {
window.onerror = goog.bind(this.errorHandler, this);
this.doInitialSend();
};
goog.addSingletonGetter(model.ErrorLogger);
model.ErrorLogger.prototype.ws_ErrLgr_config = true;
model.ErrorLogger.prototype.doInitialSend = function(){
if (this.ws_ErrLgr_config){
window.setInterval(this.sendReport, this.ws_ErrLgr_config);
}
};
model.ErrorLogger.prototype.sendReport = function(){
// the value of 'this' needs to be of the ErrorLogger model and not windows
if (!this.ws_ErrLgr_config || this.stopped) {
//some more code here
}
}
In the constructor I call the doInitialSend function which set the window.setInterval. Now in the sendReport function the the value of 'this' is not correct. How to correctly pass 'this' to get the correct value instead of getting window's this.
I tried storing the the value of this in a reference but that didn't work either. For example
var that = this;
window.setInterval(that.sendReport, that.ws_ErrLgr_config);
The idiomatic way to do this in Google Closure is using goog.bind, with the advantage that it's guaranteed it's always going to work. Plus it will use Function.prototype.bind() under the hood when that is available.
In that case the solution will be:
myIntervalInMilliseconds = 1000; // One second.
window.setInterval(goog.bind(this.sendReport, this), myIntervalInMilliseconds);
Using that = this works, but requires you to explicitly wrap your function within another one to capture that as this within the desired function.
It's much better using Function.prototype.bind() for that, as pointed out in the other answers. However this won't work if you care to support older browsers (IE < 9).
PS: Another issue in your code is that it is using this.ws_ErrLgr_config as the interval, which is set to true in the prototype. This is incorrect, you should pick a number to represent your interval.
Or this:
window.setInterval((function() {
this.sendReport();
}).bind(this), this.ws_ErrLgr_config.ReportInterval);
You can do this:
var that = this;
window.setInterval(function() {
that.sendReport()
}, this.ws_ErrLgr_config.ReportInterval);
That way you can call sendReport in the correct context, also this works:
window.setInterval(this.sendReport.bind(this), this.ws_ErrLgr_config.ReportInterval);
The reason why window.setInterval(that.sendReport, this.ws_ErrLgr_config.ReportInterval) doesn't work is because Javascript is pass-by-value. The above statement is the equivalent to:
window.setInterval(function(){
// the value of 'this' needs to be of the ErrorLogger model and not windows
if (!this.ws_ErrLgr_config || this.stopped) {
//some more code here
}
}, this.ws_ErrLgr_config.ReportInterval);
By using the .bind() keyword or wrapping it in another function and then refering to a thatfrom the outerscope, you can call the function in the desired scope.
I am writing my first jQuery plugin, and I'm not entirely sure what should be inside the extension declaration and what shouldn't.
$.fn.myplugin = function () {
var somevar = this;
someFunc(somevar);
};
function someFunc() {/*doSomethin'*/};
OR
$.fn.myplugin = function () {
var somevar = this;
someFunc(somevar);
function someFunc() {/*doSomethin'*/};
};
I'd use the first option because:
It doesn't have any negative side effects.
That's where you are wrong. If you are working with different libraries you risk overwriting someone elses someFunc(), possibly breaking whatever they were trying to do for you. A safer way would be to wrap a closure around your code.
(function(){
$.fn.myplugin = function () {
var somevar = this;
someFunc(somevar);
};
function someFunc() {/*doSomethin'*/};
/* Do whatever other things you need someFunc/myplugin for */
})();
This way your someFunc is shielded from the global namespace.
Alternatively what you might be looking to do is to expose the method of the object to the outside world. Take following example:
$.fn.dog = function () {
this.bark = function() {alert("Woof");};
return this;
};
var $max = new $('#myDogMax').dog();
$max.bark();
This keeps the function within the context of your object but makes it possible to access it cleanly from the outside. Although this usually means that the method is somehow related to the object. It would make little sense to write a bark() function globally, as it are usually dogs that tend do it and not browser windows.
In your first method, you end up polluting the global scope adding someFunc(). The second method doesn't.
However, that doesn't automatically mean the second method is better. If you enclose the first method in a closure, it basically ends up being the exact same thing, and comes down to personal preference.
Here, it might even be better to use the first method (in a closure), so that if you have multiple jQuery extensions in a single JS file, they can share this base function.
I've been working a lot lately on making cleaner Javascript code, and using objects with prototypes etc. But I'm confused on some points...
function TimeCard(){
this.date = new Date();
this.pay_period_begin = null;
this.pay_period_end = null;
}
Here is my timecard object with some initial values. I have a bunch of functions that I've written and more to come that are part of that timecard and, if I understand correctly, they will be prototype functions. Here is some of what I have so far:
TimeCard.prototype = {
init : function(){
this.pay_period_begin = $("#pay_period_begin");
this.pay_period_end = $("#pay_period_end");
},
getTimeCardData : function(){
//ajax request
},
selectAll : function(){
this.getTimeCardData();
}
...
};
My problem is that when I try to call this.getTimeCardData() it says that my object has no such method. I can obviously access the other variables because they are declared in my constructor, but I don't understand how prototype scopes I guess. So far I have gotten around this by using tc.getTimeCardData() instead of this.getTimeCardData(), with tc being the instance of my object declared outside - var tc = new TimeCard();. I'm sure that that's not the correct way to go about this, but what is?
My problem is that when I try to call this.getTimeCardData() it says that my object has no such method.
It sounds like this is no longer referring to your instance. You'll have to show us the actual call for us to be sure, but in JavaScript, this is set primarily by how a function is called, not where it's defined, and so it's fairly easy for this to end up being something different.
Here's a hypothetical example:
TimeCard.prototype = {
// ...
doSomething: function() {
// here, `this` probably refers to the timecard
someArray.forEach(function() {
this.getTimeCardData(); // <=== Problem, `this` has changed
});
}
// ...
};
If I call this.doSomething(); on a TimeCard object, within the call this will refer to the timecard. But within the forEach callback, this will no longer refer to the timecard. The same sort of thign happens with all kinds of callbacks; ajax, etc.
To work around it, you can remember this to a variable:
TimeCard.prototype = {
// ...
doSomething: function() {
var thisCard = this;
someArray.forEach(function() {
thisCard.getTimeCardData(); // <=== Problem
});
}
// ...
};
There are also various other ways to work around it, depending on your specific situation. For instance, you have selectAll calling getTimeCardData. But suppose selectAll is called with the wrong this value? In your comment, you said you were doing it like this:
$('#container').on('click', '#selectAll', tc.selectAll);
That means that when selectAll is called, this will refer to the DOM element, not to your object.
You have three options in that specific situation:
Since you're using jQuery, you can use $.proxy, which accepts a function and a value to use as this, and returns a new function that, when called, will call the original with this set to the desired value:
$('#container').on('click', '#selectAll', $.proxy(tc.selectAll, tc));
Use ES5's Function#bind, which does the same thing. Note that IE8 and earlier don't have it unless you include an "ES5 shim" (hence my noting $.proxy above; you know you have that):
$('#container').on('click', '#selectAll', tc.selectAll.bind(tc));
Use a closure (don't let the name bother you, closures are not complicated):
More (on my blog):
$('#container').on('click', '#selectAll', function() {
tc.selectAll();
});
In all of the above, you'll lose the benefit of this referring to the DOM element. In that particular case, you probably don't care, but if you did, you can get it from the event object's currentTarget property. For instance, this calls tc.selectAll with this referring to tc and passing in what would have been this (the DOM element you hooked the handler on) as the first argument:
$('#container').on('click', '#selectAll', function(e) {
tc.selectAll(e.currentTarget);
});
Mythical methods
You must remember this
Another, less likely, possibility relates to how you're updating TimeCard.prototype. The way you're doing it, it's possible to create objects via new TimeCard() before your code that replaces the TimeCard.prototype object runs, which means they'll have the old prototype.
In general, I strongly recommend not replacing the object automatically created for the prototype property of the constructor function. Instead, just add to the object already there, like this:
function TimeCard(){
this.date = new Date();
this.pay_period_begin = null;
this.pay_period_end = null;
}
TimeCard.prototype.getTimeCardData = function(){
//ajax request
};
// ...
Here's why: Timing. If you replace the object on the prototype property, any objects you create via new TimeCard() before you do that replacement will have the old prototype, not the new one.
I also recommend always creating these within a scoping function so you know that the declaration and the prototype additions happen at the same time:
var TimeCard = (function() {
function TimeCard(){
this.date = new Date();
this.pay_period_begin = null;
this.pay_period_end = null;
}
TimeCard.prototype.getTimeCardData = function(){
//ajax request
};
// ...
return TimeCard;
})();
...primarily because it prevents the timing issue.
I have the following JavaScript code:
function PatternField(id, name, pattern) {
...
this.check = function () {
var field = this.elem.val();
...
};
this.elem.keyup(this.check);
this.elem.change(this.check);
}
When the execution comes to check function var field = this.elem.val(); it turns out that this points to elem rather than actual object.
How can I access real this from inside this object function?
this.check = function() {
var field = this.elem.val();
...
}.bind(this);
The important part being bind(this) which controls the scope of the function once it is invoked/called (note that the function is not invoked immediately when using bind, you are manipulating the definition, if you will...); in this case, retaining the scope of PatternField. Check the docs regarding bind at MDN.
In other words (in regards to some comment that magically deleted itself):
It makes sure that the scope of this.check (when called) will be whatever is passed to the first parameter of bind, overriding whatever might naturally occur. If the you want this to reference PatternField within the this.check method, the bind method of Function will enable this capability.
Like #zamnuts answered, you can use the ES5 bind method.
But if you want to do it the old way, i.e., supporting old browsers without a polyfill, you can use:
var that = this;
this.check = function () {
var field = that.elem.val();
...
};
I have defined a class named MyClass and I have defined two methods myMethod1 and myMethod2 for it:
function MyClass() {}
MyClass.prototype.myMethod1 = function() {...};
MyClass.prototype.myMethod2 = function() {...};
Inside myMethod1, I use jQuery and there's a callback closure defined there:
MyClass.prototype.myMethod2 = function() {
$.jQuery({success: function(data) {
this.myMethod2();
}, ...});
}
Now the problem is that this no longer is referring to MyClass. The question is how can I refer to it? At the moment I have assigned it to a variable named thisObj and access it this way:
MyClass.prototype.myMethod2 = function() {
var thisObj = this;
$.jQuery({success: function(data) {
thisObj.myMethod2();
}, ...});
}
Is there a better way to access MyClass.this from the closure nested in myMethod2?
Thanks in advance.
The method you've used is often called the "that reference", because the name that is commonly used as a name for the copy of the this reference. See Crockford's talks on JavaScript for example.
Your solution is perfectly fine. Since you already have a closure there, may as well make use of it, that's absolutely fine.
But if you like, you can use jQuery.proxy instead, like this:
MyClass.prototype.myMethod2 = function() {
$.jQuery({success: jQuery.proxy(function(data) {
this.myMethod2();
}, this), ...});
}
Again, though, there's nothing wrong with your original solution. Using proxy can be helpful, though, when you want to reuse a function in lots of different places, or when you don't already have a closure and don't want to introduce one (perhaps because it would close over a lot of unrelated stuff). The good thing about proxy is that it creates a closure over a controlled set of stuff and not over the current scope.
You can pass a reference to this to the function, but your solution seems fine to me. You are just using the lexical scoping rules of Javascript so what is wrong?