I have this class in javascript
(function() {
this.testObject = function() {
/*options*/
this.options = arguments[0];
};
/*make object*/
testObject.prototype.make = function(){
this.targetElement = document.getElementById('testDiv');
this.targetElement.addEventListener('mousedown', function(e){
...
});
this.targetElement.addEventListener('mouseup', function(e){
...
});
this.targetElement.addEventListener('mousemove', function(e){
...
});
};
}());
var test; // I need this to be global
function callObject(){
test = new testObject({...});
test.make();
}
This object binds some events. the instantiation is also inside another function. this is because I have situations that adding new elements to DOM, so calling callObject() for every new element to bind events for it.
But I think there is a performance issue here, it's going slow when I call callObject multiple times. I do not know what is the problem in fact.
so how can I delete an object and all it's binded events?
> var test; // I need this to be global
> function callObject(){
> test = new testObject({...});
> test.make();
> }
In the above, test will only reference the last instance of testObject.
The pattern you're using means that every function on the prototype chain has a closure to the execution context of the outer IIFE, and so does every listener added by the make method. That's inefficient if you don't need the closures. If not, then using an IIFE here isn't suitable, consider using a standard approach (it's convention to give constructors a name starting with a capital letter):
function TestObject() {
/*options*/
this.options = arguments[0];
}
TestObject.prototype.make = function (){
this.targetElement = document.getElementById('testDiv');
this.targetElement.addEventListener('mousedown', function (e){
...
};
this.targetElement.addEventListener('mouseup', function (e){
...
};
...
};
As noted elsewhere, adding listeners using function expressions makes it difficult to remove them later. The above pattern also means that each instance has its own copy of the function. An alternative that solves both these issues is to use references. You might add them as properties of the constructor so that they don't create additional global variables and don't need another object, e.g.
TestObject.mousedown = function (e){ ... };
TestObject.mouseup = function (e){ ... };
TestObject.prototype.make = function(){
var TO = TestObject;
this.targetElement = document.getElementById('testDiv');
this.targetElement.addEventListener('mousedown', TO.mousedown, false);
this.targetElement.addEventListener('mouseup', TO.mouseup, false);
...
};
Which avoids a lot of closures and unnecessary copies of functions and means listeners can be removed by name. And you might want to make the test global an object or array so you can keep references to all instances of TestObject, not just the last one.
There are a few things to consider. First, and most importantly, your event listeners have anonymous functions. You CAN'T unbind a listener when you give it an anon function. So go ahead and make actual functions for those. Then you can call removeEventListener the same way you called addEvent... and it will detach those listeners.
What I normally do is make a destroy function that removes all listeners and sets any global vars to null. Then you can call that destroy function whenever you need to.
Related
I am beginning to define classes in JavaScript, and I have a lot of troubles with the keyword this.
Here is an example of what I want to do:
function MyMap() {
this.map = new google.maps.Map(....);
google.maps.event.addListener(this.map, 'idle', function() {
this.mapIdle(); // PROBLEM: "this" undefined
});
this.mapIdle = function() {
google.maps.event.addListener(marker, 'click', function() {
$("button").click(function() {
$.ajax({
success: function() {
this.map.clearInfoWindows(); // PROBLEM: "this" undefined
}
});
});
});
}
}
As you can see in the comments, this won't work here, because it is used inside a closure.
I have started using workarounds like:
var that = this;
google.maps.event.addListener(this.map, 'idle', function() {
that.mapIdle();
});
Or even where you have to define a callback function around your callback function (seriously!!).
This is extremly ugly and doesn't work everywhere. When I get a lot of nested lambda functions (as in the example I gave), I have no idea how to use a class attribute.
What is the best and most correct way to do that?
The easiest way is to define a self variable (or that, if you don't mind a non-semantic variable name) as you already mentioned:
function MyMap() {
var self = this;
// in nested functions use self for the current instance of MyMap
}
Noting that you have to do it again for methods you add to the prototype (if they use nested functions):
MyMap.prototype.myMethod = function() {
var self = this;
// use self in nested functions
};
You should also read up on the .bind() method, noting that it doesn't work for IE <= 8.
The question you linked to about defining a callback around your callback is to solve a different problem, i.e., setting up an appropriate closure structure to allow functions that are nested inside a loop to have access to the appropriate loop counter value(s). That has nothing to do with the this issue. (And can easily be combined with the self technique when needed.)
If you're using jQuery, $.proxy is handy for this:
http://api.jquery.com/jQuery.proxy/
You can just assign the object and the function to local varibles, then they are included in the closures for the callback functions:
function MyMap() {
var map = new google.maps.Map(....);
var mapIdle = function() {
google.maps.event.addListener(marker, 'click', function() {
$("button").click(function() {
$.ajax({
success: function() {
map.clearInfoWindows();
}
});
});
});
};
this.map = map;
this.mapIdle = mapIdle; // Is this needed?
google.maps.event.addListener(this.map, 'idle', function() {
mapIdle();
});
}
In JavaScript, this is reassigned with every single function call.
It's just something you have to be aware of in JavaScript. It can be confusing at first, but once you know a few simple rules, it's actually pretty straightforward.
If it's a method call like myObj.doSomething(), then this will be automatically set to myObj inside of doSomething().
If you want to explicitly control the value of this when making a function call, you can use doSomething.apply() or doSomething.call() to control what this is set to inside the function. That's what event handler callbacks do. They explicitly set this to point to the object that created the event (something which is very useful). You can read more about .apply() and .call() on MDN.
If you just call a regular function, then this will be set to the global object which in a browser is the window object.
All callback functions will have their value of this messed with because every function call changes this. Since your event handler is a callback and the success handler is a callback in the Ajax function, you should expect that the value of this will not be preserved from the surrounding code. There are work-arounds using proxy or bind functions, but usually it's just as easy to capture the previous value of this in a closure and just access it from there with something like var self = this;.
In your circumstance, when you want access to a this pointer from outside the event handler, the right thing is to just save it to a local variable that you will have access to in the event handler or even in the Ajax call that is in the event handler. There is no cleaner way to do it. This way you have access to both the this pointer from the event or the Ajax call and the this pointer from your calling object like this:
var self = this;
self.mapIdle = function() {
google.maps.event.addListener(marker, 'click', function() {
$("button").click(function() {
$.ajax({
success: function() {
self.map.clearInfoWindows(); // PROBLEM: "this" undefined
}
});
});
});
}
}
For a particular listener in my application, I'm using the following code for scope-busting purposes:
// this is all in a prototype of MyClass
var self = this;
myElement.addEventListener("stuff", function(e){self.doStuff(e)});
That will get doStuff to have the desired this binding.
The problem shows up when I try to removeEventListener. I suppose it's because the native function signatures must be different?
// in a different prototype of MyClass
var self = this;
myElement.removeEventListener("stuff", function(e){self.doStuff(e)}); // doesn't work
If I make a separate function that contains all of my scope-busting code, then the this binding in that code will be to the unwanted object of myElement. So the question is: How can I force listener scope and still be able to remove an added event listener?
*note using global / static variables in any way is prohibited due to the nature of the project (otherwise this would be simple!)
This has nothing to do with scope or the way in which you're storing a reference to this. The problem is that removeEventListener expects a reference to a function that's previously been registered as a listener, but you're giving it a brand new function it's never seen before.
You need to do something like this:
var self = this;
var listener = function(e){self.doStuff(e)}
myElement.addEventListener("stuff", listener);
// later
myElement.removeEventListener("stuff", listener);
It doesn't matter that the bodies of your two functions are the same; they're still different functions.
See:
https://developer.mozilla.org/en/DOM/element.removeEventListener
Inline anonymous functions are a very bad practice anyway, so I will suggest the obvious:
function MyClass() {
this.onStuff = this.onStuff.bind(this); //Each instance steals the prototyped function and adds a bound version as their ownProperty
}
MyClass.prototype = {
onStuff: function (e) { //Prototyped, no instance actually uses this very function
this.dostuff()
},
bind: function () {
myElement.addEventListener("stuff", this.onStuff);
},
unbind: function () {
myElement.removeEventListener("stuff", this.onStuff);
}
}
see removeEventListener on anonymous functions in JavaScript
You can't removeEventListener as your using an anonymous function.
I'm trying to do a custom tabs system using JavaScript. However, in order to reuse the code I want to write it in OOP style. This what I have so far:
function Tabs()
{
this.tabArray = new Array(arguments.length);
this.tabArrayC = new Array(arguments.length);
for(i=0;i<arguments.length;i++)
{
this.tabArray[i] = arguments[i];
this.tabArrayC[i] = arguments[i]+'_content';
}
this.setClickable = setClickable;
function setClickable()
{
for(i=0;i<this.tabArray.length;i++)
{
document.getElementById(this.tabArray[i]).onclick = function()
{
alert(this.tabArray[i]);
}
}
}
}
function init()
{
tab = new Tabs('tab1','tab2','tab3','tab4');
tab.setClickable();
}
window.onload = init();
Now here's the deal. I want to assign the onclick event handler to every tab that has been passed in Tabs 'class' constructor. So later in the code when I write something like:
<div id="tab1">Tab1</div>
<div id="tab2">Tab2</div>
<div id="tab3">Tab3</div>
<div id="tab4">Tab4</div>
The code which has been set up earlier:
document.getElementById(this.tabArray[i]).onclick = function()
{
alert(this.tabArray[i]);
}
... would be executed. I hope I explained that well enough. Any ideas?
There are three issues with your setClickable function (edit: and an issue with how you're calling init):
this will have a different meaning within the event handler you're generating than you expect. (More here: You must remember this)
A closure (a function that closes over data like your i variable) has an enduring reference to the variable, not a copy of its value. So all of the handlers will see i as of when they run, not as of when they're created. (More here: Closures are not complicated)
You're not declaring i, and so you're falling prey to the Horror of Implicit Globals.
Here's one way you can address those:
function setClickable()
{
var i; // <== Declare `i`
var self = this; // <== Create a local variable for the `this` value
for(i=0;i<this.tabArray.length;i++)
{
// v=== Use a function to construct the handler
document.getElementById(this.tabArray[i]).onclick = createHandler(i);
}
function createHandler(index)
{
// This uses `self` from the outer function, which is the
// value `this` had, and `index` from the call to this
// function. The handler we're returning will always use
// the `index` that was passed into `createHandler` on the
// call that created the handler, so it's not going to change.
return function() {
alert(self.tabArray[index]);
};
}
}
...and as goreSplatter and Felix point out, this line:
window.onload = init();
...calls the init function and uses its return value to assign to onload. You mean:
window.onload = init;
...which just assigns init to the onload event.
Off-topic: You might consider using the newer "DOM2" mechanisms for attaching event handlers instead of the old "DOM0" way of using the onXYZ properties and attributes. The new way is called addEventListener, although sadly Internet Explorer has only recently added that (but it has attachEvent which is very similar). If you use a library like jQuery, Prototype, YUI, Closure, or any of several others, they'll smooth out those differences for you (and provide lots of other helpful stuff).
Problematic:
function init()
{
tab = new Tabs('tab1','tab2','tab3','tab4');
tab.setClickable();
}
window.onload = init();
In this case, window.onload will be undefined, since init() returns nothing. Surely you meant
window.onload = init;
?
function FakeClass(){};
FakeClass.prototype.someMethod = function(){};
FakeClass.prototype.otherMethod = function(){
//need to call someMethod() here.
}
I need to call someMethod from otherMethod, but apparently it doesn't work. If i build it as a single function (not prototyped), i can call it, but calling a prototyped does not work. How can i do it as if i was treating the function just like a class method?
Update:
I'm calling the method after a jQuery event is triggered. Does it affect the way the whole thing behaves?
function CicloviarioEngine(){};
CicloviarioEngine.prototype.test = function(){
alert("Hey");
}
CicloviarioEngine.prototype.initialize = function(){
$('#add-route').click(function(){
this.test(); //doesn't work
CicloviarioEngine.test(); //doesn't work
externalTest(); //works
});
}
function externalTest(){
alert("externalTest()");
}
this inside the event handler function is not the same as this in the enclosing initialize function (in fact it will be a reference to the element that has been clicked). The easiest way to deal with this is to save the value of this into a variable, which the event handler will have access to from its enclosing scope:
CicloviarioEngine.prototype.initialize = function() {
var that = this;
$('#add-route').click(function(){
that.test();
});
};
The members of the prototype will be available on the object instance, so you can simply call the method using the this keyword:
FakeClass.prototype.otherMethod = function(){
this.someMethod();
};
Check an example here.
Problem & Reason
One of my team mate ended up in messy situtaion implementing function hooking in javascript. this is the actual code
function ActualMethod(){
this.doSomething = function() {
this.testMethod();
};
this.testMethod = function(){
alert("testMethod");
};
}
function ClosureTest(){
var objActual= new ActualMethod();
var closeHandler = objActual.doSomething;
closeHandler();
closeHandler.apply(objActual,arguments); //the fix i have added
this.ActualTest = function() {
alert("ActualTest");
};
}
In the above code, var closeHandler is created in the context of ClosureTest(), but it holds the handler of the ActualMethod.doSomething. Whenever calling the closeHandler() ended up in "object doesnt support this method" error.
This is because doSomething() function calls another method inside called this.testMethod();. Here this refers to the context of the caller not callee.so i assume the closeHandler is bound to the environment(ClosureTest) actually created.Even though it holds the handler to the another context, it just exposes the properties of its own context.
Solution
To avoid this i suggest to use apply to specify the conext in which it needs to execute.
closeHandler.apply(objActual,arguments);
Questions
is it perfect scenario for closures..??
What are the intersting places you have encountered closures in javascript..?
UPDATE
Yes its simple i can call the method directly. but the problem is, in a particular scenario I need to intercept the call to actuall method and run some code before that, finally execute the actual method..
say for an example, am using 3rd party aspx grid library, and all the mouseclick events are trapped by their controls. In particular group by mouse click i need to intercept the call to their ilbrary method and hook my mthod to execute instead and redirect the call to actual library method
hope this helps
Update: Because you probably left out some details in your code, it is difficult to adapt it into something workable without missing the point of your actual code. I do think I understand your underlying problem as you describe it. I hope the following helps.
Suppose the following simple example:
// Constructor function.
function Example() {
// Method:
this.method = function() {
alert("original method");
}
}
// You would use it like this:
var obj = new Example();
obj.method(); // Calls original method.
To intercept such a method call, you can do this:
function wrap(obj) {
var originalMethod = obj.method;
obj.method = function() {
alert("intercepted call");
originalMethod.apply(this, arguments);
}
return obj;
}
var obj = wrap(new Example());
obj.method(); // Calls wrapped method.
Unfortunately, because method() is defined in the constructor function, not on a prototype, you need to have an object instance to wrap the object.
Answer to original question: The doSomething() function is used as a method on objects created with ActualMethod(). You should use it as a method, not detach it and use it as a function in a different context. Why don't you just call the method directly?
function ClosureTest(){
var objActual = new ActualMethod();
// Call method directly, avoid messy apply() calls.
objActual.doSomething();
this.ActualTest = function() {
alert("ActualTest");
};
}
If you assign a method (a function on some object) to a local variable in Javascript and call it, the context will be different (the value of this changes). If you don't want it to happen, don't do it.
When I want to hook a function, I use the following Function method which is also a fine piece of Closure demonstration:
Function.prototype.wrap = function (wrapper) {
var __method = this;
return function() {
var __obj = this;
var args = [ __method.bind(__obj) ];
for(var i=0; i<arguments.length; i++) args.push(arguments[i]);
return wrapper.apply(__obj, args);
}
};
Then do something like:
ActualMethod = ActualMethod.wrap(function (proceed, option) {
// ... handle option
proceed(); // calls the wrapped function
});
proceed is bound to its initial object, so you can safely call it.