2

As the data for a date/temperature scatter-plot, I wanted to be able to key the data with a date-like string, and the following is the structure I devised:

  var dateTemperatures = {
      '[2016,8,29]': {low: 63, high: 94},
      '[2016,9,2]': {low: 59, high: 81},
      '[2016,9,1]': {low: 58, high: 85}
  }

The idea being that I could call JSON.parse on the keys, and have the arguments necessary to create a date. Then I found this Stackoverflow answer as to how to create an object, using a combination of both apply and new .... (), and in particular used the following substituting Something with Date:

var s = new (Function.prototype.bind.apply(Something, [null, a, b, c]));

And it works like a charm, for instance in the following function I devised for ordering the "Date" keys:

  function orderTemperatureDates(dateTemperatures) {
      var orderedDates = [];

      for (var temperatureDate in dateTemperatures) {
          var YMD = JSON.parse(temperatureDate);
          YMD.unshift(null); //standard first argument to .apply()
          orderedDates.push(new (Function.prototype.bind.apply(Date, YMD)));
      }

      return orderedDates.sort(function(a,b){return a.getTime() - b.getTime()});
  }

CONSOLE/LOG/OUTPUT:

[Thu Sep 29 2016 00:00:00 GMT-0500 (Central Daylight Time), Sat Oct 01 2016 00:00:00 GMT-0500 (Central Daylight Time), Sun Oct 02 2016 00:00:00 GMT-0500 (Central Daylight Time)]

Going back to the example line from the referenced Stackoverflow answer though, how does this work? Because my understanding, per documentation here, is that null can be the first argument to apply followed by the remaining arguments, but in the example and in my code, null and all of the remaining arguments are part of one and the same array.

Community
  • 1
  • 1
Dexygen
  • 12,287
  • 13
  • 80
  • 147
  • BTW I've released a gist for the entire scatter-plot at https://gist.github.com/dexygen/40f333657afc17aabbbcf5597bd3871e – Dexygen Oct 24 '16 at 16:49
  • "in the example and in my code, null and all of the remaining arguments are part of one and the same array" You're still feeding 2 parameters to `apply` though... – André Dion Oct 24 '16 at 16:58
  • @AndréDion The first parameter being passed to apply is the function/constructor to invoke. Somewhere somehow to me at least it seems that `null` needs to get shifted off the front of the array to which it belongs (the second argument to apply) in order for the code to work, so it seems a little "magical" to me right now. I think it may have something to do with how bind works but cannot figure out exactly how: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind – Dexygen Oct 24 '16 at 17:58
  • [The first argument to `apply` is `thisArg`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply). The second argument (`argsArray`) are arguments to be passed into the function you're `apply`ing. – André Dion Oct 24 '16 at 18:06
  • @AndréDion I could be wrong but in this case I think the first argument is the thisArg to `bind`, *not* `apply` – Dexygen Oct 27 '16 at 16:09

1 Answers1

6

My understanding is that null can be the first argument to apply followed by the remaining argument

Yes, you can pass null as the first argument of apply when you want the this value inside the method to be null (or generally don't care about it). But that is not the case here.

In new (Function.prototype.bind.apply(Date, [null, 2016, 8, 29])) we don't pass null as the first argument to apply, because we don't want that. We are applying the bind method, and we need to apply it to Date, with the rest of the arguments. The call is equivalent to

new (Date.bind(null, 2016, 8, 29))

where we are passing null as the first argument to bind, because we don't care about the value that will get ignored anyway when the bound function is invoked with the new keyword.

Have a look at this example:

function constructor(a, b, c) {
  "use strict";
  console.log(this, a, b, c);
}
var bound = constructor.bind(null, 1, 2, 3);
bound();
new bound();

If you know that YMD always has three elements, it would be easier to write new Date(YMD[0], YMD[1], YMD[2]). If you were using ES6, you could also use spread syntax new Date(...YMD) for the call.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Boy I had blinders on and was convinced I needed to use `apply`. Thanks for your explanation and the suggested alternative approach. – Dexygen Oct 27 '16 at 16:55
  • Yeah, you need to use `apply` only if you don't know how many arguments to pass. Otherwise `call` or just direct invocation are enough. – Bergi Oct 27 '16 at 17:02
  • Also, whenever you use `bind`, you are creating new functions. Creating functions in a loop is bad for performance. You can use apply, bind or call more creatively to create a general constructor if you care for performance. – Gokhan Kurt Oct 28 '16 at 13:02
  • @GökhanKurt Actually, using `bind` is the only (non-eval, pre-ES6) way to pass arbitrarily many arguments into a native constructor. – Bergi Oct 28 '16 at 13:05
  • @Bergi You can write your own constructor to extend the native one' prototype, and use `apply` on the native constructor, I think. I always do that to implement OOP and inheritance in javascript. In case of `Date` though, `Date` doesn't let you use its prototype methods on objects of other type. I don't know why was it implemented like that. – Gokhan Kurt Oct 28 '16 at 13:13
  • @GökhanKurt No, the whole point is that you *cannot* use `apply` with native constructors - it only calls them like a function (without `new`), and no instance will be initialised. Only ES6 `super` really fixes that. – Bergi Oct 28 '16 at 13:14
  • @Bergi I didn't know that. Thank you. – Gokhan Kurt Oct 28 '16 at 13:16