Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

JavaScript: Using the shift method on the arguments array

DZone's Guide to

JavaScript: Using the shift method on the arguments array

·
Free Resource

 

In my webinar today on reading jQuery source code (links to come once the webinar has been uploaded to the DevExpress Channel site), I was talking about the extend method. I started out by talking about what a typical extend method would do before going onto the jQuery-specific one.A while back I wrote a simple one, more as a pedagogical exercise, rather than any real attempt to create the be-all and end-all version:

var extend = function () {
  var obj,
      name,
      shift = Array.prototype.shift,
      target = shift.call(arguments);

  target = target || {}; 
  obj = shift.call(arguments);
  while (obj) {
    for (name in obj) {
      if (obj.hasOwnProperty(name)) {
        target[name] = obj[name];
      }
    }
    obj = shift.call(arguments);
  };
  return target;
};

So, essentially, the code assumes that the first argument to the function is the target object and there will be zero or more source objects defined that will provide properties to the target object. The source objects are peeled off the arguments array one by one. The target object is returned (and of course is modified by the function).

Here’s an example of it in action (we extend an empty object with some default values and then some changed values):

var defaultOptions = { animate: true, color: "red" };
var myOptions = { color: "blue" };
var actualOptions = extend({}, defaultOptions, myOptions);
// result: { animate=true, color="blue"}

Anyway, this is not whether this code is correct or not, but about my first attempt at writing it. I initially had something like this:

var extend = function (target) {
  var obj,
      name,
      shift = Array.prototype.shift;

  obj = shift.call(arguments);
  obj = shift.call(arguments);
  while (obj) {
    for (name in obj) {
      if (obj.hasOwnProperty(name)) {
        target[name] = obj[name];
      }
    }
    obj = shift.call(arguments);
  };
  return target;
};

I was assuming that there was always a target object, so I declared it in the function declaration itself. I then shifted off (and discarded) the first element of the arguments array (which was the target object) and from then on, it was the same as before. Except: it didn’t work. What the heck was going on?

To help resolve the problem, I wrote the following code:

var cl = function (obj, text) {
  if (text) { console.log(obj); }
  else { console.log('<' + obj + '>'); }
};

var arraycl = function (a) {
  for (var i = 0; i < a.length; i++) {
    cl(a[i]);
  }
  cl('length: ' + a.length);
};

var f = function (a, b, c) {
  cl('===before manipulation', true);
  cl(a);
  cl(b);
  cl(c);
  cl('arguments array', true);
  arraycl(arguments);

  cl('===remove first argument', true);
  Array.prototype.shift.call(arguments);
  cl(a);
  cl(b);
  cl(c);
  cl('arguments array', true);
  arraycl(arguments);
};

f("one", "two", "three");

Ignore the first two functions there, they’re just helper functions. The real meat is in the f function. It uses three parameters, a, b, and c. It first logs the values of those parameters, and then the elements of the arguments array. All well and good, and here is the result of that part of the f function:

===before manipulation
<one>
<two>
<three>
arguments array
<one>
<two>
<three>
<length:   3>

This is exactly what I’d expect: the named parameters have the correct values from the call site and the arguments array reflects that. No sweat.

Now the f function shifts off the first parameter of the arguments array. Here is the result of that:

 

===remove first argument
<two>
<three>
<three>
arguments array
<two>
<three>
<length: 2>

First thing to note is that the arguments array has lost its first element and hence now has length 2. The next really wacky thing is that removing the first element from the array has actually changed the values of the named parameters, a and b. a now has the value of the second parameter passed in, and b the value of the third. c on the other hand still has the value of the third parameter and has not been set to undefined, for example. Hence, sometimes the named parameters reflect the current version of the arguments array, and ... sometimes not.

The lesson to be learned here is that modifying the arguments array in a function will also modify the values of the named parameters as well. This seemed counter-intuitive to me at first glance (hence my first cut at an extend function where I assumed that the named parameters kept their values even though I was manipulating the arguments array).

My recommendation is, if you are going to change the arguments array inside a function, don’t use named parameters as well.

Topics:

Published at DZone with permission of Julian Bucknall, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}