0

I have a function that returns an object with one of the properties being the value of a date object passed into it as a parameter (argument).

When this function is being called from within a loop, varying the date parameter sent into it and all of the returned objects are being added into an array - I noticed that all of the objects in the array ends up with the same value for the date object - which is the final value of the date from within the loop.

To demonstrate, I have a function (theFunction) that returns the string representation as well as the date parameter itself as two properties on the returned object.

This function is being called from another function (theLoop) that calls theFunction with different date values from within a loop and storing the results of the function calls into an array.

var theFunction = function (inputDate) {
    /* The string representation and the date itself as properties on the returned object */
    return {
        string: inputDate.toLocaleString(),
        original: inputDate
    };
};

var theLoop = function (startDate) {
    // declare an array for the output
    var dates = [];

    for (var minute = 0; minute < 1440; minute = minute + 30) {
        var newHour = minute % 60,
            newMinute = minute - newHour * 60;
        // loop and increment the time by a half-hour starting from midnight until minutes < 1440
        startDate.setHours(newHour, newMinute);
        // record the output from theFunction into the array
        dates.push(theFunction(startDate));
    }
    // return the array
    return dates;
};

// get the array
var datesArray = theLoop(new Date());

// console.log the array
console.log(datesArray);

See this fiddle to run the above code.

Question 1: Why does the object returned from within the function named theFunction hold a reference to the date variable that is being incremented within the loop in theLoop?

Question 2: How can theFunction be changed so that it returns a copy of the date passed in to it?

Hari Pachuveetil
  • 10,294
  • 3
  • 45
  • 68
  • 3
    #1 There's only 1 `Date` object actually created with many references to it being established. #2 You have to explicitly clone the object yourself -- `original: new Date(inputDate)`. Related: [Does JavaScript pass by reference?](http://stackoverflow.com/questions/13104494/does-javascript-pass-by-reference) – Jonathan Lonowski Sep 30 '15 at 04:16
  • 1
    This answer may be helpful in understanding how function arguments are passed: http://stackoverflow.com/questions/13506398/why-are-objects-values-captured-inside-function-calls/13508654#13508654 – slebetman Sep 30 '15 at 04:25
  • 2
    Fwiw, JavaScript is pass by value, not pass by reference. However, objects are implemented **as** refrences and they are mutable. `startDate.setHours` mutates the object and `theFunction(startDate)` pass **a** reference to the object to the function. – Felix Kling Sep 30 '15 at 04:58
  • Javascript pass parameters by value, not (and maybe never) pass them by reference. A Javascript object is a pointer, we pass that pointer by value, we don't pass the object by reference. So, the "new" date and the "old" date are the same, but pointer to them are differences but contain the same value. – DMaster Sep 30 '15 at 05:09

3 Answers3

2

For Question 1: A Date in Javascript is an Object; In Javascript Object and Function is a pointer, so, an object value is simply a pointer. When you pass an object (or a function) as a parameter (or assign an object value to another variable), you passed (or assigned) a pointer to the "real" object in memory. In this situation:

//get the array
var datesArray = theLoop(new Date());

You passed new Date() as a parameter to call theLoop, you just passed a pointer, you didn't passed a new copy of the object. So, modifying "several" date is simply modifying one date in one memory location through several copy of its pointer.

For Question 2: To solve this, just add a little line in the theLoop:

var theLoop = function (startDate) { // 'startDate' should be renamed, but I still use this for ease of explain.
  ...
  for (...) {
    startDate = new Date(startDate); // HERE!!
    ...
  }
  return dates;
}

Because new Date(startDate) will create a new copy of the original date. For more details: Date in Javascript contains an unsigned integer called timestamp; new Date(startDate) in this case is exactly equivalent to new Date(parseInt(startDate)) or new Date(startDate.valueOf()).

IMPORTANT: Again, Javascript arguments are not passed by reference, that's the mistaken, they're always passed by value with any kind of value. With an object (or a function), they aren't passed by reference, too! Instead, they're pointers, let's say that the pointer (to object) are passed by value.

Also, now you may know what does operator new actually means. I think, just like C++, it means "create a pointer to an allocated memory region".

DMaster
  • 603
  • 1
  • 10
  • 23
1
  1. Because each object is still referencing the same startDate object. So as startDate changes, each object will be affected by that change.

  2. Change your loop to this

    var theLoop = function (startDate) {
    // declare an array for the output
    var dates = [];
    
    for (var minute = 0; minute < 1440; minute = minute + 30) {
        var newHour = minute % 60,
            newMinute = minute - newHour * 60;
        // loop and increment the time by a half-hour starting from midnight until minutes < 1440
        startDate.setHours(newHour, newMinute);
        var newDate = new Date(startDate.getTime());
        // record the output from theFunction into the array
        dates.push(theFunction(newDate));
    }
    // return the array
    return dates;
    };
    
hooda
  • 105
  • 6
  • Your code will create *new copies* of the original date but the original date still be changed. So, it seems no use. – DMaster Sep 30 '15 at 04:49
1

You have one Date object (startDate), that you modify using setHours, and pass the reference to theFunction in each iteration.

To solve the problem, create a new date from inputDate (updated fiddle):

var theFunction = function (inputDate) {
    /* The string representation and the date itself as properties on the returned object */

    var inputDateClone = new Date(inputDate.getTime()); // the cloned date

    return {
        string: inputDateClone.toLocaleString(),
        original: inputDateClone
    };
};
Ori Drori
  • 183,571
  • 29
  • 224
  • 209
  • 1
    `new Date(inputDate)` can be problematic in some browsers for certain dates. It's more robust to use the time value of the Date to be copied, so either `new Date(inputDate.getTime())` or `new Date(+inputDate)`, which are equivalent but the second is less to type. ;-) – RobG Sep 30 '15 at 04:44
  • Wasn't aware of that. What browsers and which dates btw? I'll use inputDate.getTime() as it's more verbose. – Ori Drori Sep 30 '15 at 05:03