I pulled the code below from this tutorial: tutorial
If you can bind this from a parent function to an anonymous inner function, then what do you do if you want to reference "this" in the anonymous inner function at some later time? Does "this" not exist for the anonymous inner function?
render: function ()
{
this.getAsyncData(function () {
this.specialFunction();
this.anotherSpecialFunction();
}.bind(this));
}
what do you do if you want to reference "this" in the anonymous inner function at some later time?
There is no way to refer to the value of this that the callback would have if we hadn't called .bind.
However, you don't have to use .bind, there are other ways to use the this value of the "parent" function: How to access the correct `this` context inside a callback?
Does "this" not exist for the anonymous inner function?
this is an implicit value in every function. Which value it has is determined by how the function is called, unless the function is explicitly bound to a certain value (via .bind).
For example, if you call a function "normally", such as func(), then this will refer to the global object (window in browsers). The API documentation usually explains which value this has inside a callback. If it doesn't mention anything explicitly, then you can assume that this refers to the global object, which is not very useful in most cases, and thus it is no problem to "override" it via .bind.
But even if this is set to a specific value in the callback, that value is often also passed as argument to the callback. Being able to access the value via this is just for convenience. But again, it depends on how the API is designed and is usually explained in the API documentation.
For more information about this, have a look at the MDN documentation.
The this context is set every time a function is called. If there is no context (as is the case with most callbacks), then this is lost.
It is more common to see:
var self = this;
// code using self instead of this here
The above is more browser-compatible due to .bind being relatively new.
When you have an anonymous callback, this usually refers to the window object. A good example is with setTimeout:
var someObject = {
set: function() { /* ... */ },
doSomething: function() {
// right here, "this" is "someObject"
setTimeout(function() {
// whereas right here, "this" is "window"
});
}
}
A lot of people solve this conflict like this:
var someObject = {
set: function() { /* ... */ },
doSomething: function() {
var that = this;
setTimeout(function() {
console.log(that) // => now it refers to "doSomething"
});
}
}
That's where bind comes into play.
See this jsfiddle:
var someObject = {
set: function() { /* ... */ },
doSomething: function() {
setTimeout(function() {
console.log(this) // => now it refers to "doSomething"
}.bind(this));
}
}
Edit
Felix Kling made a good comment on this answer. For this answer, I'm assuming you're calling someObject.doSomething(), and not calling the doSomething() method a different context.
If you can bind this from a parent function to an anonymous inner function, then what do you do if you want to reference "this" in the anonymous inner function at some later time?
You won't be able to, if you want to reference the outer this then set this to a variable and don't use bind.
var that = this;
Does "this" not exist for the anonymous inner function?
this always exists.
Simple example:
this.name = 'Bob';
function sayHello() {
return ['Hello,', this.name].join(' ');
}
console.log(sayHello()); // "Hello, Bob"
console.log(sayHello.bind({name: 'Ana'})()); // "Hello, Ana"
http://jsbin.com/xibis/1/edit
Related
There's a little something about scope I just keep getting confused about:
this.init = function(){
var t = this;
setTimeout(function(){
// why is t still available here?
t.initgame();
// but not this?
this.initgame();
}, 500);
}
this.initgame = function() {
// yada yada
}
I get that inside an anonymous function, scope is different that outside of it.
But why, in the above example, is the variable "t" available inside the timeout function, while "this" is not working?
The problem is that setTimeout is called with window as scope.
Using a dedicated variable to store this (t) is a perfectly valid and usual solution.
On modern browsers, bind is sometimes convenient :
setTimeout((function(){
// use this
}).bind(this), 500);
When the anonymous function runs, it is no longer running as a member function of init, but rather a top-level function of window. As a result, this.initgame() has no meaning.
For example, running console.log(this) inside the timeout function returns as follows:
Window {top: Window, window: Window, location: Location, external:...
When you use var t = this, you assign reference to the current object, which works.
I have a function localised to the main function and i want to use this to call it but it doesn't seem to work.
My code has:
function option(room,slot){
var div_id = document.getElementById(room);
var opacity = window.getComputedStyle(div_id).opacity
transition_opacity(div_id,opacity,0,function(){this.load});
function load(){
console.log('test'); //does not happen
}
}
Have i misunderstood the use of this or is the scope lost when i use function(){} to call load?
From your code it is not obvious, what object this could refer to. It depends on how option is called. However, if you define the load function inside of the option function anyway, it is best to just reference it directly. You will have to move the declaration of test above the transition_opacity call though:
function option(room,slot){
var div_id = document.getElementById(room);
var opacity = window.getComputedStyle(div_id).opacity;
function load() {
console.log('test');
}
transition_opacity(div_id,opacity,0,load);
}
As you can see, I just reference load directly. You could make another function which calls the load function inside (i.e. function() { load(); } – note the parentheses which calls the function) but that would give you no benefit but would just add another unneeded function to the stack. So just refer to the actual function itself.
For more information on the this keyword, check out this question. Spoiler: It’s more complicated than you would expect.
The scope of this is lost in this instance, probably pointing to the document. You can capture this to a variable in the outer scope to make this work as intended.
var context = this;
transition_opacity(div_id,opacity,0,function(){context.load();})
The above will not work however. This is because load does not exist on the context of this. You would need to define the load function as such:
context.load = function(){
console.log('test');
}
Both.
First, your load function is not a member/property of any this, the way you have it coded. Your load function is simply a nested function that exists within your option function, as has been sort of implicitly noted in other responses.
In your option function, if you want 'load' to become a member of 'this', you'd need to say so, like this:
function option(){
this.load = function(){}; // now load is actually a property of whatever this is
}
Second, you and the other poster are correct that 'this' is no longer the same 'this' by the time your anonymous function is called.
Whenever you call a function, a brand new 'this' is created and exists within the scope of that function. If you just call a function like this:
transition_opacity(args);
.. then within transition_opacity, 'this' just refers to the window object, or maybe window.document. For 'this' to refer to anything other than window or window.document, you need to (in effect) do one of the following:
myObject.transition_opacity(args);
transition_opacity.call(myObject, arg1, arg2, ..);
transition_opacity.apply(myObject, argArray);
or
var myObject = new transition_opacity(args);
In each of those cases, within transition_opacity, 'this' refers to myObject (or, well, in the last case, it refers to a new object that is being created and assigned to myObject).
Here is a way to do what it looks like you're trying to do:
var MyNamespace = {
option: function(room,slot){
var div_id = document.getElementById(room);
var opacity = window.getComputedStyle(div_id).opacity;
var _this = this;
transition_opacity(div_id,opacity,0,function(){
// Careful! Inside here, 'this' is just window or window.document,
// unless transition_opacity sets it to something using call or apply,
// in which case that 'this' is probably not the 'this' you want.
// So carefully refer to the saved instance of 'this':
_this.load();
});
},
load: function(){
console.log('test'); // now it should happen
}
}
.
.
MyNamespace.option(room, slot); // inside option, 'this' is MyNamespace.
Here's another way to do it:
function MyClass(){};
MyClass.prototype = {
// all the same stuff that is in MyNamespace above..
};
.
.
var myObject = new MyClass();
myObject.option(room, slot);
Clear as mud?
Just use
transition_opacity(div_id,opacity,0,load);
You have defined a 'load' within another function as an 'Function Declaration', so now it is only accessible within 'option' function and in other functions defined in this one by name 'load'. You can't access it by using 'this.load' no matter what 'this' is. If you want to access 'load' function as 'this.load' you can try this example to understand how 'this' keywoard works
// Function Declaration
function f1(callback){
callback();
};
// Function Declaration
function f2(){
// Function Expression
this.load = function(){
console.log("test");
};
f1(this.load);
};
var obj = new f2(); // test, this == obj, so obj.load() now exists
obj.load(); //test, this == obj
f2(); //test, this == window, so window.load() now exists
load(); //test, window is the global scope
I have been fiddling with code to call a function with the name of the value of a variable and then keep the this scope when called, but the this pointer seems to be in context of the element I have used jQuery's bind method on, rather than the object the function I might be calling is within. To clarify here´s some code to illustrate the problem:
classname.prototype = {
bindElementToFunction: function(element, functionToCall){
$(element).bind("click",
{realThis: this, functionToCall: functionToCall},
this.callFunction);
},
// I had hoped I could change the this pointer back to the object by running
// it through this function, I have tried using .apply and .call but I can't
// seem to get them to work with function pointers
callFunction: function(event){
var realThis = event.data.realThis;
var functionToCall = event.data.functionToCall;
functionToCall = realThis[functionToCall];
// I have tried .apply and .call in several different ways but can't seem
// to get them to work in this context
functionToCall();
},
abitraryFunction: function(){
this.test();
},
};
The problem here is then that everything works fine up until abitraryFunction where this is still referring to the element from the bind function. I have tried doing .apply() with the appropriate this pointers, but they do not seem to work.
So here's the question how do I change the context of the "this" pointer in combination with function pointers?
Feel free to scrap all the code I have written, as long as I am able to do a bind function to an element that then runs a method within a object where "this" is refferring to the object the method is within.
Thanks
I think the jQuery bind is making your code way more complicated than it needs to be. The JavaScript bind() function works perfectly:
http://jsfiddle.net/bQGWS/
By simply assigning a function to the onclick (or any other event hook) of an element, this is evaluated from the element's point of view and so points to the element itself.
When you use bind, you end up with a copy of the function where this is effectively replaced with the var you passed into bind().
classname = function(){}
classname.prototype = {
method: function(){
try {
alert( this.othermethod() );
} catch(e) {
// Method doesn't exist in scope
alert( 'Wrong scope :(');
}
},
othermethod: function(){
return 'hello desired scope!';
},
referenceToElement: function(elementId, functionname){
var el = document.getElementById(elementId);
// Just assigning the function as is
el.onclick = this[functionname];
},
bindToElement: function(elementId, functionname){
var el = document.getElementById(elementId);
// Using the bind function to create a copy in the
// scope of this (within the prototype)
el.onclick = this[functionname].bind(this);
}
}
var instance = new classname();
instance.referenceToElement('reference', 'method');
instance.bindToElement('bound', 'method');
I have an issuer where I lose the this inside this object. The output of the following piece of JavaScript gives me "some-id" and then undefined. When I use this inside a callback function, the scope goes out of the object and it cannot use this any more. How can I get the callback to use 'this' or at least have access to the object?
Since I will make multiple objects, I won't be able to create a 'static' like storage.
Here is my test code that you can use to reproduce my problem. What I would like to have is CheckBox.doSomething() to return the value of this.id which should match some-id for this test case.
function CheckBox(input_id) {
this.id = input_id;
this.doSomething();
$('#some-element').click(this.doSomething);
}
Checkbox.prototype.doSomething = function() {
alert(this.input_id);
}
var some_box = new CheckBox('some-id');
some_box.doSomething();
$('#some-element').click();
I can't even get this to work as I want it to:
function CheckBox2(input_id) {
this.id = input_id;
alert(this.id);
}
CheckBox2.prototype.doSomething = function() {
alert(this.input_id);
}
var some_box = new CheckBox2('some-id');
some_box.doSomething();
Your problem is with this line: $('#some-element').click(this.doSomething);
Why this is a problem
JavaScript methods don't know anything about the object that should be assigned to this, it's set when the method is called either explicitly (with myFunction.call(obj)) or implicitly (when called using obj.myFunction()).
For example:
var x = {
logThis: function () {
console.log(this);
}
};
x.logThis(); // logs x
x.logThis.call(y); // logs y
var func = x.logThis;
func(); // logs window: the fallback for when no value is given for `this`
In your case, you're passing this.doSomething to jQuery, which is then explicitly calling it with the element that was clicked as the value of this. What's happening is (a slightly more complex version of) this:
var callback = this.doSomething;
callback.call(anElement, anEvent);
The solution
You need to make sure that doSomething is called with the right value of this. You can do that by wrapping it in another function:
var cb = this;
$('#some-element').click(function() {
return cb.doSomething();
});
jQuery provides a proxy function lets you do this more simply:
$('#some-element').click(jQuery.proxy(this.doSomething, this));
function CheckBox(input_id) {
this.id = input_id;
this.doSomething = $.proxy( this.doSomething, this );
$('#some-element').click(this.doSomething);
}
The "javascript equivalent" of this is Function#bind but that is not available in every browser and since it seems you are using jQuery I am using the jQuery equivalent $.proxy
Others have already explained the causes of the problem and how to fix it with jQuery. What's left is how you fix it with standard JavaScript. Instead of ...
$('#some-element').click(this.doSomething);
... you write:
document.getElementById('some-element').addEventListener('click', this.doSomething.bind(this));
This changes the context of this inside doSomething. You can also do that with anonymous functions - instead of ...
$('#some-element').click(function(event) {
console.log(this);
});
... you write:
document.getElementById('#some-element').addEventListener('click', (function(event) {
console.log(this);
}).bind(this));
That has been very useful to me in projects with lots of callbacks, e.g. in Node.js (where you don't have to care about outdated browsers).
Edit: getElementById() and addEventListener() instead of $(...).click(...).
I have an event that binds a function to a click. The click calls another function in the same view. Unfortunately, the scope is not the correct scope. When I try to do this.otherFunction(), the function that is assigned to the click is not in the same scope as this.otherFunction(). Is there a way to pass in the scope of otherFunction()?
initialize: function() {
this.render();
if (joinedGoalList.get(this.model.id) != null) {
this.renderLeaveGoal();
} else {
this.renderJoinGoal();
}
},
events: {
"keypress #goal-update": "createOnEnter",
"click #join-goal": "joinGoal",
"click #leave-goal": "leaveGoal",
},
joinGoal: function() {
matches = joinedGoalList.where({id: this.model.get("id")});
if (matches.length == 0) {
joinedGoalList.create({goal_id: this.model.get("id")}, {wait: true, success: function() {
var self = this;
self.renderLeaveGoal();
}, error: function() {
console.log("error");
}});
}
},
renderLeaveGoal: function() {
console.log("render leave goal");
var template = _.template($("#leave-goal-template").html());
console.log(template);
$("#toggle-goal-join").html(template());
},
These are all under the same view.
Edit:
Hmm, now the problem is that I get this error:
Uncaught TypeError: Object [object DOMWindow] has no method 'renderLeaveGoal'. Does this seem that I saved the wrong scope?
Standard technique is to do something like
var self = this;
Then you can do
self.otherFunction();
In place of
this.otherFunction();
deltanovember's answer is correct, but he didn't explain why it's correct, and I feel that's important:
Scope is messy in Javascript. I've found the easiest way to keep track of it is to think about when something will execute. If a block of code executes right away, it's probably running in the same scope as "this". If a function is going to be called later, it's probably running in a completely different scope, with its own concept of "this".
In your example, the anonymous function you're providing as that success callback is going to run later, and it won't be scoped to the same "this" as the code that's running when said callback is defined. This is why you're getting an error: renderLeaveGoal was defined in the scope where the callback was defined, but not the scope where the callback will be executed.
Now, to make this more confusing, variables defined when a callback is defined will be available within that callback's scope. This is why deltanovember's answer works. By disguising "this" in a variable when the success callback is defined, the callback can still access it when it runs later, despite the completely different scope.
I hope that makes sense. Comment if it doesn't, and I'll try again :)
You can also use Underscore's bindAll function like so:
initialize: function() {
_.bindAll(this);
}
Behind the scenes, Underscore will replace all function calls in the object with proxied versions that set "this" to be the same "this" as your original object. Of course, if one of your methods has its own anonymous callback functions inside it, then you'll need to do the whole "self = this" dance; bindAll only fixes the context of your "outer" methods on the object.
I've gotten into the habit of using _.bindAll as the first line in each of my View's initialize() methods.
The function is attached to the view, so you need to call it off of the View object.
success: this.renderLeaveGoal
In this case, you don't need the anonymous function because the view functions are automatically bound to the view's context.