Reassign JavaScript Function Parameters In Reverse Order, Or Lose Your Params

Every now and then I need to have a method support 2 or 3 arguments, providing a default value for one of them if only 2 are specified. My typical solution is to check the number of arguments passed to the function and reassign the named parameters as needed. I think this is a fairly typical solution in JavaScript. Except I spent about 30 minutes trying to track down a bug, just now, and it’s somewhat perplexing to me.

Can You Spot The Bug?

Here’s a sample of the code in question, that you can run from NodeJS. I’m using NodeJS 0.10.26 in this case.

function foo(a, b, c){
  if (arguments.length === 2){
    a = "";
    b = arguments[0];
    c = arguments[1];
  }

  console.log("a:", a);
  console.log("b:", b);
  console.log("c:", c);
}

foo("bar", "baz");

There isn’t anything terribly special here. Check the number of arguments. If it’s 2, then re-assign the ‘a’ variable to a default, reassign ‘b’ to what ‘a’ originally was, and reassign ‘c’ to what ‘b’ originally was. Note that I’m doing this reassignment through the use of the arguments array, as well.

Can you guess what the output is, based on the code above?

NewImage

#WAT

Why are my parameters empty?

How I Thought Parameters Worked

I’ve always assumed method parameters worked the same way as variables. If I have 2 variables pointing at the same data, and I reassign one of them, then the other one is not reassigned.

var a = "foo";
var b = a;
a = "baz";
console.log(a, b); //=> baz, foo

This makes sense to me. This is how by-reference variables have always worked in my mind. And so, I’ve always expected function parameters to work the same. But apparently method parameters don’t work this way. Based on the above behavior, my currently confused understanding of this relationship says that the “a” parameter is not actually a variable in JavaScript. Rather, it’s some special construct that references the value of arguments[0]… not a by-ref variable that points to this value, but more like a by-val variable that *IS* the value of this memory location. 

Given this by-val nature of the named parameter -> arguments[n] relationship, when my code above assigned “a” to an empty string it wiped out the “arguments[0]” value as well. 

#WAT

Fixing The Bug: Reverse The Reassignment Order

In order to work around this, you have to reassign the parameters in reverse order. 

function foo(a, b, c){
  if (arguments.length === 2){
    c = arguments[1];
    b = arguments[0];
    a = "";
  }

  console.log("a:", a);
  console.log("b:", b);
  console.log("c:", c);
}

foo("bar", "baz");

And now the results are what I expected:

NewImage

By-Val Params

It turns out JavaScript does treat params and the arguments object as a by-val relationship. You change one, the other is also changed, unlike standard variables. From what I’ve read, this isn’t just NodeJS either – it’s the JavaScript spec.

Honestly, I had no idea that it was treating function parameters as by-val. After all these years with JavaScript, this language still surprises me.


Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Derick Bailey

Derick Bailey is an entrepreneur, problem solver (and creator? :P ), software developer, screecaster, writer, blogger, speaker and technology leader in central Texas (north of Austin). He runs SignalLeaf.com - the amazingly awesome podcast audio hosting service that everyone should be using, and WatchMeCode.net where he throws down the JavaScript gauntlets to get you up to speed. He has been a professional software developer since the late 90's, and has been writing code since the late 80's. Find me on twitter: @derickbailey, @mutedsolutions, @backbonejsclass Find me on the web: SignalLeaf, WatchMeCode, Kendo UI blog, MarionetteJS, My Github profile, On Google+.
This entry was posted in Javascript, NodeJS. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Blerik

    If you rewrite your code to use b and c instead of arguments[0] and arguments[1] it is immediately obvious it doesn’t work: a = “”; b = a; c =b;

    This doesn’t change the fact that arguments is a weird thing, but makes it a lot easier to see the problem.

    • djnemec

      Well of course, but that’s not really relevant. He’s not using variables, he’s assuming `arguments` is an array:

      var arguments = ["bar", "baz"];
      var a = arguments[0], b = arguments[1], c=arguments[2];

      c = arguments[1];
      b = arguments[0];
      a = “”;

      This works as expected. Derick was assuming that `arguments` worked like a normal array and the actual parameter variables were bound, by reference, to a, b, and c when the function executed — which is exactly what the code above does.

  • http://aashishkoirala.github.io/ Aashish Koirala

    Most statically typed languages will force you to have optional parameters towards the end. Maybe it’s worth doing that?

  • Paul Connolly

    What? No! The parameters are by-ref, that’s why they are all changing, because they are pointing to the same reference. If it was by-val, the values of the variable would be passed around, and wouldn’t affect each other.

    • http://mutedsolutions.com Derick Bailey

      #facepalm – i always get those backwards

      • http://lazycoder.com/ Scott Koon

        It’s not really an array. It’s just a weird object that has a length and ordinals.

        • DJ

          Agreed. I see this quite often.

          The “arguments” object has an array-like structure but it is not an instance of “Array” as many assume.

          To see what I’m saying, try:

          Array.isArray(arguments)

          It will always return false.

  • Evgeniy Kazakov

    Hi, Derick!

    Strict mode fix this quirk.

    “use strict”;

    console.clear();

    function foo(a, b, c){

    if (arguments.length === 2){

    a = “”;

    b = arguments[0];

    c = arguments[1];

    }

    console.log(“a:”, a);

    console.log(“b:”, b);

    console.log(“c:”, c);

    }

    foo(“bar”, “baz”);

  • Mandeep Singh Bhangu

    thanks for the Tip..!!

  • jessewilliamson

    Just for fun…
    If this is a pattern that gets repeated often, you could use a combinator to make the first argument to a function optional:

    var optionalFirstArg = function(fn){
    return function(){
    var args = Array.prototype.slice.call(arguments);
    args = args.length === fn.length ? args : [""].concat(args);
    return fn.apply(null, args);
    };
    };

    var myFunc = optionalFirstArg(function(a,b,c){
    console.log(a,b,c);
    });

    myFunc(“hi”, “there”, “everybody”); //=> “hi” “there” “everybody”
    myFunc(“hi”, “there”); //=> “” “hi” “there”

  • Dumitru “Mitică” Ungureanu

    Agreeing with Aashish Koirala. TypeScript does that.

  • Fernando Zamora

    Thanks for sharing this brain teaser.