I've heard that it's possible to do something like this
this[func].apply(this, Array.prototype.slice.call(arguments, 1));
But to have access to the arguments object I need to be inside that function.
So, if I am, for example, running a code in the function function1, is there a way to get the arguments of a different function function2?
The whole problem is that I want to add events to any given element. But this element might already have another event attached to it. so, if I have, for example, an 'onclick' event attached to an element, I would do the following:
var event = 'onclick';
if(typeof currentElement[event] === 'function'){
cf = currentElement[event];
f = function(){
cf();
func();
}
}else f = func;
currentElement[event] = f;
Now both functions, the new function and the previous function, are being called. The problem is that the arguments being passed to the previous function were lost with this method.
Does anyone know if it is possible to not lose the arguments when we call a function dynamically like in my example?
OBS: jQuery is not an option :/
Sounds like using addEventListener would work better for you. It lets you attach multiple listeners (functions) to a single element:
elem.addEventListener("click", function() {
console.log("click listener 1");
});
elem.addEventListener("click", function() {
console.log("click listener 2");
});
Note, according to MDN, addEventListener is supported in IE >= 9.
If you did want to continue down your current path, you could do:
f = function(){
cf.apply(this, arguments);
func.apply(this, arguments);
}
For you specific case, it's not necessary to figure out what arguments are passed to the function since you know what it is - the event object in all browsers except (older) IE where its nothing (undefined).
So the code can simply be:
var event = 'onclick';
if(typeof currentElement[event] === 'function'){
cf = currentElement[event];
f = function(e){
cf(e);
func(e);
}
} else f = func;
currentElement[event] = f;
If possible use addEventListner and attachEvent as fallback to play nicely with other scripts that may run on your page.
Additional discussion:
In general, it is never necessary to figure out what has been passed to the callback function because that doesn't even make sense. The programmer cannot determine what to pass to the callback, it's the event emitter that determines what gets passed. In this case its the event object but it's a general rule.
Say for example we have an API that queries our database and returns some data:
function my_api (query) {}; // returns an object where a callback may
// be attached to the oncomplete property
The API documentation mentions:
my_api() returns an API object.
API object - has one property - oncomplete where a callback may be attached to handle data returned from the api call. The callback will be called with one argument - the returned data or no argument (undefined) if an error occured.
OK. So we use it like this:
var db = my_api('get something');
db.oncomplete = function (data) {alert(data)};
Now, if we want to wrap that oncomplete event in another event handler, we don't need to know what data it accepts since it's the API object that determines what to pass to the function, not the function! So we simply:
var tmp = db.oncomplete;
db.oncomplete = function (x) {
new_callback(x);
tmp(x);
}
We get the x argument not from querying the previous callback but from the documentation.
somefunction.arguments //Use the arguments property to get arguments of another function
Related
I have seen several examples of java script functions with parameters passed which are not located in the script but are implicitly passed in. For example:
function myFunction(xml) {
var xmlDoc = xml.responseXML;
document.getElementById("demo").innerHTML =
xmlDoc.getElementsByTagName("title")[0].childNodes[0].nodeValue;
}
Where is the "xml" defined or listed? Where can i find a listing of other implicit parameters?
I've also seen a function with the following:
$("body").click(function (event) {
// Do body action
var target = $(event.target);
if (target.is($("#myDiv"))) {
// Do div action
}
});
Where is "event" coming from or listed?
Thanks in advance.
These variables are called (function) parameters. This is a common feature of most programming languages. They are defined with a function, and simply serve as variables that are defined within the function. They do not need to be defined outside of the function previously, because they exist only for the function.
I believe you're confused because they're not declared with var (as they shouldn't be) because you're calling them "implicit variables." However, they are not implicit; they are defined with the function.
You can find the parameters to a function by looking at the documentation for the function, if you are using a library like jQuery. For example, the .click() function handler is defined like:
(If you can't see the image, it shows .click(handler), where handler is of Type: Function(Event eventObject))
As you can see, it defines the function parameter eventObject which you can "pass" in when you invoke a function. You can use any valid variable name to do so.
You can see this MDN documentation for more information on parameters.
Where is the "xml" defined or listed? Where can i find a listing of other implicit parameters?
Is listed in the very function definition. When I define a function like:
function greet( name , greeting ){
console.log('hi ' + name );
console.log(greeting);
}
name and greeting vars are just defined within the parenthesis in the function definition. You can just call that function passing literals or variables:
greeting('peter' , 'have a nice day');
//or:
var name = 'Francisco';
var greeting = 'qué pasa hombre';
greet(name , greeting);
In the second example, name and greeting vars happen to be called exactly like the internal function parameters. That is just by case, could be too:
var theAame = 'Francisco';
var theGreeting = 'qué pasa hombre';
greet(theName , theGreeting);
And would work exactly the same. Also, in javaScript, you can just pass more parameters to a function than the parameters actually defined in the function, and access them with the arguments keyword or the ES6 spread syntax.
This is javaScript bread and butter and any search on how does arguments and parameters work in javaScript will be useful to you. However, your second question is more tricky.
You're also asking about this kind of code:
$("body").click(function (event) {
// Do body action
var target = $(event.target);
if (target.is($("#myDiv"))) {
// Do div action
}
});
This is similar, but also is more complex. Here $("body").click() is a function that takes a parameter. This parameter happens to be another function. This is a feature not supported in some languages, but is pretty straightforward in javascript. You could also wrote that code this way:
function reactToClick (event) {
// Do body action
var target = $(event.target);
if (target.is($("#myDiv"))) {
// Do div action
}
}
$("body").click( reactToClick );
But, who is then calling that reactToClick function with that event parameter? Well, in this case, the browser does.
Any browser has some API's to register to events -like clicks- with function callbacks, and $.click() is just some syntactic helper over that mechanism. Since is the browser who is ultimately calling the function, is difficult to fully understand the internals -and I must admit I don't-.
However, you can set up your own non-browser-api-dependant javaScript code that invoke callbacks, and the parameters set up and function invocations works the same way:
function theCallback( name , options ){
console.log('Im a callback function fired by someCallbackRegister whenever its fire methods is called');
console.log('my callbackRegister name is: ' + name);
console.log('and the options provided in this call are: ' + options);
}
function someCallbackRegister( callback , registerName ){
return {
fire : function(options){
callback(registerName , options );
}
}
}
var listener = someCallbackRegister( theCallback , 'Johhny');
listener.fire({ foo : 'bar'});
In this example, is the listener who is invoking theCallback after it's fire method call, and setting up all the parameters to that theCallback function properly, just like the browser manages to pass an event object to the callback function you pass to $.click().
Hope this helps :-)
PS: This video about the javaScript event loop helped me a lot to understand how the browser api's work.
function myFunction(xml) {
}
Whoever invokes this myFunction will pass the details which will be saved to variable xml. It's JS language syntax - you don't need to define the type of variable here unlike Java.
Similarly, when you do this:
$("body").click(function (event) {
});
JS internally registers a callback method whenever the body is clicked. It internally passes the event details to the function. You can do console.log(event) and see what all details are listed there
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.
I'm trying to setup a function that stores a callback which can be fired later on an event, but I'm not getting the context to bind right. The setup is basically like this:
app.myObject = function(){
this.bindEvents();
};
app.myObject.prototype = {
bindEvents: function(){
var self = this;
this.library = new OutsideLibrary();
this.library.onMyEvent = function(data) {
OtherLibrary.processData(data, self.runCallback); // I'm not able to pass a context here...
}
},
sendToLibrary: function(message,callback) {
this.callback = callback;
this.library.send(message);
}
// ... and this doesn't work because it gets evaluated in the wrong context.
runCallback: function() {
if (this.callback) {
this.callback();
this.callback = null;
}
}
}
I hope the above code makes sense. The idea is, I'm trying to expose an API where I can call:
app.myObject.sendToLibrary("something", function() {...});
...and the callback function will run after the whole chain is complete.
The problem is, I can't pass the callback that is received in sendToLibrary directly through, because the callback need to be passed to another object when an event fires. Also since I'm having to store the currently passed callback, and the callback is optional, I need to unset it once it has fired, so I actually need to pass the runCallback function to run the callback once then unset it.
Anyway, the problem is that when runCallback eventually gets executed it is no longer in the correct context, so it doesn't work. My first idea for how to fix this was to change the runCallback function to include a context argument, so I could pass self.runCallback(self) -- but that executes immediately so it defeats the purpose.
Anyway, I'm sort of lost in the callback woods here, and would appreciate some help finding my way out :)
My question is: is there a way to wrap up a callback function and the correct context, without executing it now, so that when it eventually fires it does so from the correct context?
Thanks!
OtherLibrary.processData(data, function() {
self.runCallback();
)};
Then inside of runCallback this refers to an instance of app.myObject, if this is what you want.
Andrew, I think you might be trying a little too hard, here.
If all you're looking to do is bind context, you can do one of the following:
var working_method = context.method.bind(context);
// bind is IE9+, and does something similar to the below
// or, from whatever scope your context/method are available in, define this function
var working_method = function () { context.method(); };
If you require parameters, and you already know the footprint:
var working_method = function (a, b, c) { context.method(a, b, c); };
The solution for variable argument lengths (ie: a generalized "bind" method) is longer, but if you're defining stuff in line, what I've got here might be all you need.
Just make sure that you do not use this in your wrapping function (whether you save it to a var, or pass it directly into another function), because it will reference window.
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.
When I try to attach event handler functions with parameters like :
myhandle.onclick = myfunction(param1,param2);
function myfunction(param1,param2){
}
Now I want to access the event object in my handler function. There is an approach mentioned on the web of sending event object, like:
myhandle.onclick = myfunction(event,param1,param2);
But its giving event object undefined when I test it out.
I know libraries make this stuff easy but I am looking for a native JS option.
myhandle.onclick = myfunction(param1,param2);
This is a common beginner error at a JavaScript level, and not something that libraries can really fix for you.
You are not assigning myfunction as a click handler, you are calling myfunction, with param1 and param2 as arguments, and assigning the return value of the function to onclick. myfunction doesn't return anything, so you'd be assigning undefined to onclick, which would have no effect..
What you mean is to assign a reference to myfunction itself:
myhandle.onclick= myfunction;
To pass the function some extra arguments you have to make a closure containing their values, which is typically done with an anonymous inline function. It can take care of passing the event on too if you need (though either way you need a backup plan for IE where the event object isn't passed as an argument):
myhandle.onclick= function(event) {
myfunction(param1, param2, event);
};
In ECMAScript Fifth Edition, which will be the future version of JavaScript, you can write this even more easily:
myhandle.onclick= myfunction.bind(window, param1, param2);
(with window being a dummy value for this which you won't need in this case.)
However, since many of today's browsers do not support Fifth Edition, if you want to use this method you have to provide an implementation for older browsers. Some libraries do include one already; here is another standalone one.
if (!('bind' in Function.prototype)) {
Function.prototype.bind= function(owner) {
var that= this;
var args= Array.prototype.slice.call(arguments, 1);
return function() {
return that.apply(owner,
args.length===0? arguments : arguments.length===0? args :
args.concat(Array.prototype.slice.call(arguments, 0))
);
};
};
}
I'm not entirely sure what you're trying to do, but perhaps something like this is what you're looking for?
function myfunction(param1, param2){
return function(event) {
// For compatibility with IE.
if (!event) {
event = window.event;
}
alert("event = " + event + ", param1 = " + param1 + ", param2 = " + param2);
};
}
myhandle.onclick = myfunction(param1, param2);
The end result of this is that the anonymous function inside of myfunction will be called as the onclick handler, resulting in the event plus the two parameters being printed. The two parameters are fixed at the time that the event handler is added while event will change each time the user clicks on myhandle.
Try this:
if (!event) { event = window.event; }
inside of myfunction().