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();
...
};
Related
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 know how to use the methods for changing the context of a function/method call: apply, bind, call. My question is this, is there a way to check if a function/method has already had its context set?
// given
var beta;
function alpha () {
return this;
}
function isContextSet (fn) {
/* check for context binding on fn */
}
beta = alpha.bind("hello");
isContextSet(alpha); // returns false
isContextSet(beta); // returns true
I think I know the answer to this already but thought I would ask anyway, if for no other reasons than: 1. confirm my assumption, or 2. learn something. I'm sure that I am not the first to ask this question but I have had no idea on how to actually find an answer since all I get are responses to how to use: .apply(), .call(), or .bind().
No.
Actually, it's incorrect to refer to binding as "changing" the context. What it does is transform one function into another one which calls the first with a particular context.
Functions can be bound in many ways. For instance, I could bind it myself:
function bind(fn,ctxt){
return function(){
fn.apply(ctxt,arguments);
};
}
Or, I could bind it with Function.prototype.bind, or a shim that does the same thing, or underscore's _.bind. I could bind it to initial arguments in addition to the context. In each case, there's no way to tell what has happened without essentially actually executing the function.
Binding is just one of many ways to transform one function into another one. I could also transform a function to execute one second from now, or be executed twice. There is nothing magic about binding; it just creates a new function based on the old one. There is no special variable called [[bound_to]] set on the function that you might be able to examine (unless you use your own custom implementation of bind, as another answer suggests). There's no post facto way to determine how the function was transformed.
The closest I can imagine coming is checking the possibly bound function against an unbound version and see if they are equal.
function log_this(){console.log(this);}
var bound_func=log_this.bind(my_var);
console.log(log_this===bound_func); // FALSE
What could be done is something like this:
function f(context, whatever) {
if(this !== context) {
//context has been changed
}
}
var y = f.bind(thingy);
y(otherThingy, something);
You could also have a custom bind function and have a sort of a hack like this:
function bind(func, context) {
var newFunc = func.bind(context);
newFunc._this = context;
return newFunc;
}
function checkContext(func, context) {
return func._this !== context;
}
I think this is interesting question for those how love JavaScirpt and I hope this is response what you are looking for:
var isContextSet = function (fn) {
var testThis = {}; //unique object for testing what is this inside fn
return fn.call(testThis) !== testThis;
}
I try to "set" this inside fn: fn.call(testThis); and if that function returned reference to newly created object (testThis) then it is not binded. I hope you get it :)
EDIT:
This works when fn retuns this (as shown in question). Otherwise you can not define isContextSet properly.
I have this code:
PageList: function(url, index, classes){
this.url = url;
this.index = index;
...
};
PageList.prototype.toHTML = function(){
var div = $('<div class="container"></div>');
var p = $('<p></p>');
var link = $('<a></a>');
$.each(this.elements, function(index, array_value){
console.log(this.url);
...
}
}
And it worked as expected.
The problem was that console.log(this.url) was printing undefined, so I reworked the code to look like this:
PageList.prototype.toHTML = function(){
var div = $('<div class="container"></div>');
var p = $('<p></p>');
var link = $('<a></a>');
var instance = this;
$.each(this.elements, function(index, array_value){
console.log(instance.url);
}
}
I know that the problem was on the closure not taking this as the value of the instance, but as far as i know a reference to this inside a function that doesn't have an instance bound to it must refer to the window object, instead of undefined, at least that's the case on many of the browsers out there.
So what exactly is going on on my code.
Note: I'm using jQuery and this.elements is already defined.
Edit: Now im figuring out that $.each is a non-instance function, so my callback is being called from $.each but it must be window the reference to this, still thinking about it.
According to the jQuery docs for $.each:
The value [of the current element] can also be accessed through the this keyword...
In JavaScript, when you hand off a callback function to a higher-order function (in this case, $.each), the higher-order function can decide what the value of this will be when the callback runs. There is no way for you to control this behavior -- simply don't use this (e.g., by using a reference like instance in your example or via a closure).
Check out the context-setting functions Function.call and Function.apply to understand how a higher-order function like $.each sets the this context of a callback. Once you read those MDN pages, it might clear a few things up.
Here's a quick example:
Array.prototype.forEachWithContext(callback, this_in_callback) {
for(var i = 0; i < this.length; ++i) {
callback.call(this_in_callback, i, this[i]);
}
}
And to use it:
PageList.prototype.toHTML = function(){
//...
this.elements.forEachWithCallback(function(index, array_value){ ... }, this);
}
My example Array.forEachWithContext is similar to Array.forEach. However, it takes a callback and a second argument that is used as the value of this during the execution each of those callbacks.
Try wrapping your $.each function with a $.proxy like this...
$.each(this.elements, $.proxy(function(index, array_value){
console.log(this.url);
},this));
The $.proxy will ensure that this references your PageList...
I know that the problem was on the closure not taking this as the value of the instance, but as far as i know a reference to this inside a function that doesn't have an instance bound to it must refer to the window object, instead of undefined, at least that's the case on many of the browsers out there.
this is window. You're printing window.url, which is undefined. Try console.log(this), and it should yield window.
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)
Using instance methods as callbacks for event handlers changes the scope of this from "My instance" to "Whatever just called the callback". So my code looks like this
function MyObject() {
this.doSomething = function() {
...
}
var self = this
$('#foobar').bind('click', function(){
self.doSomethng()
// this.doSomething() would not work here
})
}
It works, but is that the best way to do it? It looks strange to me.
This question is not specific to jQuery, but specific to JavaScript in general. The core problem is how to "channel" a variable in embedded functions. This is the example:
var abc = 1; // we want to use this variable in embedded functions
function xyz(){
console.log(abc); // it is available here!
function qwe(){
console.log(abc); // it is available here too!
}
...
};
This technique relies on using a closure. But it doesn't work with this because this is a pseudo variable that may change from scope to scope dynamically:
// we want to use "this" variable in embedded functions
function xyz(){
// "this" is different here!
console.log(this); // not what we wanted!
function qwe(){
// "this" is different here too!
console.log(this); // not what we wanted!
}
...
};
What can we do? Assign it to some variable and use it through the alias:
var abc = this; // we want to use this variable in embedded functions
function xyz(){
// "this" is different here! --- but we don't care!
console.log(abc); // now it is the right object!
function qwe(){
// "this" is different here too! --- but we don't care!
console.log(abc); // it is the right object here too!
}
...
};
this is not unique in this respect: arguments is the other pseudo variable that should be treated the same way — by aliasing.
Yeah, this appears to be a common standard. Some coders use self, others use me. It's used as a reference back to the "real" object as opposed to the event.
It's something that took me a little while to really get, it does look odd at first.
I usually do this right at the top of my object (excuse my demo code - it's more conceptual than anything else and isn't a lesson on excellent coding technique):
function MyObject(){
var me = this;
//Events
Click = onClick; //Allows user to override onClick event with their own
//Event Handlers
onClick = function(args){
me.MyProperty = args; //Reference me, referencing this refers to onClick
...
//Do other stuff
}
}
If you are doing ES2015 or doing type script and ES5 then you can use arrow functions in your code and you don't face that error and this refers to your desired scope in your instance.
this.name = 'test'
myObject.doSomething(data => {
console.log(this.name) // this should print out 'test'
});
As an explanation: In ES2015 arrow functions capture this from their defining scope. Normal function definitions don't do that.
var functionX = function() {
var self = this;
var functionY = function(y) {
// If we call "this" in here, we get a reference to functionY,
// but if we call "self" (defined earlier), we get a reference to function X.
}
}
edit: in spite of, nested functions within an object takes on the global window object rather than the surrounding object.
One solution to this is to bind all your callback to your object with javascript's bind method.
You can do this with a named method,
function MyNamedMethod() {
// You can now call methods on "this" here
}
doCallBack(MyNamedMethod.bind(this));
Or with an anonymous callback
doCallBack(function () {
// You can now call methods on "this" here
}.bind(this));
Doing these instead of resorting to var self = this shows you understand how the binding of this behaves in javascript and doesn't rely on a closure reference.
Also, the fat arrow operator in ES6 basically is the same a calling .bind(this) on an anonymous function:
doCallback( () => {
// You can reference "this" here now
});
I haven't used jQuery, but in a library like Prototype you can bind functions to a specific scope. So with that in mind your code would look like this:
$('#foobar').ready('click', this.doSomething.bind(this));
The bind method returns a new function that calls the original method with the scope you have specified.
Just adding to this that in ES6 because of arrow functions you shouldn't need to do this because they capture the this value.
I think it actually depends on what are you going to do inside your doSomething function. If you are going to access MyObject properties using this keyword then you have to use that. But I think that the following code fragment will also work if you are not doing any special things using object(MyObject) properties.
function doSomething(){
.........
}
$("#foobar").ready('click', function(){
});