Can you alter a Javascript function after declaring it?
Asked Answered
G

11

84

Let's say I have var a = function() { return 1; }. Is it possible to alter a so that a() returns 2? Perhaps by editing a property of the a object, since every function is an object?

Update: Wow, thanks for all the responses. However, I'm afraid I wasn't looking to simply reassign a variable but actually edit an existing function. I am thinking along the lines of how you can combine partial functions in Scala to create a new PartialFunction. I am interested in writing something similar in Javascript and was thinking that the existing function could perhaps be updated, rather than creating an entirely new Function object.

Grunenwald answered 25/1, 2010 at 23:35 Comment(3)
Why would you not just assign a new function to a that returned 2?Hendrickson
Because this wouldn't update references to the function held elsewhere.Mcanally
It might be over a decade later, but it's still something folks are going to think about doing, and so to benefit all of those who find this question, it's still worth accepting https://mcmap.net/q/240216/-can-you-alter-a-javascript-function-after-declaring-it as the only correct answer. This cannot be done, unless you're using a spec-violating JS engine.Heelpost
L
52

You can do all kinds of fun stuff with javascript, including redefining functions:

let a = function() { return 1; }
console.log(a()); // 1
    
// keep a reference
let old = a;
   
// redefine
a = function() {
  // call the original function with any arguments specified, storing the result
  const originalResult = old.apply(old, arguments);
  // add one
  return originalResult + 1;
};

console.log(a()); // 2

Voila.

Edit: Updated to show this in a crazier scenario:

let test = new String("123");
console.log(test.toString()); // logs 123
console.log(test.substring(0)); // logs 123
String.prototype.substring = function(){ return "hahanope"; }
console.log(test.substring(0)); // logs hahanope

You can see here that even though "test" is defined first, and we redefine substring() afterwards, the change still applies.

Side note: you really should reconsider your architecture if you're doing this...you're going to confuse the crap out of some poor developer 5 years down the road when s/he's looking at a function definition that's supposed to return 1, but seems to always return 2....

Lubbock answered 25/1, 2010 at 23:45 Comment(3)
This does not redefine the function. It assigns an entirely new function to the variable which referenced the old one. References to the original function elsewhere won't be updated.Periodic
@hashchange: touche. That said, the fact that it's simply a "var" reference is immaterial here. If you wanted to change, say, a core function in your application, you would simply modify the prototype. The concept still applies.Lubbock
Thanks for the help! I was looking around hoping to find a way to save any current window.onload function then append my own (assigning window.onload overwrites any previous function).Antares
P
48

So you want to modify the code of a function directly, in place, and not just reassign a different function to an existing variable.

I hate to say it, but as far as I have been able to figure it out - and I have tried -, it can't be done. True, a function is an object, and as such it has methods and properties which can be tweaked and overwritten on the object itself. Unfortunately, the function body is not one of them. It is not assigned to a public property.

The documentation on MDN lists the properties and methods of the function object. None of them gives us the opportunity to manipulate the function body from the outside.

That's because according to the spec, the function body is stored in the internal [[Code]] property of the function object, which can't be accessed directly.

Periodic answered 2/7, 2014 at 19:28 Comment(2)
I tried hard, and couldn't get it to happen either. Even Proxy doesn't really let you hook into the original function, since to intercept you have to call the new proxy.Arsonist
This is the only correct answer. The spec has never allowed writable access to a declared Function's [[Code]], only read access via toString().Heelpost
G
43

I used something like this to modify an existing function whose declaration was not accessible to me:

// declare function foo
var foo = function (a) { alert(a); };

// modify function foo
foo = new Function (
  "a",
  foo.toSource()
    .replace("alert(a)", "alert('function modified - ' + a)")
    .replace(/^function[^{]+{/i,"")  // remove everything up to and including the first curly bracket
    .replace(/}[^}]*$/i, "")  // remove last curly bracket and everything after<br>
);

Instead of toSource() you could probably use toString() to get a string containing the function's declaration. Some calls to replace() to prepare the string for use with the Function Constructor and to modify the function's source.

Goodell answered 26/1, 2010 at 23:18 Comment(5)
Interesting. No way to do it without going through strings, though? Bummer.Grunenwald
toSource doesn't work on IE and Chrome. And this is simpler to get the function code: foo.toString().match(/{([\s\S]*)}/)[1]. Also @prm1001 you should accept this as the correct answer. It did helped me tooElwira
thanks hmundt and Bubu Daba. Correct, apparently must use strings.Reset
I want to change content of a generated function (by Animate). This would be correct answer. But as of 2016, toSource is still not (or already not) supported. Also, this uses regex, where it's not needed and indexOf/lastIndexOf can be used. This uses toString() and no regex. Also consider proxying itPetiolate
Funny thing, this is the essence of shims. When a browser doesn't implement a function, or does so poorly, you set up built-in objects with your own custom function, so your script always has a uniform environment. I wish there was finer grained control than string replace on the entire function. But at least the function reference appears to remain valid after replacement and reparse, so that is handy.Jujutsu
A
21
let a = function() { return 1; }
console.log(a()) // 1

a = function() { return 2; }
console.log(a()) // 2

technically, you're losing one function definition and replacing it with another.

Appoggiatura answered 25/1, 2010 at 23:37 Comment(0)
H
17

How about this, without having to redefine the function:

var a = function() { return arguments.callee.value || 1; };
alert(a()); // => 1
a.value = 2;
alert(a()); // => 2
Hereupon answered 26/1, 2010 at 0:9 Comment(4)
Agree as each time you redefine it, you're overwriting old reference, without changing other existing references, which sux indeed.Sink
This is the only correct answer on here at the moment - the only one that actually affects the function, and not creating a new one.Somerville
arguments.callee is deprecated, unfortunatelyKonrad
If you name the function, you can use its name on the body as a reference... const a = function myFunction() { return myFunction.value || 1 }Fly
R
13

