srgg6701
srgg6701

Reputation: 2048

JS: strange result while accessing to arguments by key

I have a function with two simple loops which handle function's arguments:

var getArgs=function(){

    var keys = Object.keys(arguments);

    for(var argKey in arguments){
        console.log({argKey:argKey, argument:arguments[argKey]});
    }

    keys.forEach(function(argKey){
        console.log({argKey:argKey, argument:arguments[argKey]});
    });
};

...and its call:

getArgs(5,3,2,11,15,7,-25);

It seems that they do the same, so the output should be the same also, but it is not. The first one works quite predictable. The output is:

{ argKey: '0', argument: 5 }
{ argKey: '1', argument: 3 }
{ argKey: '2', argument: 2 }
{ argKey: '3', argument: 11 }
{ argKey: '4', argument: 15 }
{ argKey: '5', argument: 7 }
{ argKey: '6', argument: -25 }

But the second one behaves unexpected:

{ argKey: '0', argument: '0' }
{ argKey: '1', argument: 1 }
{ argKey: '2', argument: [ '0', '1', '2', '3', '4', '5', '6' ] }
{ argKey: '3', argument: undefined }
{ argKey: '4', argument: undefined }
{ argKey: '5', argument: undefined }
{ argKey: '6', argument: undefined }

Why?

Upvotes: 2

Views: 56

Answers (3)

Juan Lago
Juan Lago

Reputation: 1048

The reason why the foreach doesn't work is you are taken the arguments into a closure, so you are taken the arguments from the arguments.

Try to add a console.log(arguments) into the foreach closure and you can see what it happens.

I provide some examples that works and it is based in your code.

var getArgs=function(){

    var keys = Object.keys(arguments);
    var myargs = arguments;	// Copy arguments object (not as reference) so we can keep it into the scope when the arguments are reading from a closure or callback.
   
    console.log("var in method");
    
    for(var argKey in arguments){
        console.log({argKey:argKey, argument:arguments[argKey]});
    }
    
    console.log("foreach method by key");
    keys.forEach(function(value, key) {
    	console.log({argKey:key, argument:myargs[key]});
    });
    
    // Because we don't have foreach method for objects we can slice it as an array
    console.log("foreach method by argument array");
    Array.prototype.slice.call(arguments).forEach(function(value, key) {
    	console.log({argKey:key, argument:value});
    });

};

getArgs(5,3,2,11,15,7,-25);

Upvotes: 1

Thomas
Thomas

Reputation: 3593

wich arguments-object? you're juggling with two-ish different arguments-objects.
it's like the this-keyword, context matters!

take a look at this:

var getArgs = function(){
    var keys = Object.keys(arguments);

    for(var argKey in arguments){
        console.log({argKey:argKey, argument:arguments[argKey]});
    }

    var _args = arguments;
    keys.forEach(function(argKey){  
        console.log({
            argKey: argKey,

            argument: arguments[argKey], 
            argumentFunction: arguments.callee.toString(),

            realArgument: _args[argKey],
            realArgumentFunction: _args.callee.toString()
        });
    })
}
//or that:
var getArgs = function(){
    var keys = Object.keys(arguments);
    keys.forEach((argKey) => {  
        console.log({
            argKey: argKey,
            argument: arguments[argKey]
        });
    })
}

but as soon as you are messing around with the arguments-object (and passing it to some function like Object.keys() counts as messing around) this function can't be optimized anymore.

var getArgs = function(){
    //you don't need for..in, you are not iterating over an Object or a (huge) sparse Array
    for(var i=0, len=arguments.length; i<len; ++i){
        console.log({
            argKey:i, 
            argument:arguments[i]
        });
    }
}

If you want to pass the arguments around, you should copy them first to an Array.

for(var i = arguments.length, args = new Array(i); i--; ) args[i] = arguments[i];       
//now you can do whatever you want to args.

but what's the purpose of this. such dynamic argument-lengths are rarely a good idea, if you want to pass a list, then pass a list (aka Array or sth like that)

Upvotes: 1

Damon
Damon

Reputation: 4346

The for loop is not a function. As such, arguments retains the context of getArgs.

The forEach while you are thinking of it as a loop, is actually a function that runs a loop. As such, arguments has a new context which is not what you are expecting.

To solve this, set arguments to a new variable and use that variable in the forEach.

Run this code and you will get your expected result:

var getArgs=function(){

    var keys = Object.keys(arguments);
    var args = arguments;

    for(var argKey in arguments){
        console.log({argKey:argKey, argument:arguments[argKey]});
    }

    keys.forEach(function(argKey){
        console.log({argKey:argKey, argument:args[argKey]});
    });
};

Upvotes: 2

Related Questions