How to reference object instance from event handler - javascript

In the code below, is there a better way to reference the object instance from handleClick() than pulling it in as a global?
var Widget = function() {
this.property = 'value';
this.bindEvents();
}
Widget.prototype = {
bindEvents: function() {
$('button').on('click', this.handleClick);
},
handleClick: function() {
var self = window.Widget;
console.log(self.property);
}
}
window.Widget = new Widget();
This question asks the same thing, and the (non-accepted) answer is to pass a callback to $.on() and call the handler from there, passing in the instance as a parameter like this:
bindEvents: function() {
var self = this;
$('button').on('click', function() {
self.handleClick.apply(self);
});
}
Is this technique of passing the instance around really the right way to do it, or is there still a preferred way besides the two I've shown?

The object can be passed as eventData to on(), like so:
var Widget = function () {
this.property = 'value';
this.bindEvents();
}
Widget.prototype = {
bindEvents: function () {
$('button').on('click', {o: this}, this.handleClick);
},
handleClick: function (e) {
var widgt = e.data.o;
console.log(widgt.property);
}
}
window.Widget = new Widget();
FIDDLE
This keeps the scope of the event handler as it should be.

You could use bind():
bindEvents: function() {
$('button').on('click', function() {
this.handleClick();
}.bind(this));
}
But when it comes to IE, that would work only in IE >= 9.
Edit: in legacy IE, you could use, as suggested by #Kevin B in the comment, jQuery's $.proxy():
bindEvents: function() {
$('button').on('click', $.proxy(function() {
this.handleClick();
}, this));
}

As #kamituel says, bind can be used:
Widget.prototype = {
bindEvents: function() {
$('button').on('click', this.handleClick.bind(this));
},
handleClick: function() {
console.log(this.property);
}
}

The specified technique is called closure, and it is in common use.
However, sometimes it is important to pay attention not to cause memory leaks.
You can read more about closure here:
How do JavaScript closures work?
and about memory leaks related to closures, here:
Memory leak risk in JavaScript closures

The best way to preserve this are lambda functions:
$('button').on('click', () => this.handleClick());
or if you would like to keep the reference to the event:
$('button').on('click', (event) => this.handleClick(event));
The special thing about lambda functions is that you don't define another function in a (potentially different) context. The lambda term is just an instruction and therefore part of the object it was defined in - in your case the instance created by window.Widget = new Widget().

Related

Object literal jquery event scoping

