6

consider the following:

foo intends to take the arguments object and rearrange the order, moving arg1 to the position of arg2

function foo (args) {
    args[2] = args[1];
    args[1] = undefined;
}

bar calls foo with it's arguments

function bar (a, b, c) {
    foo(arguments);
    console.log(arguments);
}

I expect the result of the following to be something like { 0: 'hello', 1: undefined, 2: 'world' }

bar('hello', 'world');

However, i get:

{
    0: 'hello',
    1: undefined,
    2: 'world',
    3: undefined,
    4: undefined,
    5: undefined,
    6: undefined,
    7: undefined,
    8: undefined,
    9: undefined,
    10: undefined,
    11: undefined,
    12: undefined,
    13: undefined,
    14: undefined,
    15: undefined,
    16: undefined,
    17: undefined,
    18: undefined,
    19: undefined
}

I am at a complete loss as to why this happens. Anyone have any ideas?

I'm running this in a node.js environment

Cerbrus
  • 70,800
  • 18
  • 132
  • 147
StickyCube
  • 1,721
  • 2
  • 17
  • 22
  • Well that's odd. It's probably related to you modifying something (`arguments`) that's not meant to be modified. – Cerbrus Jul 01 '15 at 13:57
  • 3
    It seems like this is an issue with V8 because with browser testing the "problem" only manifests in Chrome, but doesn't using Moz Spidermonkey. TBH, I'd consider direct manipulation of the arguments "array" to be dangerous, and you'd be better off taking a copy with `Array.prototype.slice.call(arguments)` before you start messing with it. – spender Jul 01 '15 at 14:01
  • `arguments.length` is still only `2` though. – Mackan Jul 01 '15 at 14:05
  • [This V8 issue](https://code.google.com/p/v8/issues/detail?id=222#c12) might be related? – doldt Jul 01 '15 at 14:06
  • 3
    To be clear, `arguments` **isn't** an array. It's array-like. Applying array expectations might lead to unexpected results. – spender Jul 01 '15 at 14:07
  • i tried to change the `.length` property but it doesnt make any difference – StickyCube Jul 01 '15 at 14:10
  • @spender That's usually what i do but unfortunately my particualr use-case requires that i modify the actual arguments object – StickyCube Jul 01 '15 at 14:12
  • That's an odd one... BTW, if you pass in 3 arguments, it doesn't resize arguments like that: `bar('hello', 'world', 'three')` returned `{ 0: 'hello', 1: undefined, 2: 'world' }` for me. – Joseph Jul 01 '15 at 14:13
  • 1
    @StickyCube I find myself wondering what your use-case might be... – spender Jul 01 '15 at 14:14
  • @joseph Interesting, maybe it's to do with setting arguments over the initial length of the array. Needto investigae more – StickyCube Jul 01 '15 at 14:16
  • @spender It's just a little utility for moving the final given argument of a function to the position of the last declared one if it's a function I was just seeing if you could actually do stuff like that and i stumbled across this peculiarity – StickyCube Jul 01 '15 at 14:19
  • It also works with 1 argument, resulting in 1 element shorter an array - for some reason 18 seems to be a magic number here. – doldt Jul 01 '15 at 14:20
  • 1
    If you add `'use strict';` to function `bar` it works as expected. – DavidDomain Jul 01 '15 at 14:21
  • 1
    Since `arguments.length` is correct, any iteration based on this would still give the desired result. In a sense, aren't all indexes out of the dimensions _undefined_? Sure, showing those in the console is a bit confusing, but it's hardly a show stopper. – Mackan Jul 01 '15 at 14:40

1 Answers1

3

The arguments object is not an array. It is a list with the internal type Arguments, with a length property and getters/setters for properties 0 to len - 1 where len is the lesser of the number of declared arguments of the function and the number of elements you called the function. Once this object is created, the system will not increase/decrease length when you operate on its properties, and attempting to set it a length does not add/remove keys. The getters/setters for properties 0 to len - 1 are indeed aliases to your argument names within the function (i.e., when you set b = 1 you will see arguments[1] === 1).

What's happened when foo tries to set args[2] is addition of an integral property triggers V8 to resize the underlying array storage to 20 elements (it somehow should know that this is an Arguments type so it can probably set it to a hash property instead). If you set args[20] = 1 it will be resized to 47, args[100] = 1 will resize to 167, but setting args[1026] = 1 will make it a sparse array (but first setting args[1025] = 1 and then args[2048] does not make it sparse), etc.

After resized, Object.keys reports all 0 to 19 as properties, so console.log (which calls util.format) just prints all of them.

(function (a, b, c) { (function (args) { args[2] = 1; })(arguments); console.log(Object.keys(arguments)); })("hello", "world")

["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19"]

This is definitely a V8 bug, although it is very edgy.

Alan Tam
  • 2,027
  • 1
  • 20
  • 33