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
}
});
});
});
}
}
Related
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.
I have written two functions in JavaScript code as follows
Manager = FormManager.extend({
First: function () {
var response = this.Second("Feature"); //I'm able to get the alert
//I have added a click event handler
$('#element').on('click', function(){
var newResponse = this.Second("Bug"); //The alert is not poping
});
}
Second: function (type) {
alert(type);
//Performs certain operation
}
});
Error: Uncaught TypeError: Object #<HTMLButtonElement> has no method 'Second'
I also tried without using this keyword like:
Second("Bug") // Error: There is no method
Whereas this a simplified format (in-order to show a simple example) on my program that I'm playing with. I'm struggling to find out the reason.
Can someone direct me to the right path?
You are using incorrect this. try this way. this inside the handler represents #element not the context of the function itself.
var self = this; //cache the context here
$('#element').on('click', function(){
var newResponse = self.Second("Bug"); //Access it with self
});
Also i think you are missing a comma after First function definision and before Second function.
Fiddle
The reason being the callback you give gets invoked from within the context of the element so your this context changes. this context refers to the context from where the callback was invoked. But there are other ways to get around this like using $.proxy while binding your callback with jquery, using EcmaScript5 Function.prototype.bind etc. But ideally you don't want to do that because most of the cases you would need the context of the element there inside the handler.
Every time you use the this context variable in a function you have to consider what its value is.
Specifically that value will be whatever value the caller specified, whether by using myObj.mymethod(...), or mymethod.call(myObj, ...), or mymethod.apply(myObj, [ ... ]).
When your anonymous function $('#element').on('click', ...) is invoked jQuery will set the context to the HTML DOM element - it's no longer referring to your object.
The simplest work around is to obtain a copy of this outside of the callback, and then refer to that copy inside the closure, i.e.:
var that = this;
$('#element').on('click', function() {
// use that instead of this, here
console.log(this); // #element
console.log(that); // your object
});
Another method is using Function.prototype.bind:
$('#element').on('click', (function() {
console.log(this); // your object
}).bind(this));
or with jQuery you can use $.proxy for the same effect, since .bind is an ES5 function.
I actually prefer the var that = this method, since it doesn't break the jQuery convention that this refers to the element associated with the event.
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.
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.