Add to a javascript function - javascript

I have a function I can't modify:
function addToMe() { doStuff(); }
Can I add to this function? Obviously this syntax is terribly wrong but it's the general idea...
function addToMe() { addToMe() + doOtherStuff(); }

You could store a reference to the original function, and then override it, with a function that calls back the original one, and adds the functionality you desire:
var originalFn = addToMe;
addToMe = function () {
originalFn(); // call the original function
// other stuff
};
You can do this because JavaScript functions are first-class objects.
Edit: If your function receives arguments, you should use apply to pass them to the original function:
addToMe = function () {
originalFn.apply(this, arguments); // preserve the arguments
// other stuff
};
You could also use an auto-executing function expression with an argument to store the reference of the original function, I think it is a little bit cleaner:
addToMe = (function (originalFn) {
return function () {
originalFn.apply(originalFn, arguments); // call the original function
// other stuff
};
})(addToMe); // pass the reference of the original function

Related

Extend javascript function without overriding current function body

Have one function as following
function JsFunction(){
//some logic already done by framework library...
}
Now, I want to add extra login on that JsFunction
when I call it will perform its logic done before + the logic I added in it.
I don't want to duplicate same logic from library and then add my own code.
so, how can I achieve that with same function name JsFunction()??
If the work the function does is synchronous, it's really easy to wrap it (see below). If it does its work asynchronously, you may or may not still be able to wrap it, the details will vary depending on the implementation of the function.
Wrapping a function that works synchonrously:
var original = JsFunction;
JsFunction = function JsFunction() {
// Do stuff before
// ...
// Call it
var result = original.apply(this, arguments);
// Do stuff after
// ...
return result; // Or something else if you prefer
};
The meat of that is
var result = original.apply(this, arguments);
...which uses Function#apply to call the original function with the same this that your replacement was called with and all arguments it was called with (arguments is a special pseudo-array in JavaScript containing the arguments used when calling a traditional function or method).
With ES2015+ features, you could replace the use of arguments with a rest parameter:
var original = JsFunction;
JsFunction = function JsFunction(...args) {
// Do stuff before
// ...
// Call it
var result = original.apply(this, args);
// Do stuff after
// ...
return result; // Or something else if you prefer
};
Copy the old function, then create a new function which calls it (passing in any arguments and the right context), captures the return value, does something else, then returns the return value.
(function () { // In an IIFE to avoid creating a global variable
let oldJsFunction = JsFunction;
JsFunction = function JsFunction() {
let return_value = oldJsFunction.apply(this, arguments);
console.log("… and another thing");
return return_value;
}
)());

How to bind an argument to a jquery callback without affecting this

I'm new to javascript/jquery and here's the example code I need help with:
function add(text) {
$(this).replaceWith('<label>'+text+'</label>');
}
var label = 'Added';
$('#myDiv').append($('<button type="button">Add</button>').click(function() {
add.call(this, label);
}));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="myDiv"></div>
Is there a "better" way of setting the click callback here?
If the add function did not have a text argument, I could have easily used .click(add) and the button will be bound automatically as this.
But with the argument, I can't bind text without having to set the value of this too i.e. .click(add.bind(this, label)) would be wrong as this is set to the global context.
Thoughts?
As Andreas points out in a comment, there's a solution to this that's specific to jQuery event handlers: The data argument to the on function.
More generally, though, what you're looking for is sometimes called "currying" (or "partial application;" purists tell me one of them is technically incorrect but I can never remember which).
I have a function I use for that which I add to Function.prototype; it looks like this (see comments):
(function() {
var slice = Array.prototype.slice;
Object.defineProperty(Function.prototype, "curry", {
value: function() {
// Remember the original function and the arguments we wre called with
var f = this,
curried = slice.call(arguments);
// Return a new function
return function() {
// Our function was called, add in any arguments it was called with...
var args = curried.concat(slice.call(arguments));
// ...and call the original, passing along `this`
return f.apply(this, args);
};
}
});
})();
In your case, you'd use it like this:
var label = 'Added';
$('#myDiv').append($('<button type="button">Add</button>').click(add.curry(label)));
Note that your add function will be called with the value of label (as it was when we made the curry call, not as it is later), followed by any arguments that the curried function was called with (e.g., the event object).
Example:
(function() {
var slice = Array.prototype.slice;
Object.defineProperty(Function.prototype, "curry", {
value: function() {
// Remember the original function and the arguments we wre called with
var f = this,
curried = slice.call(arguments);
// Return a new function
return function() {
// Our function was called, add in any arguments it was called with...
var args = curried.concat(slice.call(arguments));
// ...and call the original, passing along `this`
return f.apply(this, args);
};
}
});
})();
function add(text) {
console.log("add called with text = '" + text + "'");
}
var label = 'Added';
$('#myDiv').append($('<button type="button">Add</button>').click(add.curry(label)));
<div id="myDiv"></div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