I am sticking to jvenema's solution, in which I don't like the global variable "old". It seems better to keep the old function inside of the new one:

function a() { return 1; }

// redefine
a = (function(){
  var _a = a;
  return function() {
  // You may reuse the original function ...
  // Typical case: Conditionally use old/new behaviour
    var originalResult = _a.apply(this, arguments);
  // ... and modify the logic in any way
    return originalResult + 1;
    }
})();
a()  // --> gives 2
Radarman answered 16/5, 2013 at 7:0 Comment(0)
L
6

All feasible solutions stick to a "function wrapping approach". The most reliable amongst them seems to be the one of rplantiko.

Such function wrapping easily can be abstracted away. The concept / pattern itself might be called "Method Modification". Its implementation definitely belongs to Function.prototype. It would be nice to be backed one day by standard prototypal method modifiers like before, after, around, afterThrowing and afterFinally.

As for the aforementioned example by rplantiko ...

function a () { return 1; }

// redefine
a = (function () {
  var _a = a;
  return function () {
    // You may reuse the original function ...
    // Typical case: Conditionally use old/new behaviour
    var originalResult = _a.apply(this, arguments);
    // ... and modify the logic in any way
    return originalResult + 1;
  };
})();

console.log('a() ...', a()); // --> gives 2
.as-console-wrapper { min-height: 100%!important; top: 0; }

... and making use of around, the code would transform to ...

function a () { return 1; }

console.log('original a ...', a);
console.log('a() ...', a()); // 1


a = a.around(function (proceed, handler, args) {
  return (proceed() + 1);
});

console.log('\nmodified a ...', a);
console.log('a() ...', a()); // 2
.as-console-wrapper { min-height: 100%!important; top: 0; }
<script>
(function(d){function f(a){return typeof a==e&&typeof a.call==e&&typeof a.apply==e}function g(a,b){b=null!=b&&b||null;var c=this;return f(a)&&f(c)&&function(){return a.call(b||null!=this&&this||null,c,a,arguments)}||c}var e=typeof d;Object.defineProperty(d.prototype,"around",{configurable:!0,writable:!0,value:g});Object.defineProperty(d,"around",{configurable:!0,writable:!0,value:function(a,b,c){return g.call(a,b,c)}})})(Function);
</script>
Lianne answered 17/12, 2014 at 19:40 Comment(3)
This was basically what the function wrap() of the Prototype framework afforded / still affords. prototypejs.org/doc/latest/language/Function/prototype/wrap Don't know whether there is some feature of this kind in the current ESx versionRadarman
Doesn't this just create a new function every time it's invoked? It's just generalizing the a = newFunction approach.Badtempered
Exactly, and the first 2 introducing paragraphs of this answer also do emphasize this fact.Lianne
S
1

This is a Clear Example based on a control timepicker eworld.ui www.eworldui.net

Having a TimePicker eworld.ui where JavaScript is unreachable from outside, you can't find any js related to those controls. So how can you add a onchange event to the timepicker ?

There is a js function called when you Select a time between all the options that the control offer you. This function is: TimePicker_Up_SelectTime

First you have to copy the code inside this function.

Evaluate...quikwatch...TimePicker_Up_SelectTime.toString()

function TimePicker_Up_SelectTime(tbName, lblName, divName, selTime, enableHide, postbackFunc, customFunc) {
    document.getElementById(tbName).value = selTime;
    if(lblName != '')
        document.getElementById(lblName).innerHTML = selTime;
    document.getElementById(divName).style.visibility = 'hidden';
    if(enableHide)
        TimePicker_Up_ShowHideDDL('visible');
    if(customFunc != "")
        eval(customFunc + "('" + selTime + "', '" + tbName + "');");
    eval(postbackFunc + "();");
}

Now

Using the code that you have saved before reassign the same source code but add whatever you want..

TimePicker_Up_SelectTime = function (tbName, lblName, divName, selTime, enableHide, postbackFunc, customFunc) {
    document.getElementById(tbName).value = selTime;
    if (lblName != '')
        document.getElementById(lblName).innerHTML = selTime;
    document.getElementById(divName).style.visibility = 'hidden';
    if (enableHide)
        TimePicker_Up_ShowHideDDL('visible');
    if (customFunc != "")
        eval(customFunc + "('" + selTime + "', '" + tbName + "');");
    eval(postbackFunc + "();");

    >>>>>>>  My function  >>>>>   RaiseChange(tbName);
}

I've added My Function to the function so now I can simulate an onchange event when I select a time.

RaiseChange(...) could be whatever you want.

Satang answered 22/10, 2013 at 15:42 Comment(0)
E
0

If you're debugging javascript and want to see how changes to the code affects the page, you can use this Firefox extension to view/alter javascripts:

Execute JS firefox extension: https://addons.mozilla.org/en-US/firefox/addon/1729

Esme answered 25/1, 2010 at 23:43 Comment(0)
H
-1
const createFunction = function (defaultRealization) {
  let realization = defaultRealization;

  const youFunction = function (...args) {
    return realization(...args);
  };
  youFunction.alterRealization = function (fn) {
    realization = fn;
  };

  return youFunction;
}

const myFunction = createFunction(function () { return 1; });
console.log(myFunction()); // 1

myFunction.alterRealization(function () { return 2; });
console.log(myFunction()); // 2
Hawking answered 18/1, 2021 at 18:36 Comment(1)
Can you add a short description to your answer explaining your code and how it answers the OP's question?Estranged
B
-2

Can you not just define it again later on? When you want the change try just redefining it as:

a = function() { return 2; }
Bookstall answered 25/1, 2010 at 23:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.