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.
Related
When creating modular JavaScript code for web pages, I often need to attach events to e.g. buttons.
Take the following example code, typically found in an AMD module:
define(function(require) {
var MyObject = function(ele) {
this.ele = ele;
}
MyObject.prototype = {
myfunct: function() {
console.log('myfunct called');
}
}
return MyObject;
});
Later on the page I would do:
$(document).ready(function() {
var button = $('#button'),
myobj = new MyObject(button);
button.click(function() {
myobj.myfunct();
});
});
This works, but still seems to be a bit unclean I think.
For example, I need to create at least one variable in the global namespace to bind a function to a button. Also, when there are many JavaScript powered interactions on a page, the code gets messy – which is something I initially wanted to tackle by using modular JavaScript.
Thats why my idea was to to the event binding inside the prototype:
var MyObject = function(ele) {
self = this;
this.element = ele;
this.init();
}
MyObject.prototype = {
init: function() {
$(this.element).click(function() {
self.myfunct();
});
},
myfunct: function() {
console.log('myfunct called');
}
}
That way, the code outside of the module would look like this:
$(document).ready(function() {
var button = $('#button'),
myobj = new MyObject(button);
});
Is it wise to move the event binding into the prototype? If so, is it okay the way I have done it, or is there a way using init()?
In addition, I've noticed that when there are two buttons on a page, some context is lost – self always refer to the last instance of MyObj.
Why is this happening – I thought with using self = this; I could prevent the context?
Fiddle
Ok, first what's happening with self.
With this line:
self = this;
you are creating a global variable called self that gets overwritten every time your constructor is called. This could have been easily detected if you were using strict mode. Also, if you were using a local variable correctly, your prototype would have no idea what self, so you attempt to use self in the prototype is broken.
I think there are problems with both of your approaches:
The first approach requires too much manual work outside of your MyObject type.
The second approach (if it worked correctly) attaches events to the button as a side effect of calling the constructor. This is confusing to someone using your API because one expects a constructor to create an object, not to modify other existing objects.
As a remedy, I would propose the following approach:
var MyObject = function(ele) {
this.element = ele;
}
MyObject.prototype = {
attachEvents: function() {
var self = this;
$(this.element).click(function() {
self.myfunct();
});
},
myfunct: function() {
console.log('myfunct called');
}
};
$(document).ready(function() {
var button = $('#button'),
myobj = new MyObject(button);
myobj.attachEvents();
});
This requires one extra step on the part of the person instantiating the MyObject, but it clearly conveys the intent of attaching events to myobj's encapsulated elements. It also doesn't require someone using a MyObject to do the intricate maneuvering of your first approach.
Let's start from the second question. The problem with your code is that you declare self as global variable because you forgot/omitted var keyword. As the result when you create two or more instances the last one overwrites previous and self inside all click events points to the last instance.
Check the fixed code. Note that you have to move var self = this to init method, because now it's local variable:
var MyObject = function(ele) {
this.element = ele;
this.init();
}
MyObject.prototype = {
init: function() {
var self = this;
$(this.element).click(function() {
self.myfunct();
});
},
myfunct: function() {
console.log('myfunct called');
}
}
As for the first question, it's alright it's your design and there is nothing wrong with it. Binding events in init method is indeed cleaner.
There's nothing wrong with doing the event binding the way you are. The reason you're losing the scope is because you did
self = this;
which created self as a global variable, in the constructor function. So every time you call the constructor it sets self to that instance.
To fix it, set self as a local variable in your init function:
MyObject.prototype = {
init: function() {
var self = this; // <-- this is the fix
$(this.element).click(function() {
self.myfunct();
});
}
I would like to reference 'this' in a callback function, but there is no guarantee that 'this' will refer to the correct object. Is it appropriate to create a local variable referencing 'this' and use that variable inside the anonymous function?
Example:
var MyClass = function (property) {
this.property = property;
someAsynchronousFunction(property, function (result) {
this.otherProperty = result; // 'this' could be wrong
});
};
The problem is, the asynchronous function may call the provided callback from an arbitrary context (which is often outside of my control, e.g. when using libraries).
My proposed solution is:
var MyClass = function (property) {
this.property = property;
var myClass = this;
someAsynchronousFunction(property, function (result) {
myClass.otherProperty = result; // references the right 'this'
});
};
But I was looking to see if there are other strategies, or if this solution is problematic in any way.
What you've done is the classical way of making sure you refer to the correct object, though you should define it locally, i.e.:
function(property) {
var that = this;
someFunc(function(result) {
that.property = whatever;
}
}
Alternatively, in modern browsers you can bind it explicitly:
someFunc(function(result) {
this.property = whatever;
}.bind(this));
See also: bind()
Libraries such as jQuery support the latter functionality as a proxy function that more browsers support and can be simplified into this reusable function:
function proxy(fn, ctx)
{
return function() {
return fn.apply(ctx, arguments);
}
}
And to use it:
someFunc(proxy(function(result) {
this.property = whatever;
}, this));
Yes, that's fine, but don't use an implicit global variable as you did, use a local variable:
var myClass = 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.
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();
I have the following code example to use an object that receives the action from the callback. Doesn't seem like this is a good design pattern. Or is it?
When setTimeOut() fires on the function after 1 second, it uses the objInstance global variable (DOM scope) to access the ClassExample object instance. Can someone recommend a better way to utilize callbacks within an object oriented design?
The whole idea is so I can use the callback to update data within my object instance (increment a variable for example).
function ClassExample{
this.initiate = function() {
setTimeOut(objInstance.afterTimeOut,1000); //using the objects global handle
}
this.afterTimeOut = function() {
alert("Received!");
}
}
var objInstance = new ClassExample(); //instance
objInstance.initiate();
No, you're not. You'll want to do this:
this.initiate = function() {
setTimeOut(objInstance.afterTimeOut,1000); //using the objects global handle
}
Now, if "afterTimeout" needs the proper object context, you could do this:
this.initiate = function() {
var instance = this;
setTimeout(function() { instance.afterTimeOut(); }, 1000);
}
OK well you changed the question considerably with that little edit :-) If I were you, I'd just do this (like my original second example):
this.initiate = function() {
var instance = this;
setTimeout(function() { instance.afterTimeOut(); }, 1000);
}
Then you don't need any ugly global variables around at all.
edit — Stackoverflow user #Christoph comments that this isn't particularly pretty. One thing that might help would be to use a "bind" facility, as provided by newer browsers natively (as a method on the Function prototype) or by some libraries (Prototype or Functional for example). What "bind" lets you do is create a little wrapper function like I've got above:
this.initiate = function() {
setTimeout(this.afterTimeOut.bind(this), 1000);
}
That call to "bind" returns a function that is effectively the same sort of thing as the little wrapper I coded explicitly in the example.
function ClassExample{
this.afterTimeOut = function() {
alert("Received!");
}; // Don't forget these
setTimeOut(afterTimeOut, 1000); // Don't use () if you're passing the function as an argument
}
var objInstance = new ClassExample(); //instance
That way you don't need the initiate() method.
If you really want the initiate() method, I'd do it like this:
function ClassExample{
var self = this;
self.afterTimeOut = function() {
alert("Received!");
};
self.initiate = function() {
setTimeOut(self.afterTimeOut, 1000);
};
}
var objInstance = new ClassExample(); //instance
objInstance.initiate();
This is how I'd do it to allow timer reuse and minimize the number of closures:
function Timer(timeout, callback) {
this.timeout = timeout;
this.callback = callback;
}
Timer.prototype.run = function(thisArg /*, args... */) {
var argArray = Array.prototype.slice.call(arguments, 1);
var timer = this;
setTimeout(function() {
timer.callback.apply(thisArg, argArray);
}, timer.timeout);
};
var timer = new Timer(1000, alert);
timer.run(null, 'timer fired!');
And just for fun, a golfed version which is functionally equivalent, but replaces the object with a closure:
function delay(func, timeout) {
return function() {
var self = this, args = arguments;
setTimeout(function() { func.apply(self, args); }, timeout);
};
}
delay(alert, 1000).call(null, 'timer fired!');
You are right it is not the optimal way of doing what you are aiming for. however i have to wonder why you need to break the callstack as part of the initiation, it seems very academic.
apart from that if i had to do that, i'd probably use a closure like so:
function ClassExample{
this.initiate = function() {
setTimeOut((function(self) { return function() { self.afterTimeout();}})(this),1000); //using the objects global handle
}
this.afterTimeOut = function() {
alert("Received!");
}
}
var objInstance = new ClassExample(); //instance
objInstance.initiate()
this.initiate = function() {
var instance = this;
setTimeOut(function() {
instance.afterTimeOut();
}, 1000);
};
By saving this to a local variable, you can avoid using the global handle at all. Also this prevent the afterTimeout() from losing it's this.
Building on Znarkus answer...
I really don't know in which environment his code is running but for me the first approach just do not works. I got: 'ReferenceError: afterTimeOut is not defined'...
The second one, nevertheless, is really cool... I just changed setTimeOut for setTimeout (using lowercase 'o') and included parenthesis after the class name definition turning the first line of code into 'function ClassExample(){'; solved my problem.
My snippet of example code:
Oop with private behaviour, intern callback calling and etc.
function MyTry (name){
// keep this object pointer... that's the trick!
var self = this;
// create private variable
var d = new Date()toJSON().slice(0, 10);
// create a private function
function getName(){return name}
// create public access method
self.hello = function(){alert('Hello '+getName()+'!\nToday is: '+d)}
// note instance method hello passed as a callback function!
self.initiate = function(){setTimeout(self.hello, 3000)}
}