I have a Javascript object the following:
function extraFields(id) {
this.numActiveFields = 0;
...
this.addRow = function(quoteClass, rowClass) {
var remButton = $("<span></span>")
.addClass(rowClass)
.addClass(quoteClass)
.click(function() {
//here I want to refer to the object's variable, but instead refer
//to the JQuery object
this.numActiveFields++;
});
}
...
}
I want to change the object's variable from inside the callback function. How would I do that? Should I change the way I declare the object?
When you are in the callback function "this" is the "remButton" you created. Just save "this" in a variable before the callback and then use that instead.
For a better explanation please look at the link that mplungjan suggested:
Reference to an object from a callback function in jQuery
Related
The following successfully prints 'foo'.
var obj = {
name: 'foo',
printName: function printName() {
console.log(this.name);
}
};
var printButton= document.getElementById('printIt');
printButton.addEventListener('click', function(){
obj.printName();
});
The following doesn't, however:
printButton.addEventListener('click', obj.printName() );
I know the solution... simply use bind so that we're referencing the obj object. i.e:
printButton.addEventListener('click', obj.printName.bind(obj) );
Why then don't we need to use bind in the first example. I don't know why wrapping obj.printName() function call in the anonymous function results in the console.log correctly referencing and printing this properly, but when called directly after click, you needs to use bind
Alright, I commented with some good information on this question so I might as well answer!
Functions are first class
Okay, let's starts with some fundamentals of javascript that is very dissimilar to some other programming languages: in javascript functions are first class citizens--which is just a fancy way of saying that you can save functions into variables and you can pass functions into other functions.
const myFunction = function () { return 'whoa a function'; }
array.map(function () { return x + 1; });
And because of this wonderful feature, there is a big difference between the expressions:
Expression 1
obj.printName
and
Expression 2
obj.printName();
In expression 1: the function isn't being invoked so the value of the expression is of type function
In expression 2: the function is being invoked so the value of the expression is what the function returns. In your case, that's undefined
addEventListener
The method addEventListener takes in two arguments:
a string of the type of event
a function that will be run when the event fires.
Alight, so what does that mean?
When you call
// doesn't work
printButton.addEventListener('click', obj.printName() );
you're not passing a value of type function to the addEventListener method, you're actually passing undefined.
// works
printButton.addEventListener('click', obj.printName.bind(obj) );
then works (for one reason) because the second argument is actually of type function.
What does bind do? Why does it return a function?
Now we need to discuss what bind actually does. It related to the pointer* this.
*by pointer, I mean a reference identifier to some object
bind is a method that exists on every function object that simply binds the this pointer of a desired object to the function
This is best shown by an example:
Say you have a class Fruit that has a method printName. Now that we know that you can save a method into a variable, let's try that. In the example below we're assigning two things:
boundMethod which used bind
unboundMethod that didn't use bind
class Fruit {
constructor() {
this.name = 'apple';
}
printName() {
console.log(this.name);
}
}
const myFruit = new Fruit();
// take the method `printName`
const boundMethod = myFruit.printName.bind(myFruit);
const unboundMethod = myFruit.printName;
boundMethod(); // works
unboundMethod(); // doesn't work
So what happens when you don't call bind? Why doesn't that work?
If you don't call bind in this case, the value of the function that gets stored into the identifier unboundMethod can be thought to be:
// doens't work
const unboundMethod = function() {
console.log(this.name);
}
where the contents of the function is the same contents of the method printName from the Fruit class. Do you see why this is an issue?
Because the this pointer is still there but the object it was intended to refer to is no longer in scope. When you try to invoke the unboundMethod, you'll get an error because it couldn't find name in this.
So what happens when you do use bind?
Loosely bind can be thought of as replacing the this value of function with the object you're passing into bind.
So if I assign: myFruit.printName.bind(myFruit) to boundMethod then you can think of the assignment like this:
// works
const boundMethod = function() {
console.log(myFruit.name);
}
where this is replaced with myFruit
The bottom-line/TL;DR
when to use bind in an Event Handler
You need to use Function.prototype.bind when you want to replace the thises inside the function with another object/pointer. If your function doesn't ever use this, then you don't need to use bind.
Why then don't we need to use bind in the first example?
If you don't need to "take the method" (i.e. taking the value of type of function), then you don't need to use bind either Another way to word that is: if you invoke the method directly from the object, you don't need bind that same object.
In the wrapper function, you're directly invoking the method of the object (as in expression 2). Because you're invoking the method without "taking the method" (we "took" the methods into variables in the Fruit example), you don't need to use bind.
printButton.addEventListener('click', function(){
// directly invoke the function
// no method "taking" here
obj.printName();
});
Hope this helps :D
Note: You need to call printButton.addEventListener('click', obj.printName() ); without parenthesis in obj.printName() since you want to pass the function.
The answer lies in the way this is bound in Javascript. In JS, the way a function is called decides how this is bound. So when you provide the callback function like below:
printButton.addEventListener('click', function(){
obj.printName();
});
Notice, printName is being called via dot notation. This is called implicit binding rule when this is bound to an object before dot, in this case obj. Clearly in this case, you get the expected output.
However, when you call it like this:
printButton.addEventListener('click', obj.printName );
Notice that, all you are passing is the address of the function that is inside obj. So in this case info about obj is lost. In other words, the code that calls back the function doesn't have the info about obj that could have been used to set this. All it has is the address of the function to call.
Hope this helps!
EDIT:
Look at this crude implementation I call bind2 that mimics native bind. This is just to illustrate how native bind function returns a new function.
Function.prototype.bind2 = function (context) {
var callBackFunction = this;//Store the function to call later
return function () { //return a new function
callBackFunction.call(context);//Later when called, apply
//context, this is `obj` passed
//in bind2()
}
};
function hello() {
alert(this.name);
}
obj = {
name:'ABC'
};
var f = hello.bind2(obj);
f();
Notice: How function f() is hard bound here. f() has hard bound this with obj. You cannot change this to other than obj now. This is another thing with bind that probably will help you knowing.
This question already has answers here:
How to access the correct `this` inside a callback
(13 answers)
Closed 8 years ago.
Lately a lot of my code has been looking like this...
function MyObjectClass (selector) {
this.color = "red";
this.$elt = $(selector);
// ... any other object vars here ...
// Example method for the object
this.showThenUpdateColor = function () {
// Keep a copy of "this" to use for callbacks
var o = this;
// Example of jQuery function that accepts a callback
o.$elt.fadeIn(function(){
// Inside of this callback function we need to access the main object
o.$elt.css({
"background-color" : o.color // This is where we need the "o"
});
});
}
}
var myObject = new MyObjectClass('#myObject');
myObject.showThenUpdateColor();
...where I have a callback inside of an object method. Typically I assign the object itself ("this") to a local variable (usually "o" because it's short and easy) which can be used in the callback.
Is this an optimal use of memory? Is there any danger of memory leaks? Is there a better way?
I would recommend having a look at jQuery.proxy(). It lets you create a function that, when executed, executes another function in the scope you specify. You could then write:
this.showThenUpdateColor = function () {
// Example of jQuery function that accepts a callback
this.$elt.fadeIn($.proxy(this._onFadeInComplete, this));
}
function _onFadeInComplete() {
this.$elt.css({
"background-color" : this.color
});
}
I have an object which has two functions within it, and as I guessed each one has a different value for this:
custom_controls : {
play_pause : function () {
console.log(this); // object
videoPlayer.getIsPlaying(function (video_is_playing) {
if (video_is_playing) {
console.log(this); // window
videoPlayer.pause(true);
} else {
videoPlayer.play();
}
});
}
},
Then the function is invoked like this:
custom_controls.play_pause()
I've heard that the way you invoke a function denotes the value of this.
So my question is: What is happening here? What kind of function invocations am I using? And how does each one affect this?
When calling obj.func(), this inside the function will be equal to obj. If there is no obj, the global object (window) is used instead. Or if you are running in Strict Mode, undefined is used.
The first log is your object because you call the function like this:
custom_controls.play_pause() // custom_controls will be 'this'
The second log is window because the function passed as parameter to getIsPlaying is not called with any this:
videoPlayer.getIsPlaying = function(callback) {
callback(); // this inside callback will be window
}
You can control what the value of this will be when you invoke a function by using call or apply. You can create a new function which will always have the this value set to whatever you want by using the bind function:
videoPlayer.getIsPlaying(function (video_is_playing) {
if (video_is_playing) {
console.log(this); // my obj
videoPlayer.pause(true);
} else {
videoPlayer.play();
}
}.bind(this)); // magic!
Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
Each function is actually executed within a context. That context is denoted as the current this for which you call the function.
Given your code:
If you call custom_controls.play_pause() you are saying "Take the field of the object custom_controls named play_pause and execute it within the context of the object custom_controls".
Later on calling videoPlayer.getIsPlaying() means pretty much the same. Except you're giving it a callback function. How that callback function is executed later on depends on how videoPlayer.getIsPlaying is implemented.
If I have to guess I'd say that getIsPlaying has a callback.call(window, video_is_playing) somewhere in it.
call is a method of all function objects in javascript.
There are a few ways to work around this "issue" if you want to reference a this in some callback.
var self = this;
call_me_maybe(function() {
console.log(this); //the this that call_me_maybe chose to call your function with
console.log(self); //the this from the upper scope
});
or if you don't care about the object in which context call_me_maybe will call your function:
call_me_maybe((function(){
console.log(this); //the this from the upper scope
}).bind(this));
What bind does is it returns a wrap[per of the function which will always be called in the context of the object to which it is bound.
bind can also bind arguments as well as the this object for the function, creating a sort of curry.
Your first this is referring to play_pause.
Your second this could either be referring to the Window or your videoPlayer object. In JavaScript, closures and regular functions are 'generally' attached to window, and calling this returns window. In certain cases, e.g. if you attach a function to the click handler of an HTML element, this refers to the element...
element.onclick = function(){
this // -> element
}
But generally if you just create a function(), or have an anonymous one like yours this refers to window.
function hello(){
this // -> window
}
The this that you've discovered is the object is what you would expect because the function is operating on that object.
I'm not familiar with your videoPlayer, but as the value of this is the "window", I would imagine that either A the video player is a function of the browser itself, or B its scope was not properly closed.
this refers to the "proprietary" of the function, or the object from the function is a method of.
When you define a basic function, the "proprietary" is the page (or the window) itself.
You can check the callback documentation for workarounds
When you call videoPlayer.getIsPlaying it accepts a callback fn. Callback is invoked directly like cb() and hence the context is global (window).
To achieve the callback happening in context of your object. You can use
var fn = cb.bind(customControl);
videoPlayer.getIsPlaying(fn);
As a rule of thumb when a function is called like object.function this is set to object. If function is called directly, this is set to window. Function.bind return a function after binding object (this value) optionally along with parameters.
Read: MDN https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
So I have some javascript class and in one method I use jQuery to bind function to click event. And within this function I need to call other methods of this class. In usual js function I did it through "this.method_name()", but here, I guess, jQuery redefines "this" pointer.
jQuery doesn't redefine the this pointer, but that's how JavaScript functions work in general. Store a reference to the this pointer under a different name, and use that.
var self = this;
$("selector").click(function() {
self.method_name();
});
See this answer for more approaches.
There are a few different ways to do this.
Anurag has a perfect example of one.
Two other ways are the jQuery Proxy class (Mentioned in other answers) and the 'apply' function
Now lets create an object with click events:
var MyObj = function(){
this.property1 = "StringProp";
// jQuery Proxy Function
$(".selector").click($.proxy(function(){
//Will alert "StringProp"
alert(this.property1);
// set the 'this' object in the function to the MyObj instance
},this));
//Apply Function
//args are optional
this.clickFunction = function(arg1){
alert(this.property1);
};
$(".selector").click(this.clickFunction.apply(this,"this is optional"));
};
In addition to the possibility of temporarily storing a reference to this (self = this, see Anurag's answer), since ES6 it is possible to use arrow functions for this problem. These have no "own" this.
This means that the "usual" object-related this can be accessed again within an arrow function within an event handler:
$("selector").click(() => {
this.method_name();
});
Further information:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions?retiredLocale=de#cannot_be_used_as_methods
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions?retiredLocale=de#using_call_bind_and_apply
I am having trouble understanding this code:
$.functionone = function(){
function setOptions(newOptions){
...
}
this.setOptions = setOptions;
}
what does adding 'this' in 'this.setOptions' for? I understand that its referencing the function setOptions, but does adding the 'this' there make the function get called? I know this refers to the DOM element, but whats the point of having it in this particular scenario. Thanks.
That will simply expose the function from the scope of functionone, to be a property of the $ object.
For example:
$.functionone = function(){
function setOptions(newOptions){
//...
}
this.setOptions = setOptions;
};
typeof $.setOptions; // "undefined", it doesn't exist
$.functionone();
typeof $.setOptions; // "function"
The this value on JavaScript is set implicitly when you make a function call.
If the function is bound as a property of an object (like $.functionone), the this value will refer to the base object ($ in your example).
That's not so useful IMO, it's equivalent to:
$.functionone = function(){
this.setOptions = function (newOptions) {
//...
};
};
Which is at the end, when you invoke functionone, equivalent to:
$.setOptions = function (newOptions) {
//..
};
The difference is that the function is not named, which can be useful for debugging.
Working with the this value on jQuery plugins is more usual when you extend the jQuery.fn object, in that case the this value refers to the jQuery object that contains the matched elements, and not to the jQuery constructor itself.
The code creates a function and adds it as a "setOptions" property of the DOM element.
Presumably, some other part of the code will know to look for a "setOptions" function on the DOM element and execute it.
this refers to the particular DOM element that called the function.
The following line:
this.setOptions = setOptions;
Means the function "setOptions" is assigned to the "setOptions" property of the DOM Element.