"This" within es6 class method [duplicate] - javascript

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.

Related

JavaScript: How to use a method within a prototype? [duplicate]

This question already has answers here:
JavaScript click event listener on class
(8 answers)
Closed 6 years ago.
I am trying to modify a plug-in (Pikaday) and add my own little bit of functionality.
The plugin is built using the proto-type system, and I have added my funciton within it.
Pikaday.prototype = {
//OTHER PARTS OF PLUGIN
showYearPicker: function()
{
document.getElementsByClassName('pika-year-label').onclick=function(){
console.log("asfd");
}
},
//OTHER PARTS OF PLUGIN
}
return Pikaday;
What I am having trouble understanding is how to actually trigger this method. Clicking the specified element doesnt work.
Would anyone know how I can include this function as part of the plugin?
Full code (minus my addition) is here if anyone is interested.
Defining a method on the prototype of a function in simple terms means you've defined a method on an object that will be one link up the prototype chain from all instances of this function.
You can think of it as a common "parent" or "object-above" for every instance of that function.
Therefore, to use your method, you must instantiate the plugin and call your function on it.
function Pikaday() {}
Pikaday.prototype = {
showYearPicker: function() {
console.log('called');
}
}
let pikaday = new Pikaday();
pikaday.showYearPicker(); // <-- call the function on the prototype
What this will do is check if pikaday has showYearPicker defined on it, and since it doesn't, it will go up the proto chain and check if that object has it, which it does.
In other words, this is essentially what the JS engine does behind the scenes:
let pikaday = new Pikaday();
pikaday.__proto__.showYearPicker();
Note that proto here is just for demonstration, its only recently been standardized in ES6 for legacy purposes, but this behavior should be left to the JS engine (The spec calls this proto link [[Prototype]]).
If needed, the proper way to access the prototype is to use Object.getPrototypeOf (in +ES6 you could use the semantically-clearer Reflect.getPrototypeOf)
function Pikaday() {}
console.log(
Object.getPrototypeOf(new Pikaday()) === Pikaday.prototype
);
console.log(
Reflect.getPrototypeOf(new Pikaday()) === Pikaday.prototype
);
One problem with your code is that getElementsByClassName will return a NodeList (not an array) of nodes that have this class.
This means you must either loop over this list and attach a handler to each element,
let nodes = document.getElementsByClassName('pika-year-label');
Array.from(nodes) // convert NodeList to an array so we can use forEach
.forEach(node => {
// attach a handler on each node
node.onclick = function (){
console.log("asfd");
}
});
// Side Note: You can also use array spread to convert the node list to an array
// [...nodes].forEach( ... )
or alternatively attach a handler to a common parent for all the elements to delegate the behavior.
document.body.onclick = function(e) {
if (e.target.classList.contains('pika-year-label')) {
console.log("asfd");
}
}
Finally, if you only want to add this function to an existing function's prototype, one way to do it is to simply define it as a method on that prototype:
function Pikaday() {}
// define a method on the existing prototype
Pikaday.prototype.showYearPicker = function() {
console.log('called');
};
let pikaday = new Pikaday();
pikaday.showYearPicker(); // <-- call the function on the prototype

How to get member variable in event handler called from a prototype function in a JavaScript class? [duplicate]

