1

I'm writing a function that checks if arguments are zero, and it doesn't seem to work correctly. Note: Using Chrome as my browser, but this code should be cross-browser supported.

// check all arguments, and make sure they aren't zero

function zeroCheck(arg1, arg2) {
    var i, argsLen = arguments.length;
    for (i = 0; i <= argsLen; i += 1) {
        if (arguments[i] === 0) {
            // This is where it doesn't behave as I expected
            arguments[i] = 1; // make arg1 = 1
        }
    }
    console.log(arg1); // arg1 = 0
}

zeroCheck(0, 2);

I was expecting arg1 to be equal to 1, but it is still equal to 0.

tim-montague
  • 16,217
  • 5
  • 62
  • 51

5 Answers5

1

Though some browsers appear to work the way you want (Chrome and Firefox), it isn't obvious to me from the ECMAScript spec that it will always be this way. It makes it sounds like the arguments array is probably just a reference to the named arguments in non-strict mode and it specifically says that the two should have no connection to one another in strict mode (in other words what you want to do is specifically NOT supposed to work in strict mode).

You can see in this jsFiddle http://jsfiddle.net/jfriend00/bG5xp/ that Chrome appears to implement it as the spec describes. There is linkage between arguments[0] and arg1 in non strict mode and there is no linkage between them in strict mode. A careful reading of the spec doesn't say that javascript is required to have linkage between the two in non-strict mode, but it does make it sound like it is likely. If you wanted to rely on that and you were sure you never needed your code to work in strict mode, then you would have to test a bunch of browsers to see if the behavior you desire is widely supported.

It is also not clear from the spec if the arguments array is always meant to be modifiable though that seems more likely given that it's implemented with a javascript object (not an actual array).

The safe way to modify the arguments array is to make a copy first and modify the copy. That will, of course, not modify any named arguments. You could modify those manually if you wanted to.

A common way to make a copy of the arguments array is:

var args = Array.prototype.slice.call(arguments, 0);

One generally uses either the arguments array or the named arguments and not both since any named argument is also in a known position in the arguments array so you don't really need to worry about a named argument changing value when you modify the arguments array.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • As the arguments are within the scope of the function, I don't understand how this operation could be "unsafe". I also don't want to manually change the arguments because, I want to reuse this operation on several different functions regardless of their named arguments. – tim-montague Aug 27 '12 at 08:34
  • `unsafe` in that it's not meant to be a modifyable structure. For example, you cannot `.push()` onto it. It is not a real array. It is only a pseudo array. – jfriend00 Aug 27 '12 at 08:35
  • If I use args = Array.prototype.slice.call(arguments, 0) will I be able to access them by name? – tim-montague Aug 27 '12 at 08:37
  • @tfmontague - No - that makes a completely separate copy of the args. As I say in my answer, pick one. Either use names or the arguments array - one or the other. Why are you trying to use both? – jfriend00 Aug 27 '12 at 08:44
  • 1
    I think the idea of using both is that it is easy to use them by name for clarity within the function, but initially _all_ arguments should be tested to be sure they're not 0 so for that an array and a loop would be easier. – nnnnnn Aug 27 '12 at 08:46
  • I guess I could just use the indexed arguments. It is nice to use both because, there may be cases where argument validation is needed and using named arguments for the actual code is more descriptive and unobtrusive. – tim-montague Aug 27 '12 at 08:53
  • 1
    @tfmontague - OK, I get the point, but I can't find any guarantee in the ECMAScript spec that what you want is supported. That means you'd either have to skip it or test exhaustively to see if all browsers implement the behavior you want and even then there's still a little risk that it could break in the future. – jfriend00 Aug 27 '12 at 08:58
  • Which version of the spec are you looking at? Have a look at my answer for details of what is supposed to happen in non-strict mode... – nnnnnn Aug 27 '12 at 09:01
  • @nnnnnn - ECMA-262, v5.1. I hadn't seen Note 1 (which you cite in your answer) until just recently so I updated my answer. – jfriend00 Aug 27 '12 at 09:03
  • 2
    @nnnnnn - I tested Chrome in strict and non-strict mode and it appears to be following what is described the ECMAScript spec: http://jsfiddle.net/jfriend00/bG5xp/. There is linkage between `arguments` and the named args in non-strict mode and no linkage in strict mode. – jfriend00 Aug 27 '12 at 09:14