What is the best way to solve this scoping problem?
NAMESPACE.myObject = {
foo: 'foo',
init: function() {
$('.myBtn').on('click', this.myMethod);
},
myMethod: function() {
console.log($(this), foo);
}
};
NAMESPACE.myObject.init();
The result of the console.log should be the jQuery object that was clicked and the propertie foo of myObject. How would I achieve this?
Basically you can't have more than one this, so need to work around it.
As a general rule, create a scoped variable (THIS in the example below) to hold the scope you want to retain/access from inside any other scope.
You need to retain the this on the call to the myMethod though, inside the click handler, so you can't just pass myMethod as it loses the myObject instance.
NAMESPACE.myObject = {
this.foo: 'foo',
init: function() {
var THIS = this;
$('.myBtn').on('click', function(){
// "this" here is the button clicked
// "THIS" is still the myObject instance
THIS.myMethod(this);
});
},
myMethod: function(element) {
// "this" here is myObject
// The clicked element was passed as parameter "element" instead
console.log($(element), this.foo);
}
};
NAMESPACE.myObject.init();
I hope I explained this clearly enough :)
As jfriend00 points out, you can also use bind to basically create a function call with this scope on-the-fly (very cute), but that does not work on IE8 or older.
You can use .bind() like this:
NAMESPACE.myObject = {
foo: 'foo',
init: function() {
$('.myBtn').on('click', this.myMethod.bind(this));
},
myMethod: function() {
console.log($(this), foo);
}
};
NAMESPACE.myObject.init();
Or, for older versions of IE, since you already have jQuery you can use jQuery's $.proxy():
NAMESPACE.myObject = {
foo: 'foo',
init: function() {
$('.myBtn').on('click', $.proxy(this.myMethod, this));
},
myMethod: function() {
console.log($(this), foo);
}
};
NAMESPACE.myObject.init();
When you pass this.myMethod to the event listener, it loses its binding to this (as you've noticed) because the event listener doesn't save that reference or call the method with it. One way to keep that binding is to use .bind() (requires IE9 or a polyfill for earlier versions of IE).
Since I see you tagged jQuery, you can also use this approach, I know it is different from what you posted in the question, but I still an option.
working example
var NAMESPACE = NAMESPACE || {};
$(function() {
"use strict"
$.extend(NAMESPACE, true, {
getMyObject: function() {
function myObject() {
var self = this;
self.foo = 'foo';
self.init = function() {
$('.myBtn').click(self.myMethod);
};
self.myMethod = function() {
console.log($(this), self.foo);
};
}
return new myObject();
}
});
var myObject = NAMESPACE.getMyObject();
myObject.init();
})

Set variable and pass to function or re-set variable?

Which is best? Set a variable and pass it into the call, or set the var again every time. I want self = this (i.e. the Slider obj) for consistency.
EDIT for answers: I know I can use this but I work with other team members and have agreed for consistency to use self when referencing the object at all times.
So is this better:
Slider = {
init: function() {
var self = this,
foo = $('.someElement');
self.bindEvent(self);
},
bindEvent: function(self) {
self.foo.on('click', self.wasClicked(self));
},
wasClicked: function(e, self) {
e.doSomething();
self.done();
}
};
Or this:
Slider = {
init: function() {
var self = this,
foo = $('.someElement');
self.bindEvent();
},
bindEvent: function() {
var self = this;
self.foo.on('click', self.wasClicked);
},
wasClicked: function(e) {
var self = Slider;
e.doSomething();
self.done();
}
};
Your code is more reusable if you utilize an argument for your binding target, as per your first example. For instance:
bindEvent: function(target) {
target.onclick = function() { alert(target + " was clicked"); };
}
bindEvent() can now be called by other functions, and target something other than self. While in your current Slider object it only needs to be called by init(), in the future you may end up adding functionality to Slider that would be benefit from the added reusability of this function. At that point in time you can avoid rewriting the function, or worse yet creating an essentially duplicate function for the new usecase.
In situations where self is not the target, merely a reference to the parent, either example is fine. If your function already has a lengthy list of arguments it accepts, setting self inside the function instead of passing it as an argument is often a good idea. Lots of arguments make it harder to understand, refactor, and test, code.
You only need self in wasClicked, in the other cases use this:
Slider = {
init: function() {
foo = $('.someElement');
this.bindEvent();
},
bindEvent: function() {
this.foo.on('click', this.wasClicked);
},
wasClicked: function(e) {
var self = Slider;
e.doSomething;
self.done();
}
}
Slider.init();
Regarding your edit: in the first option, the event handler won't work, because you're calling it right away instead of passing a reference. You'd have to modify the method to return a function.

Prototype Pattern and "this"

I'm trying to create a client-side api for a web control using the Prototype pattern. However I want to make life easier by not having to manage "this".
This is some sample code (i have commented the problematic line):
MyObject = function ()
{
MyObject.initializeBase(this);
this._someProperty = null;
};
MyObject.prototype = {
initialize: function()
{
// Init
},
get_someProperty: function()
{
return this._someProperty;
},
set_someProperty: function(value)
{
this._someProperty = value;
},
doSomething: function ()
{
$('.some-class').each(function ()
{
$(this).click(this.doClick); // this.doClick is wrong
});
},
doClick: function ()
{
alert('Hello World');
}
};
Normally, using the revealing module pattern I would declare a private variable:
var that = this;
Can I do something similar with the Prototype pattern?
You can do the exact same thing you are used to, just do it within the doSomething method:
doSomething: function ()
{
var instance = this;
$('.some-class').each(function ()
{
$(this).click(instance.doClick);
});
},
This approach has nothing to with prototype or not, it's just how to manage context with nested functions. So when a function on a prototype (method) has nested functions within in, you may have to preserve the context this at any of those level if you want to access it in a nested scope.
ES5's Function.prototype.bind() might be an option for you. You could go like
doSomething: function ()
{
$('.some-class').each(function(_, node)
{
$(node).click(this.doClick); // this.doClick is right
}.bind(this));
},
Now, we proxied each event handler by invoking .bind() and as a result, we call it in the context of the prototype object. The caveat here is, you no longer have this referencing the actuall DOM node, so we need to use the passed in arguments from jQuery instead.

OO JavaScript - Avoiding self = this

Does anyone know of a way to get around declaring var self = this when using JavaScript in an OO fashion? I see it quite often and was curious if its just something you have to do, or if there really is a way (perhaps a class library?) that lets you get around it? I do realize why it is necessary (this has function scope). But you never know what clever ways may be out there..
For example, I usually code my "classes" like this in JS:
function MyClass() {
}
MyClass.prototype = {
firstFunction: function() {
var self = this;
$.ajax({
...
success: function() {
self.someFunctionCall();
}
});
},
secondFunction: function() {
var self = this;
window.setTimeout(function() {
self.someOtherFunction();
}, 1000);
}
};
In your first function you can do this...
$.ajax({
context: this,
success: function() {
this.someFunctionCall();
}
});
In the second one, you can do this, though you'll need to shim .bind() in older browsers...
window.setTimeout(function() {
this.someOtherFunction();
}.bind(this), 1000);
With jQuery, you could also do this...
window.setTimeout($.proxy(function() {
this.someOtherFunction();
}, this), 1000);
No, you need to do this if you want to refer to this in a different context (such as a callback) since otherwise it will be reassigned to another object such as window.
By the way, self is a python convention - in JavaScript people generally use the convention that = this. But it is just a matter of personal taste.
ES5 added the standard method called bind which allows you to bind the this of a function as well as the first n number of parameters. In the example above, you can avoid using self by calling bind.
$.ajax({
...
success: function() {
this.someFunctionCall();
}.bind(this);
});
For non-ES5 browsers you can use a shim for it such as the one found here: https://github.com/kriskowal/es5-shim
As an asside, I would avoid using self in your coding pattern because self is defined as a global variable that is equal to window which is the global scope. In other words, if you accidentally forget to define self you will silently get the global scope as the value instead of an exception. If you use that instead, you will get an exception (unless someone above you defined it).
Some javascript frameworks have their own event handling mechanisms that allow you to set context for the handler function. This way, instead of using self = this, you can simply specify this as the context.
Other possibility that comes to my mind is to pass the context in somewhere in global scope. Like
function MyClass() {
MyClass.instances.push(this);
}
MyClass.instances = new Array();
MyClass.getInstanceBySomeRelevantParameter = function(param) {
for (var i = 0; i < MyClass.instances.length; i++)
if (condition(param))
return MyClass.instances[i];
}
...
success: function(event) {
MyClass.getInstanceBySomeRelevantParameter(event).someFunctionCall();
}
You may always bind your methods to this and then use it as follows:
function MyClass() {
}
MyClass.prototype = {
firstFunction: function() {
var funct = someFunctionCall.bind(this);
$.ajax({
...
success: function() {
funct();
}
});
},
secondFunction: function() {
var funct = someOtherFunction.bind(this);
window.setTimeout(function() {
funct();
}, 1000);
}
};
For properties just assign them to another variable.
I fooled around on JSFiddle, and came up with the below. It does assume that you are using a top level namespace. This makes it so you only need to declare self once (at the bottom). I wrapped the class in an anonymous function so self wouldn't have a global scope. The fiddle is: http://jsfiddle.net/bdicasa/yu4vs/
var App = {};
(function() {
App.MyClass = function() { }
App.MyClass.prototype = {
firstFunction: function() {
console.log('in first function');
console.log(self === this); // true
},
secondFunction: function() {
window.setTimeout(function() {
self.firstFunction();
console.log(self === this); // false
}, 100);
}
};
var self = App.MyClass.prototype;
})();
var myClass = new App.MyClass();
myClass.secondFunction();

Overwritten "this" variable problem or how to call a member function?

I have this class where I am using a combination of jQuery and Prototype:
var MyClass = Class.create({
initElements: function(sumEl) {
this.sumEl = sumEl;
sumEl.keyup(this.updateSumHandler);
},
updateSumHandler: function(event) {
// Throws error here: "this.updateSum is not a function"
this.updateSum();
},
updateSum: function() {
// does something here
}
});
How can I call this.updateSum() after all?
You need to use closures.
initElements: function(sumEl) {
this.sumEl = sumEl;
var ref = this;
sumEl.keyup( function(){ref.updateSumHandler();});
},
Totally untested suggestion:
sumEl.keyup(this.updateSumHandler.bind(this));
.bind() gives back a new function where the first parameter of bind is closured for you as the function's this context.
It can also closure parameters, check out the documentation.
To me, Function.bind() is the single best function ever written in JavaScript :)
DOMEvent handlers are traditionally called with the elements they're registered to as context / "this". This is what jQuery does, too.
The easiest option for you would be to use jQuery's ability to handle event data
var MyClass = Class.create({
initElements: function(sumEl) {
this.sumEl = sumEl;
sumEl.bind("keyup", this, this.updateSumHandler);
},
updateSumHandler: function(event) {
// event.data is the initial this
// call updateSum with correct context
event.data.updateSum.call(event.data);
},
updateSum: function() {
// does something here
}
});
The other possibility is to use closures to define the updateHandler inside the constructor
var MyClass = Class.create({
initElements: function(sumEl) {
this.sumEl = sumEl;
// save this as that so we can access it from the anonymous function
var that = this;
sumEl.keyup(function()
{
that.updateSum();
});
},
updateSum: function() {
// does something here
}
});
This is a working example what one of the other answers tried to do. It works because the anonymous function can always access the variables in the surrounding function -- but it only works if the function is really defined in the function that has "that" as local variable.
It is the famous Javascript idiom you need to use in initElements function:
var that = this;
Later in your handler just refer to that instead of this:
var MyClass = Class.create({
initElements: function(sumEl) {
this.sumEl = sumEl;
var that = this;
sumEl.keyup(this.updateSumHandler);
},
updateSumHandler: function(event) {
that.updateSum();
},
updateSum: function() {
// does something here
}
});
It was covered in great detail in talk by Stuart Langridge on Javascript closures at Fronteers 2008 conference.

Categories