views:

39

answers:

2

Let's say I have these two functions:

function fnChanger(fn) {
    fn = function() { sys.print('Changed!'); }
}
function foo() {
    sys.print('Unchanged');
}

Now, if I call foo(), I see Unchanged, as expected. However, if I call fnChanger first, I still see Unchanged:

fnChanger(foo);
foo(); //Unchanged

Now, I assume this is because foo is not being passed to fnChanger by reference, but I may be wrong.

Why does fnChanger not change foo to print Changed!?
Furthermore, how can I get fnChanger to change foo without too much messy syntax?

PS: I'm using node.js to test all this stuff, so that's where the sys.print comes from.

+4  A: 

The assignment to the fn argument just makes that identifier to point to the anonymous function, foo in the outer scope is not affected.

When you pass an object as an argument, one can say "references are passed by value". The assignment just replaces the location where the fn identifier refers to.

That's how the evaluation strategy works in JavaScript.

Just before the assignment in the fnChanger functions, the two identifiers, the global foo and the fn argument, point to the same function object:

                ---------------------------------------------
    foo ----->  |function foo { sys.print('Un changed!'); } |
                ---------------------------------------------
                   ^
                   |
    fn -------------

After the assignment, fn will simply point to the new function:

                ---------------------------------------------
    foo ----->  | function foo { sys.print('Unchanged!'); } |
                ---------------------------------------------

                ---------------------------------------
    fn ------>  | function { sys.print('Changed!'); } |
                ---------------------------------------

How could you change it?

Well, assuming that foo is a function in the global scope, you could do something like this:

function fnChanger(obj, name) {
    obj[name] = function() { sys.print('Changed!'); };
}

function foo() {
    sys.print('Unchanged');
}

fnChanger(this, 'foo');
foo(); // Changed!

The above will work because in the fnChanger function, we require a base object and a property name, functions declared in the global execution context are bound as properties of the Global object, therefore we can re-assign its value in that way.

The line fnChanger(this, 'foo'); should be executed also in the Global scope, it will pass the this value (which refers to the Global object in this scope) and a property name, allowing you to make an assignment to the GlobalObject.foo identifier.

If that code were inside a function, there is no way we can get a base object, because in this "Function Code Execution Context", function declarations (variable declarations and function formal parameters also) are bound as properties of a non-accessible object, called the Variable Object (a chain of these Variable Objects, forms the Scope Chain), and if it were the case, the only workaround would be to use eval.

More info:

CMS
Yeah, I was starting to suspect that's what is happening. Thanks for the clarification, and the link - I never knew exactly what to call that or how it worked. How can I get around this?
Austin Hyde
Ok, I like that approach. Not as messy as I was thinking of doing: passing an object containing the function to be changed.
Austin Hyde
+1  A: 

As @CMS pointed out you cannot assign it within the function due to the scope. However you could reassign it like this:

var fnChanger = function() {
  return function() {
      alert('changed!');
  }
}

var foo = function() {
  alert('Unchanged');
}

foo = fnChanger();
foo();

example

subhaze
Yeah, thought about that. I'm hesitant to do it by return in the real code, though. On one hand, it's cleaner, but on the other, it doesn't quite match up with what the function actually does.
Austin Hyde