1

Try it like this. arg1 with value 0 evaluates to false/falsy, so you can use this shortcut boolean evaluation:

function zeroCheck(arg1,arg2) {
    arg1 = arg1 || 1; 
    console.log(arg1); //=> 1
}

zeroCheck(0,2);

A generic function to check for all arguments (returns an Array)

function zeroCheckArgs(args){
  return [].slice.call(args).map(function(a){return a || 1;});
}

//more conservative
function zeroCheckArgsAlt(args){
  var retArgs = [];
  for (var i=0;i<args.length;i+=1){
    retArgs.push(args[i] || 1);
  }
  return retArgs;
}

function some(){
  var args = zeroCheckArgs(arguments);
  console.log(args);
}

function someAlt(){
  var args = zeroCheckArgsAlt(arguments);
  console.log(args);
}

some(1,0,0,12,12,14,0,1);    //=> [1, 1, 1, 12, 12, 14, 1, 1]
someAlt(1,0,0,12,12,14,0,1); //=> [1, 1, 1, 12, 12, 14, 1, 1]
KooiInc
  • 119,216
  • 31
  • 141
  • 177
  • 1
    And what if you want to check if arguments 1..n are equal to falsy, without knowing how many arguments the function was passed? – tim-montague Aug 27 '12 at 09:02
1

From the ECMA-262 spec:

"For non-strict mode functions the array index (defined in 15.4) named data properties of an arguments object whose numeric name values are less than the number of formal parameters of the corresponding function object initially share their values with the corresponding argument bindings in the function’s execution context. This means that changing the property changes the corresponding value of the argument binding and vice-versa. This correspondence is broken if such a property is deleted and then redefined or if the property is changed into an accessor property. For strict mode functions, the values of the arguments object‘s properties are simply a copy of the arguments passed to the function and there is no dynamic linkage between the property values and the formal parameter values."

But if you read the technical details of how the arguments object is set I think you'll find it is based on how many arguments are actually passed to the function when it is called, not how many named arguments are declared, so using arguments and a loop to check the value of each named parameter might not work if they're not all passed in. Though in your case if you're testing specifically for 0 it should work since parameters that are not passed will be undefined rather than 0.

Having said that, exactly how the arguments object actually behaves depends on the browser. Chrome doesn't follow the spec.

nnnnnn
  • 147,572
  • 30
  • 200
  • 241
0

It works for me, check this example fiddle

Asciiom
  • 9,867
  • 7
  • 38
  • 57
  • Thanks for your jsFiddle. Under "use strict", the arguments are copied so the value of the named arguments are never altered. – tim-montague Aug 27 '12 at 09:47
0

@nnnnnn -

"For non-strict mode functions the array index (defined in 15.4) named data properties of an arguments object whose numeric name values are less than the number of formal parameters of the corresponding function object initially share their values with the corresponding argument bindings in the function’s execution context. This means that changing the property changes the corresponding value of the argument binding and vice-versa. This correspondence is broken if such a property is deleted and then redefined or if the property is changed into an accessor property. For strict mode functions, the values of the arguments object‘s properties are simply a copy of the arguments passed to the function and there is no dynamic linkage between the property values and the formal parameter values."

Your citation actually answers my original question. The reason that my code as posted below does not work as expected is because I was actually using "strict" mode.

// check all arguments, and make sure they aren't zero

function zeroCheck(arg1,arg2) {
    var i, argsLen = arguments.length;
    for (i = 0; i <= argsLen; i += 1) {
        if (arguments[i] === 0) {
            // This is where it doesn't behave as I expected
            arguments[i] = 1; // make arg1 = 1
        }
    }
    console.log(arg1); // arg1 = 0
}

zeroCheck(0,2);

It worked in for xdazz, Jeroen Moons, and jFriend00 - because they did not include the strict: http://jsfiddle.net/nSJGV/ (non-strict) http://jsfiddle.net/HDjWx/ (strict)

tim-montague
  • 16,217
  • 5
  • 62
  • 51