Javascript OOP: binding method to event handler [duplicate] - javascript

This question already has answers here:
How does the "this" keyword work, and when should it be used?
(22 answers)
Closed 9 years ago.
I'm new to JS and I understand functions are the way to declare "class like" structures.
I'm trying to do something like this:
function Game() {
// Class stuff
this.handleClick = function(e) {
alert(e);
}
// Bind event to method previously defined
$('#board').bind('click', function(e) {
this.handleClick(e); // <--- THIS IS THE PROBLEMATIC LINE
});
}
Now, if in the "problematic line" I write:
handleClick(e)
I get Uncaught ReferenceError: handleClick is not defined.
Instead, if I write:
this.handleClick(e);
I get Uncaught TypeError: Object #<HTMLCanvasElement> has no method 'handleClick'
But then, if I do:
function Game() {
// Class stuff
this.handleClick = function(e) {
alert(e);
}
var game = this; // <--- I ASSIGN this TO board
// Bind event to method previously defined
$('#board').bind('click', function(e) {
game.handleClick(e); // <--- THIS WORKS!! WHY????
});
}
It works!?!?!
My questions are:
Why does this happen this way? I know this can be problematic, but why assigning it to a variable changes it like that?
Am I doing this wrong? Is there a better way to achieve something like this in a more elegant way?

You found the one hole in Javascript's implementation of closures. (If you don't know what closures are, don't worry about it.)
this in Javascript references the object on which the function was called. So if you have an object with a property that happens to be assigned the value of a function, and you call that property, the this pointer references the object. It does not reference whatever this was where the function was created, nor does it reference the function itself.
var object = {};
object.myfunc = function() { console.log(this); } //<-- this will refer to object
object.myfunc();
In the case of an event handler, the handler function (in pure Javascript, ignoring jQuery) gets assigned to the DOM element, so it gets called from that element and hence this points to the DOM element.
Using a local variable, like game in your example above, means that the function you created (a "closure") will "close" around that variable, capturing the values of any variable in scope at the place the function was defined. Any variable except this. So your work-around is a very common way to handle this.
Another common way to handle this would be to use a library like Underscore.js which has a function called bind that does some higher-order-function magic to make sure this always points to what you want.
$('board').bind('click', _.bind(function(e) {
this.handleClick(e);
}, this));
For this reason, Underscore.js is one of my favorite Javascript libraries (after jQuery), and I use it almost exclusively for the bind function.

Related

"This" within es6 class method [duplicate]

This question already has answers here:
How to access the correct `this` inside a callback
(13 answers)
Closed 6 years ago.
For some reason I'm getting weird values for "this" in my es6 class...
'use strict';
class Clicker {
constructor(element) {
this.count = 0;
this.elem = element;
this.elem.addEventListener('click', this.click);
// logs Clicker { count:0, elem: button#thing} as expected
console.log(this);
}
click() {
// logs <button id="thing">...</button> as unexpected...
console.log(this);
this.count++;
}
}
var thing = document.getElementById('thing');
var instance = new Clicker(thing);
<button id="thing">Click me</button>
Question:
Why is the "this" inside of of the Clickers' click method referring to the dom node rather than ... itself?
More importantly, how do I refer to Clickers' count property from within its' click method if I can't use "this" to do it?
Why is the "this" inside of of the Clickers' click method referring to
the dom node rather than ... itself?
Because the specification for .addEventListener() is to set the this pointer to the DOM element that caught the event. That's how it is designed to work.
When passing a method as a callback where you want to override the value of this, you can use .bind() to force the desired value of this with it:
this.elem.addEventListener('click', this.click.bind(this));
Explanation:
All function calls in Javascript set a new value of this according to how the function is called. See this explanation for further info on that basic set of rules.
On top of that, when you do this:
this.elem.addEventListener('click', this.click);
You are just getting the this.click method and passing that method alone to addEventListener(). The value of this will be completely lost. It's as if you are doing this:
var m = this.click; // m here is just a reference to Clicker.prototype.click
this.elem.addEventListener('click', m);
On top of this, .addEventListener() is specifically built to set it's own value of this when it calls the callback (to point this at the element creating the event).
So, you can use .bind() as shown above to force the proper value of this to be in place when your method is called.
For reference, you may find this description of the six ways that this is set for a function call in Javascript to be useful.
Other Options
I find .bind() to be the clearest way of defining this, but you could also use either a local anonymous function:
var self = this;
this.elem.addEventListener('click', function() {
self.click();
});
or in ES6, an arrow function:
this.elem.addEventListener('click', () => this.click());
The arrow function will preserve the value of this for you automatically to avoid needing the self reference used in the prior example.

JS: this reference does not work [duplicate]

This question already has answers here:
Using this in event handler in strict javascript?
(3 answers)
Closed 7 years ago.
I want to encapsulate my Javascript code for certain objects in structures as indicated below. However, I ran into trouble with the semantics of this.
While this during tiles.init() refers to the tiles object, in the event handlers it refers to the event, i.e. I cannot use this to call other methods from my object.
Is there any way to pass the object to the event handlers, such that I do not have to use the global variable to call my own sub-routines and still retain this from the callback context?
I put up a JSFiddle here. This is the working JS part:
myData = {
'color': "red",
'makeRed': function(){
// don't want reference to global here!
$(this).css("color",myData.color);
},
'reset': function(){
$(this).css("color","");
},
'init': function(){
$("#click").hover(this.makeRed,this.reset);
}
};
myData.init();
I found several solutions like this and this idea to pass additional arguments. The question has been marked a duplicate of this, but using .bind() wastes the this required for the jQuery inside the callback.
Any idea how to get both tiles and this of the event context to the handler function without using globals?
You can use a closure variable in the init method
'init': function () {
var self = this;
$("#tiles div.tile").hover(function () {
self.hoverIn(this);
}, function () {
self.hoverOut(this);
});
}
Your construct is not working because this inside the hover callbacks does not refer to the tiles object

