1

UPDATE: FelixKling rightly points out that my use of the term spread operator was incorrect and it should be Rest Parameter. Using the compatibility table I linked to, it clearly shows that the Rest Parameter is not supported in Safari 9. Clarifying that it is a Rest Parameter is the real answer to this question (although the example of how to rewrite the function is excellent).


I wrote a javascript function performance tester which tests how long a function takes to run. It uses the ES6 spread operator (...) and it works fine in Firefox but not in Safari (up to version 9.1.2). According to this compatibility chart, Safari 9 received a score of 9/15 for the spread operator and I presume this is a shortcoming of Safari 9. Is there an ES6 way to rewrite it so it will work with Safari 9 (and if not, why the "9" in 9/15 - I would think it means that it must have worked in some situations)?

function timerTest(func, iterations, ...someData) {
   if (typeof iterations == "undefined") {
      iterations = 1;
   }
   var start = performance.now();
   for (var i = 0; i < iterations; i++) {
      func.apply(this, someData);
   }

   var funcName = /function ([^\(]+)/.exec(func.toString())[0];
   v("Time to run " + funcName + " for " + iterations + " time(s): " + (performance.now() - start));
   return performance.now() - start;
}

A sample of how it is used (in this case, determining which of the 3 methods is faster to test if an element has a class assigned):

var e = document.getElementById("test");
timerTest(hasClass, 1000000, e, "x");
timerTest(hasClass2, 1000000, e, "x");
timerTest(hasClass3, 1000000, e, "x");

function hasClass(e, name) {
   if (typeof e !== "undefined" && typeof e.className !== "undefined") {
      return new RegExp('(\\s|^)' + name + '(\\s|$)').test(e.className);
   }
   return false;
}

function hasClass2(e, name) {
   if (typeof e !== "undefined" && typeof e.className !== "undefined") {
      return (' ' + e.className + ' ').indexOf(' ' + name + ' ') > -1;
   }
   return false;
}

function hasClass3(e, name) {
   if (typeof e !== "undefined" && typeof e.classList !== "undefined") {
      return e.classList.contains(name)
   }
   return false;
}
mseifert
  • 5,390
  • 9
  • 38
  • 100
  • 2
    Click the 9/15 to expand. "with strings, in function calls" is a no – jeff carey Jan 03 '17 at 03:10
  • It could be as simple as replacing `timerTest(func, iterations, ...someData)` with `function timerTest(func, iterations) { var someData = Array.prototype.slice.call(arguments, 2); ...}`. – RobG Jan 03 '17 at 03:16
  • @jeffcarey - Ah, I didn't notice that when I clicked on the 9/15 that it expanded. Thanks for pointing this feature out. This lets me know what options I have. – mseifert Jan 03 '17 at 03:16
  • @robG - thanks for reminding me about the `arguments` keyword. That is a great and simple solution. – mseifert Jan 03 '17 at 03:18
  • @mseifert—no worries. Oh, this is actually rest parameter syntax which is not supported in Safari 9 at all (according to Kangax's matrix). Careful with mobile too, support is patchy ([*according to MDN*](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters#Browser_compatibility)). – RobG Jan 03 '17 at 03:27
  • 1
    [`...` is not an operator](http://stackoverflow.com/a/37152508/218196) and it has nothing to do with "spread" in your case. – Felix Kling Jan 03 '17 at 03:32
  • @FelixKling - What is the proper term for it then? According to [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator), "The spread syntax allows an expression to be expanded in places where multiple arguments (for function calls) ... are expected." and the example they give is `myFunction(...iterableObj);` – mseifert Jan 03 '17 at 04:26
  • But you have a function *definition*, not a function *call*. As I mentioned in the post I linked to, the official name would be *rest parameter*. – Felix Kling Jan 03 '17 at 04:29

1 Answers1

-1

Use arguments object

// call your timerTest function
timerTest(hasClass, 1000000, e, "x");

function timerTest(func, iterations) {

  if (typeof iterations == "undefined"){
     iterations = 1;
  }
  var start = performance.now();
  //Get parameters from arguments
  var args = Array.prototype.slice.call(arguments, 2);

  for (var i = 0; i < iterations; i++){
    func.apply(this, args);
  }

  var funcName = /function ([^\(]+)/.exec(func.toString())[0];
  v("Time to run " + funcName + " for " + iterations + " time(s): " + (performance.now() - start));
    return performance.now() - start;
}
Andrés Andrade
  • 2,213
  • 2
  • 18
  • 23
  • The function works with your changes, but when I compare the times it takes to run the functions shown in the example, your method takes between 67% and 10 times longer to run. The solution is to get the arguments into an array once using `var args = Array.prototype.slice.call(arguments, 2);` and then passing `args` with `func.apply(this, args);` – mseifert Jan 03 '17 at 07:37
  • @mseifert Right, create the array inside the loop is not a good idea at all if you are measuring time, I guess I put the code before calling the function just for readability. I've just edited my answer. – Andrés Andrade Jan 03 '17 at 13:05