Override the arity of a function

I would like to make a generic function wrapper that (for example) prints the called function and its arguments.
Doing so is easy through the arguments quasi-array and simple calls. For example:
function wrap(target, method) {
return function() {
console.log(Array.prototype.slice.call(arguments).join(', '));
return method.apply(target, arguments);
}
}
However, this way of doing of course completely loses the arity of the called function (if you didn't know, one can obtain the arity (number of arguments) of a JavaScript function through its length property).
Is there any way to dynamically create a wrapper function that would copy the arguments of the wrapped function to itself?
I've thought about creating a new Function object, but I don't see any way to statically extract the arguments list, since the arguments property is deprecated.
Here's a solution using Function:
// could also generate arg0, arg1, arg2, ... or use the same name for each arg
var argNames = 'abcdefghijklmnopqrstuvwxyz';
var makeArgs = function(n) { return [].slice.call(argNames, 0, n).join(','); };
function wrap(target, method) {
// We can't have a closure, so we shove all our data in one object
var data = {
method: method,
target: target
}
// Build our function with the generated arg list, using `this.`
// to access "closures"
f = new Function(makeArgs(method.length),
"console.log(Array.prototype.slice.call(arguments).join(', '));" +
"return this.method.apply(this.target, arguments);"
);
// and bind `this` to refer to `data` within the function
return f.bind(data);
}
EDIT:
Here's a more abstract solution, which fixes the closure problem:
function giveArity(f, n) {
return new Function(makeArgs(n),
"return this.apply(null, arguments);"
).bind(f);
}
And a better one, that preserves context when invoked:
function giveArity(f, n) {
return eval('(function('+makeArgs(n)+') { return f.apply(this, arguments); })')
}
Used as:
function wrap(target, method) {
return giveArity(function() {
console.log(Array.prototype.slice.call(arguments).join(', '));
return method.apply(target, arguments);
}, method.length)
}

How do I pass a function(delegate?) as a parameter to another function in Javascript and then use it

I want to pass a function to another function. I think functions being passed like that are call delegates? I am having a hard time finding a good explanation for this kind of thing online. Is this the right way to do this?
function getCellContentByColumnIndex = function(row, index) {
return $(row.children().get(index)).text();
}
function naturalSort(a, b, func) {
//...
var $a = func(a);
var $b = func(b);
//...
}
//usage
naturalSort(x, y, getCellContentByColumnIndex);
Your code:
function getCellContentByColumnIndex = function(row, index) {
return $(row.children().get(index)).text();
}
Is a syntax error. The following is a function declaration:
functon foo() {}
And this is a function expression:
var foo = function(){}
And this is a named function expression:
var foo = function bar(){}
There are a number of answers here on the differences, there is a detailed explanation in the article Named function expressions demystified which also covers many other aspects of function declarations and expressions.
The term "anonymous function" is jargon for a function expression that has no name and isn't assigned to anything, e.g.
someFn( function(){...} )
where someFn is called and passed a function that has no name. It may be assigned a name in someFn, or not. Ic could just be referenced as arguments[0].
Passing a function is not delegating, that is jargon for the practice of putting a listener on a parent element and catching bubbling events, it is preferred in cases where it can replace say a click listener on every cell in a table with a single listener on the table.
Anyhow, passing a function is just like passing any other object:
function foo(){
alert('foo');
}
function callIt(fn) {
fn();
}
callIt(foo); // 'foo'
In the above, foo is passed to callIt and assigned to the local variable fn, and is then called.
You pass functions around as variables like so:
var getCellContentByColumnIndex = function(row, index) {
return $(row.children().get(index)).text();
}
function naturalSort(a, b, func) {
//...
var $a = func(a);
var $b = func(b);
//...
}
//usage
naturalSort(x, y, getCellContentByColumnIndex);
This is called using anonymous functions.
Anonymous functions..
var getCellContentByColumnIndex = function(row, index) {
return $(row.children().get(index)).text();
}
will work..rest stuff of calling is already perfect in your code..:)
In JavaScript, functions are treated as first class citizens which mean you can toss them here and there like simple variables. The key is, use the FunctionName when you want to refer to function and use FunctionName() to invoke it.
this line: naturalSort(x, y, getCellContentByColumnIndex);
could have been written as
naturalSort(x, y, function (){
return $(row.children().get(index)).text();
});
In which case it would have been called passing Anonymous Function

