This may be a bit abstract but I'm trying to get my head round JavaScript closures etc. Take the following code:
function MyObj() {
var me = this;
this.foo = function(bar) {
// Do something with 'bar'
}
// Set up lots of local variables etc.
// ....
$(window).load(function() {
// Add a delegated click handler to specific <input> elements
$(document).on('click.myobj', 'input.special', function() {
// Do something with the <input> that triggered the click event
me.foo(this);
});
});
}
var myObj = new MyObj();
The anonymous function passed to that is bound to the click event creates a closure that references me. What I want to know is whether it's better to do something like this instead (to avoid the closure):
$(window).load(function() {
// Add a delegated click handler to specific <input> elements
(function(localMe) {
$(document).on('click.myobj', 'input.special', function() {
// Do something with the <input> that triggered the click event
localMe.foo(this);
});
})(me);
});
Is this a better approach, or am I being overly paranoid about creating a closure? Alternatively, is there a "third way"?
EDIT
Additionally, would it be better to do something like this:
$(window).load(function() {
// Add a delegated click handler to specific <input> elements
$(document).on('click.myobj', 'input.special', {localMe : me}, function(event) {
// Do something with the <input> that triggered the click event
event.data.localMe.foo(this);
});
});
The latter is (AFAIK) more efficient, but probably not measurably so unless used in a tight loop.
The reason is that all variable dereferencing must follow the scope chain. In the latter case, the variable localMe can be found in the anonymous function's parameter list.
In the former case, the variable isn't found there, but in the outer scope. This traversal up the scope chain takes extra time.
Anonymous functions are massively used in javascript now (as arguments and as an immediate function for scopes/closures). There's no performance problem with that.
But you can have a problem of code reading maybe. Because when you see a variable, you must check where the variable is from. But no big deal here.
And in your second example, you still have a closure "break". Because in your anonymous function in the click, you use the localMe variable. And the localMe is an argument of a fonction outside of your fonction.
// Here, 'me' is a direct local variable.
$(window).load(function() {
// Here, we are in an anonymous fonction, so 'me' is not a direct variable anymore. But you still can access it.
// Add a delegated click handler to specific <input> elements
(function(localMe) {
// Here, 'localMe' is a direct local variable.
$(document).on('click.myobj', 'input.special', function() {
// We are in an anonymous function, so 'localMe' is not a direct variable anymore.
// Do something with the <input> that triggered the click event
localMe.foo(this);
});
})(me);
});
If you really want to avoid a closure "break", you should bind your function to your object. But note that not every browser support the bind method on functions.
You will always create a closure if you bind the event from the constructor. In fact, you even need the closure to preserve the reference to your instance. However, you might do something like this:
function MyObj() {
this.foo = function(bar) {
// Do something with 'bar'
}
// Set up lots of local variables etc.
// ....
}
var myObj = new MyObj();
$(function() {
$(document).on('click.myobj', 'input.special', function() {
myObj.foo(this);
});
});
If you do only create a singleton instance of your constructor, it won't matter anyway.
I would probably do it this way:
var bind = function( fn, me ) { return function() { return fn.apply(me, arguments); }; },
Object = (function() {
function Object() {
this.handler = bind(this.handler, this);
// Add a delegated click handler to specific <input> elements.
$(document).on("click.myobj", "input.special", this.handler);
}
Object.prototype.foo = function( bar ) {
// Do something with "bar".
};
Object.prototype.handler = function( event ) {
// Do something with the <input> that triggered the click even.
return this.foo(event.currentTarget);
};
return Object;
})();
var obj = new Object();
This skips the uses of closures and iifes, using .apply instead. Not sure if it is more efficient or not, but it is another option.
Related
On the setClickEvents funtion in the below javascript object when I try to use the this element inside the addEventListener it doesn't work, I have to use tabUI, the object itself.
I'm not sure if it's a good practice what I'm doing but it works.
What can I do for improving it?
var tabUI = {
leftTab: undefined,
rightTab: undefined,
setTabs: function(leftTabId, rightTabId) {
this.leftTab = document.getElementById(leftTabId);
this.rightTab = document.getElementById(rightTabId);
},
setClickEvents: function() {
this.leftTab.addEventListener('click', function() {
// This works
tabUI.leftTab.classList.add('tab_selected');
tabUI.rightTab.classList.remove('tab_selected');
// This doesn't work
this.leftTab.classList.add('tab_selected');
this.rightTab.classList.remove('tab_selected');
});
this.rightTab.addEventListener('click', function() {
tabUI.leftTab.classList.remove('tab_selected');
tabUI.rightTab.classList.add('tab_selected');
});
},
}
this inside the event handler is the element on which the event has occurred.
You can solve your problem using any of the following two ways. I'd recommend you to use the second way, so that you can access the elements on which event has occurred.
bind context
Changing the context of the function regardless of how it'll be called.
The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.
this.leftTab.addEventListener('click', function() {
// This works
tabUI.leftTab.classList.add('tab_selected');
tabUI.rightTab.classList.remove('tab_selected');
// This also works here
this.leftTab.classList.add('tab_selected');
this.rightTab.classList.remove('tab_selected');
}.bind(this));
// ^^^^^^^^^^
Cache context/this
Cache the previous context and then, that can be used in the event handler.
var that = this; // Cache this here
// ^^^^^^^^^^^^^
this.leftTab.addEventListener('click', function() {
// This works
tabUI.leftTab.classList.add('tab_selected');
tabUI.rightTab.classList.remove('tab_selected');
// That works here
that.leftTab.classList.add('tab_selected');
// ^^^
that.rightTab.classList.remove('tab_selected');
// ^^^
});
The this object isn't tabUI, as you seem to have noticed. What it is is the element the event is running on: event handlers have their context set to the element they run on.
See https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#The_value_of_this_within_the_handler
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'm trying to call a function within an object literal that I created, using the this keyword. But an error shows up saying this.doTheMove() is not a function:
window.onload = function(){
var animBtn = document.getElementById('startAnim');
animBtn.addEventListener('click', Animation.init, false);
}
var Animation = {
init: function(){
this.doTheMove(); // I'm calling the function here, but it gives an error.
},
doTheMove: function(){
alert('Animation!');
}
}
Why is there an error?
An explanation of what's happening. Pointy's answer is good but I want to explain it more generically. A very good research on this can be found here
An event handler is just a callback. You pass it a function and an event to listen on. Interally all it will do is call that function.
Animation.init is just a getter for that function. Think of it like this:
var callback = Animation.init
animBtn.addEventListener('click', callback, false);
...
// internal browser event handler
handler() {
// internal handler does stuff
...
// Oh click event happened. Let's call that callback
callback();
}
So all you've done is passed in
var callback = function(){
this.doTheMove(); // I'm calling the function here, but it gives an error.
}
By default in javascript this === window. This will refer to the global object if it isn't set to something. The net effect is that window.doTheMove is called. And that function doesn't exist.
In this case since callback is actaully called by an event handler the this object points at the DOM object that triggered the event so your calling node.doTheMove which still doesn't exist.
What you wanted to do is wrap it with a reference to Animation.
var callback = function() {
Animation.init();
}
This is a function execution and it executes init on Animation. When you execute it on an object like that then internally this === Animation as you would expect.
To sum up. The issue here is that Animation.init is just a reference to a function. It has no information about anything else like Pointy mentioned.
You have to change the way you set that up:
window.onload = function(){
var animBtn = document.getElementById('startAnim');
animBtn.addEventListener('click', function() { Animation.init(); }, false);
}
In JavaScript, the fact that a function happens to be defined as part of an object literal really doesn't mean very much (if anything, in fact). The reference to Animation.init does get you to the proper function, but the problem is that when the function is later invoked (in response to an actual "click"), the browser calls the function but has no idea that the object "Animation" should be the this reference. Again, the fact that the function was declared as part of the object is of no importance at all here. Therefore, if you want this to be something in particular of your own choosing, then you have to make sure it's set explicitly in code you control. The solution above is about the simplest way to do it: it handles the "click" events with an anonymous function that does nothing other than invoke the "init" function via an explicit reference through "Animation". That will ensure that this refers to the "Animation" object when "init" runs.
Another alternative would be to use the ".bind()" facility that some browsers and frameworks support:
window.onload = function(){
var animBtn = document.getElementById('startAnim');
animBtn.addEventListener('click', Animation.init.bind(Animation); }, false);
}
The net effect is almost exactly the same: that call to ".bind()" returns a function that invokes the function on which it was called (that being the "init" function in the "Animation" object), and does so with its first argument as the this reference (the "context" object). That's the same thing that we get from the first example, or effectively the same anyway.
Here's another nice approach, I think.
window.onload = function(){
var animBtn = document.getElementById('startAnim');
animBtn.addEventListener('click', Animation.init, false);
};
var Animation = {
init: function(){
Animation.doTheMove(); // This will work, but your IDE may complain...
},
doTheMove: function(){
alert('Animation!');
}
};
You might want to use the portotype base approach:
// generate a prototype object which can be instantiated
var Animation = function() { this.doTheMove(); }
Animation.prototype.doTheMove = function() {
// if the object has only one method, the whole code could be moved to
// var Animation = function() {...} above
alert('Animation!');
}
Animation.prototype.otherMethod = function(param1, param2) {
// ...
}
// run the code onload
window.onload = function(){
var animBtn = document.getElementById('startAnim');
animBtn.addEventListener('click', new Animation(), false);
}
Six and a half years later, but I'm hoping my answer can also provide some insight for current and future developers.
I tend to code using literal objects inside of self defined functions, and the original question posted works just fine if another self-executing function is added along with a try and catch statement.
It's very important to point out that it's all about scope and context.
Please correct any drawbacks or provide more effective suggestions of using this method.
(function() {
console.log(this); // window object
var animation = {
init: function() {
this.doTheMove();
},
doTheMove: function() {
alert("Animation");
console.log(animation); // animation object
}
};
(function() {
try {
console.log("animation.init"); // animation.init function
animation.init();
} catch(e) {
console.log("Error is: " + e);
}
})();
})();
I am developing an add-on for Firefox (3.6.*). in the following code notify called from inside init works fine, but I get an error saying this.notify is not a function when it is called from within onPageLoad. Why is that?
Also when I change the call to myextobj.notify('title', 'msg'), it works. The same is true for accessing variables. So, what is the difference between this and the object name as a prefix?
var myextobj = {
init: function() {
this.notify('init', 'We are inside init');
...
var appcontent = document.getElementById("appcontent"); // browser
if(appcontent)
appcontent.addEventListener("DOMContentLoaded", this.onPageLoad, true);
},
onPageLoad: function(aEvent) {
this.notify('onPageLoad', 'We are inside onPageLoad');
...
},
notify: function (title, text) {
Components.classes['#mozilla.org/alerts-service;1'].
getService(Components.interfaces.nsIAlertsService).
showAlertNotification(null, title, text, false, '', null);
}
};
window.addEventListener("load", function() { myextobj.init(); }, false);
When you do this:
appcontent.addEventListener("DOMContentLoaded", this.onPageLoad, true);
you just add the function that is hold in onPageLoad as event handler. The connection to the object is lost and this will refer to the global object when executed.
Just create an anonymous function as you do for the load event:
var that = this; // capture reference to object
appcontent.addEventListener("DOMContentLoaded", function(event) {
that.onPageLoad(event);
// myextobj.onPageLoad(event); should also work in this case
}, true);
Remember that functions are first class objects in JavaScript, they can be passed around like any other value. Functions have no reference to an object they are defined on, because they don't belong to that object. They are just another kind of data.
To which object this refers to in a function is decided upon execution and depends on the context the function is executed in. If you call obj.func() then the context is obj, but if you assign the function to another variable before like var a = obj.func (that is wat you do with adding the event handler (in a way)) and then call a(), this will refer to the global object (which is window most of the time).
When onPageLoad is called for the event, 'this' would not be referring to your myextobj. Because it wasn't called in the context of your object myextobj.
The way I deal with this is, by having all member functions of an object using the following convention.
var myObj = {
.....
counter: 0,
.....
myFunction: function () {
var t = myObj;
t.myOtherFunc();
},
....
myOtherFunc: function() {
var t = myObj;
t.counter++;
}
};
See how, I'm aliasing myObj as t, to save on typing and making my intent of using this clear.
Now you can call your methods safely from any context without worrying about what this would be referring to. Unless you really want the standard behavior; in that case, you may like to look at the call and apply methods. This link might help: Function.apply and Function.call in JavaScript
You may also want to look at a recent addition to JavaScript (would be available in FireFox 4): the bind method: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind
Another link, which directly addresses your problem: https://developer.mozilla.org/en/DOM/element.addEventListener#The_value_of_this_within_the_handler
The other way to add an event listener without losing track of this is to pass this itself as the event listener. However you are limited in that the function is always called handleEvent, so it's less useful if you have many listeners (unless they are all for different events, in which case you can switch on the event's type).
This is a snippet from a prototype class i am putting together. The scoping workaround feels a little hacky to me, can it be improved or done differently?
var myClass = Class.create({
initialize: function() {
$('formid').getElements().each(function(el){
$(el).observe("blur", function(){
this.validateEl(el);
}.bind(this,el));
},this);
},
validateEl: function(el){
// validate $(el) in here...
}
});
Also, it seems to me that i could be doing something like this for the event observers:
$('formid').getElements().invoke("observe","blur" ...
Not sure how i would pass the element references in though?
You can indeed simplify that a fair bit:
var myClass = Class.create({
initialize: function() {
var self = this;
// To demo that we keep the instance reference, set a
// property on the instance
this.message = "Hi there";
$('formid').getElements().invoke("observe", "blur", blurHandler);
function blurHandler() {
// `self` references our instance
// `this` references the element we're observing
self.validateEl(this);
}
},
validateEl: function(el){
// validate $(el) in here...
// note that because of the way we called it, within this function,
// `this` references the instance, so for instance:
this.foo(); // alerts "Hi there!"
},
foo: function() {
alert(this.message);
}
});
That uses invoke (as you suggested) and a named function for the handler (doesn't have to be named, but I find that it's very helpful to have your functions have names). The handler is a closure. In the initialize function, I use a variable to point to this because the variable will then be available to the closure. (I called it self because that's a standard practice when aliasing this for this reason.) The handler makes use of Prototype's native functionality of setting this within an event handler to the element being observed. When we call validateEl via the closure's self reference, we're calling it as a member function as per normal, so within validateEl, this refers to the instance.
Speaking of named functions, your initialize and validateEl functions are both anonymous, which means on call stacks and such in debuggers, you'll just see "(?)" rather than a nice, handy name. I always recommend actual named functions; more here.
I can't see nothing wrong with your code :)
About observers you can to something like this:
$('formid').getElements().invoke("observe","blur", function(e) {
this.validateEl(e.element());
}.bind(this));
I think that it will look slightly less verbose if you create a registerBlur method:
var myClass = Class.create({
initialize: function() {
$('formid').getElements().each(this.registerBlur, this);
},
registerBlur: function(el) {
Event.observe(el, 'blur', this.validateEl.bind(this, el));
},
validateEl: function(el){
// validate $(el) in here...
}
});
But I agree with Rui Carneiro, I don't see anything wrong with your code either.