This question already has answers here:
Preserving a reference to "this" in JavaScript prototype functions [duplicate]
(7 answers)
Closed 6 years ago.
What is the correct way to preserve a this javascript reference in an event handler stored inside the object's prototype? I'd like to stay away from creating temp vars like '_this' or 'that' and I can't use a framework like jQuery. I saw a lot of people talk about using a 'bind' function but was unsure of how to implement it in my given scenario.
var Example = function(foo,bar){
this.foo = foo;
this.bar = bar;
};
Example.prototype.SetEvent = function(){
this.bar.onclick = this.ClickEvent;
};
Example.prototype.ClickEvent = function(){
console.log(this.foo); // logs undefined because 'this' is really 'this.bar'
};
I find bind() being the cleanest solution so far:
this.bar.onclick = this.ClickEvent.bind(this);
BTW the other this is called that by convention very often.
Check out the MDN document on bind: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind
Using this functionality, you can change the scope (what this is):
Example.prototype.SetEvent = function(){
this.bar.onclick = this.ClickEvent.bind(this);
};
Be aware, however, that this is a new addition to EMCA and thus may not be supported in all user agents. There is a pollyfill available at the MDN document linked above.
The problem with bind is that is only supported by IE9+.
The function can be polyfilled with es5-shim, but it's not completely identical to the native implementation:
Caveat: the bound function has a prototype property.
Caveat: bound functions do not try too hard to keep you from manipulating their arguments and caller properties.
Caveat: bound functions don't have checks in call and apply to avoid executing as a constructor.
Another alternative can be jQuery.proxy:
$(elem).on('click', $.proxy(eventHandler, this));
This is even more helpful if you want to remove the event handler later, because when a function goes through the proxy method, jQuery generates a new guid value and then applies that guid to both the core function as well as the resultant proxy function, so that you can use the original function reference to unbind an event handler callback that has been proxied:
$(elem).off('click', eventHandler);
Other solution: use the "arrow functions" introduced by ES6. Those have the particularity to not change the context, IE what this points to. Here is an example:
function Foo(){
myeventemitter.addEventListener("mousedown", (()=>{
return (event)=>{this.myinstancefunction(event)}; /* Return the arrow
function (with the same this) that pass the event to the Foo prototype handler */
})());
}
Foo.prototype.myinstancefunction = function(event){
// Handle event in the context of your Object
}
Arrow function specs # MDN
Edit
Be carefull with it. If you use it client-side and you can't be sure of the capabilities of the JS interpreter, note that old browser won't recognize arrow functions (see CanIUse stats). Use this method only if you KNOW what will run it (recent browsers only & NodeJS apps)

Can addEventListener point to an object's own function? [duplicate]

This question already has answers here:
Preserving a reference to "this" in JavaScript prototype functions [duplicate]
(7 answers)
Closed 6 years ago.
What is the correct way to preserve a this javascript reference in an event handler stored inside the object's prototype? I'd like to stay away from creating temp vars like '_this' or 'that' and I can't use a framework like jQuery. I saw a lot of people talk about using a 'bind' function but was unsure of how to implement it in my given scenario.
var Example = function(foo,bar){
this.foo = foo;
this.bar = bar;
};
Example.prototype.SetEvent = function(){
this.bar.onclick = this.ClickEvent;
};
Example.prototype.ClickEvent = function(){
console.log(this.foo); // logs undefined because 'this' is really 'this.bar'
};
I find bind() being the cleanest solution so far:
this.bar.onclick = this.ClickEvent.bind(this);
BTW the other this is called that by convention very often.
Check out the MDN document on bind: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind
Using this functionality, you can change the scope (what this is):
Example.prototype.SetEvent = function(){
this.bar.onclick = this.ClickEvent.bind(this);
};
Be aware, however, that this is a new addition to EMCA and thus may not be supported in all user agents. There is a pollyfill available at the MDN document linked above.
The problem with bind is that is only supported by IE9+.
The function can be polyfilled with es5-shim, but it's not completely identical to the native implementation:
Caveat: the bound function has a prototype property.
Caveat: bound functions do not try too hard to keep you from manipulating their arguments and caller properties.
Caveat: bound functions don't have checks in call and apply to avoid executing as a constructor.
Another alternative can be jQuery.proxy:
$(elem).on('click', $.proxy(eventHandler, this));
This is even more helpful if you want to remove the event handler later, because when a function goes through the proxy method, jQuery generates a new guid value and then applies that guid to both the core function as well as the resultant proxy function, so that you can use the original function reference to unbind an event handler callback that has been proxied:
$(elem).off('click', eventHandler);
Other solution: use the "arrow functions" introduced by ES6. Those have the particularity to not change the context, IE what this points to. Here is an example:
function Foo(){
myeventemitter.addEventListener("mousedown", (()=>{
return (event)=>{this.myinstancefunction(event)}; /* Return the arrow
function (with the same this) that pass the event to the Foo prototype handler */
})());
}
Foo.prototype.myinstancefunction = function(event){
// Handle event in the context of your Object
}
Arrow function specs # MDN
Edit
Be carefull with it. If you use it client-side and you can't be sure of the capabilities of the JS interpreter, note that old browser won't recognize arrow functions (see CanIUse stats). Use this method only if you KNOW what will run it (recent browsers only & NodeJS apps)

Javascript OOP: binding method to event handler [duplicate]

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.

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

Categories

Resources