How do I reference an object method in javascript from outside the object (through event)?

I am creating a fairly complex object and want to be able to manipulate the object from the outside. I want to reference an privileged method from a click event
function myObject(){
//How do I reference this function?
this.doSomething = function (action){
var doing = action;
}
}
I understand that I could reference the method if I create the object inside a variable like:
var obj = new myObject();
obj.doSomething('hit keyboard');
But the links I am creating to trigger this event are being created by the object, but placed outside the object, so they will not know the container variable to reference it.
i.e.
Do Something
doesn't work because of course the doSomething method is part of the object, not a global function. I could rewrite the code to make it global, but would rather avoid doing that.
If this question doesn't make sense I would be happy to make it clearer. Thanks in advance.
Although it will sit in the global scope, and generally not recommended, you could do this (if you just need to get it working):
function myObject(){
//How do I reference this function?
window.doSomething = function (action){ // USE window instead of this !!
var doing = action;
}
}
By using window instead of this, it can be called in your handler.
IF the object instance is already created, then you can say:
Do Something
Otherwise,
Do Something
You need the object to be in the global scope. Try this:
window.myObject = {
doSomething: function(action) {
alert("Done!");
}
};
and then you make a click event like
Do Something
Example
To define the problem, you're in a particular object's click handler and you want to call a method on another object in your code that you can't get to directly from the object you're in. You really only have these options and you can just pick the one that seems best or seems the least objectionable:
You can make the object that you want to call a method on be in the global scope.
You can make the object that you want to call a method on be findable in the global scope (either in a parent hierarchy or with some sort of global find). It can be done this way without necessarily adding any more top level objects to the global scope.
You can set the object that you want to call a method as instance data on all objects that will have the click handler so that you can just reference the global object from the click handler object's instance data.
You can declare the click handler to have a parameter that contains your object by using function closures and avoid all global data.
The problem is that there's a disconnect between the instance of an object and the element on the page whose event should trigger the object's function. There are various ways to handle that without exposing anything globally. One way, for example, would be to tie the two via an object's property:
Give the element an ID:
<a id="myLink">Do Something</a>
Add element property to object:
function myObject(element){
this.element = element;
if (this.element) {
this.element.onclick = this.doSomething;
}
this.doSomething = function () {
// do stuffs.
}
}
Instantiate object:
var obj = new myObject(document.getElementById('myLink'));
Or simplify the object definition a bit more:
function myObject(element){
this.element = element;
if (this.element) {
this.element.onclick = function () {
// do stuffs.
}
}
}

Calling class methods within jQuery function

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

In javascript functions, can you set this.function = function?

I have run into this jquery plugin and i quite understand how this works:
$.functionone = function(){
function setOptions(newOptions){
...
}
this.setOptions = setOptions;
}
What i dont understand is what does this actually do? this.setOptions = setOptions can you call a function without parenthesis? What is the relationship between this.setOptions and setOptions by itself?
Functions in JavaScript are objects like (nearly) everything else. When you do this:
this.setOptions = setOptions;
you're not calling the setOptions function, you're just assigning a reference to the function to a property, exactly like setting a property to any other object, like this:
var dt;
dt = new Date();
this.today = dt;
With functions, you'd do this so you can later call the function via the property (which sets up the this value to be the object the property's on, which is handy). It's a bit clearer what's going on if you use a different name for the property than for the function:
function functionName() { ... } // Declares the function
this.propertyName = functionName; // Sets the property
functionName(); // Calls the function (with `this` = the global object ["window", on browsers])
this.propertyName(); // Also calls the function (but with `this` = the object the property's on)
The pattern you identified, declaring a function and then separately setting a reference to it on an object, is frequently used to make sure the resulting function has a name. You can create a function and bind it to a property like this:
this.setOptions = function() {
...
};
...but then the function doesn't have a name (the property does, but not the function), which can be an issue when you're trying to debug because debuggers show you the names of functions in various contexts (call stacks, for instance). If a lot of your functions don't have names, even though the properties referring to them do, it makes debugging difficult. More about anonymous vs. named functions here. (There's also a difference in terms of when the function is instantiated, but going into it here would just complicate things.)
You'd think you could combine things, like this:
this.setOptions = function setOptions() { // <=== DON'T DO THIS
...
};
...but although that mostly works, it triggers a bug in Internet Explorer / JScript (it creates two different functions for that code, which is at best a memory waste and at worst a very subtle and time-wasting problem, as it was in this question).
The function setOptions is only called if you add parenthesis: setOptions(). If you do not add parenthesis, you have a reference to a function. This is just like a normal variable, only it contains a function reference instead of some other value.
If you set this.setOptions = setOptions, you make a function setOptions on the this object, which points to the same function as setOptions. That is, if you call it using this.setOptions(), the referenced function will be called.

Categories

Resources