Can I intercept a function called directly?

In this code I created a function called someFunction. Then I modified Function.prototype.apply and call methods. So instead of my function code is working I am running my interception code (which shows an alert). But neither "call" nor "apply" intercepts direct method call. Is it possiple to intercept this?
Function.prototype.call = function(){alert("call");};
Function.prototype.apply = function(){alert("apply");};
function someFunction(){}
window.onload = function(){
someFunction.call(this); //call alert is shown
someFunction.apply(this); //apply alert is shown
someFunction(); //how can I intercept this?
}
You can only override a known function by setting another function in its place (e.g., you can't intercept ALL function calls):
(function () {
// An anonymous function wrapper helps you keep oldSomeFunction private
var oldSomeFunction = someFunction;
someFunction = function () {
alert("intercepted!");
oldSomeFunction();
}
})();
Note that, if someFunction was already aliased/referenced by another script before it was changed by this code, those references would still point to the original function not be overridden by the replacement function.
Function.prototype.callWithIntercept = function () {
alert("intercept");
return this.apply(null, arguments);
};
var num = parseInt.callWithIntercept("100px", 10);
It is worth noting that in newer versions of JS, there are Proxy objects you can use:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
There is a chance you can intercept direct function call. This requires:
Either the function is created by Function.prototype.bind and you have to overwrite Function.prototype.bind before creating the function, or
The function is created from Function() (or new Function()) and you also have to overwrite Function function before creating the target function.
If neither of the above two can be met, the only way to intercept a direct call is to wrap the target function, which is the solution provided by AndyE https://stackoverflow.com/a/3406523/1316480
For a function that is created by function literal and is hidden in private scope, there is no way to intercept a direct call to it.
I have a blog post concludes all of these: http://nealxyc.wordpress.com/2013/11/25/intercepting-javascript-function/
You could iterate over the global scope and replace any objects of function type you find which aren't "yours".
Brilliant, love it :)
const originalApply = window.Function.prototype.apply;
window.Function.prototype.apply = function(){
console.log("INTERCEPTING APPLY", arguments);
return originalApply.call(this, ...arguments);
};
You can achieve this with a Proxy.
First define a handler with an apply trap that intercepts calls to the function. Then, using that handler, set the function to be a proxy onto itself. Example:
function add(a, b){
return a + b;
}
const handler = {
apply: function(target, thisArg, argumentsList) {
console.log('add was called with ' + argumentsList.join(' and '));
return target(...argumentsList);
}
};
add = new Proxy(add, handler);
var m = add(3, 5);
console.log('m = ', m);
var n = add(12, 8);
console.log('n = ', n);

Categories

Resources