The Qodesmith
The Qodesmith

Reputation: 3375

[].map.call() VS Array.prototype.map.call()?

Why would one be preferred over the other?

[].map.call(...)

Array.prototype.map.call(...)

Doing a quick test in jsPerf shows the Array.prototype way is more performant, although I read somewhere that jsPerf results can be deceiving. Not here to debate jsPerf, rather, just looking for some insight on why one would be preferred over the other. Thanks!

Upvotes: 2

Views: 4279

Answers (2)

John Slegers
John Slegers

Reputation: 47091

Benchmarking

1. Context

I did a basic benchmark test to see if I could spot any differences in performance between these four different statements :

  • dataSet.map(function)
  • dataSet.map.call(dataSet, function)
  • [].map.call(dataSet, function)
  • Array.prototype.map.call(dataSet, function)

2. Method

I executed each of the four statements 1,000,000 times in an isolated sandbox and compared processing time afterwards.

I used the following timer function to determine processing time :

var timer = function(name) {
    var start = new Date();
    return {
        stop: function() {
            var end  = new Date();
            var time = end.getTime() - start.getTime();
            console.log('Timer:', name, 'finished in', time, 'ms');
        }
    }
};

3. Test environment

I ran my tests on a 2-year-old ASUS laptop with an Intel i7 quad-core CPU.

I tested in Linux Ubuntu, on the following two browsers :

  • Firefox 43.0
  • Chrome 45.0

4. Scenarios

4.1. Scenario for dataSet.map(function)

var t = timer('Benchmark'); // <-- START BENCHMARK

var reformattedArray;
var rObj;
var kvArray = [{key:1, value:10}, {key:2, value:20}, {key:3, value: 30}];
for(var i = 0; i < 1000000; i++) {
    reformattedArray = kvArray.map(function(obj){ 
        rObj = {};
        rObj[obj.key] = obj.value;
        return rObj;
    });
}

t.stop();  // <-- STOP BENCHMARK

(see also this Fiddle)

4.2. Scenario for dataSet.map.call(dataSet, function)

var t = timer('Benchmark'); // <-- START BENCHMARK

var reformattedArray;
var rObj;
var kvArray = [{key:1, value:10}, {key:2, value:20}, {key:3, value: 30}];
for(var i = 0; i < 1000000; i++) {
    reformattedArray = kvArray.map.call(kvArray, function(obj){
        rObj = {};
        rObj[obj.key] = obj.value;
        return rObj;
    });
}

t.stop();  // <-- STOP BENCHMARK

(see also this Fiddle)

4.3. Scenario for [].map.call(dataSet, function)

var t = timer('Benchmark'); // <-- START BENCHMARK

var reformattedArray;
var rObj;
var kvArray = [{key:1, value:10}, {key:2, value:20}, {key:3, value: 30}];
for(var i = 0; i < 1000000; i++) {
    reformattedArray = [].map.call(kvArray, function(obj){
        rObj = {};
        rObj[obj.key] = obj.value;
        return rObj;
    });
}

t.stop();  // <-- STOP BENCHMARK

(see also this Fiddle)

4.4. Scenario for Array.prototype.map.call(dataSet, function)

var t = timer('Benchmark'); // <-- START BENCHMARK

var reformattedArray;
var rObj;
var kvArray = [{key:1, value:10}, {key:2, value:20}, {key:3, value: 30}];
for(var i = 0; i < 1000000; i++) {
    reformattedArray = Array.prototype.map.call(kvArray, function(obj){
        rObj = {};
        rObj[obj.key] = obj.value;
        return rObj;
    });
}

t.stop();  // <-- STOP BENCHMARK

(see also this Fiddle)


5. Intermediate results

I did not notice any performance difference between each of the four scenarios, however I did notice that Firefox was about 4x faster than Chrome (for both scenarios).

To be more specific, it takes Chrome about 1 seconds to process each scenario, whereas it takes Fixefox only 0.25 seconds.

To assess whether these browser differences are specific to the map method, I simplified the tests even further.


6. Scenarios v2

6.1. Scenario v2 for dataSet.map(function)

var t = timer('Benchmark'); // <-- START BENCHMARK

var reformattedArray;
var rObj;
var kvArray = [{key:1, value:10}, {key:2, value:20}, {key:3, value: 30}];
for(var i = 0; i < 1000000; i++) {
    kvArray.map(function(obj){ 
        return rObj;
    });
}

t.stop(); // <-- STOP BENCHMARK

(see also this Fiddle)

6.2. Scenario v2 for dataSet.map.call(dataSet, function)

var t = timer('Benchmark'); // <-- START BENCHMARK

var reformattedArray;
var rObj;
var kvArray = [{key:1, value:10}, {key:2, value:20}, {key:3, value: 30}];
for(var i = 0; i < 1000000; i++) {
    kvArray.map.call(kvArray, function(obj){
        return rObj;
    });
}

t.stop(); // <-- STOP BENCHMARK

(see also this Fiddle)

6.3. Scenario v2 for [].map.call(dataSet, function)

var t = timer('Benchmark'); // <-- START BENCHMARK

var reformattedArray;
var rObj;
var kvArray = [{key:1, value:10}, {key:2, value:20}, {key:3, value: 30}];
for(var i = 0; i < 1000000; i++) {
    [].map.call(kvArray, function(obj){
        return rObj;
    });
}

t.stop(); // <-- STOP BENCHMARK

(see also this Fiddle)

6.4. Scenario v2 for Array.prototype.map.call(dataSet, function)

var t = timer('Benchmark'); // <-- START BENCHMARK

var reformattedArray;
var rObj;
var kvArray = [{key:1, value:10}, {key:2, value:20}, {key:3, value: 30}];
for(var i = 0; i < 1000000; i++) {
    Array.prototype.map.call(kvArray, function(obj){
        return rObj;
    });
}

t.stop(); // <-- STOP BENCHMARK

(see also this Fiddle)


7. Final results

Now, Chrome takes about 0.275 seconds to run these scenarios, and Firefox takes about 0.035 seconds. That means Firefox is more than 7x faster than Chrome, here.

Again, there's no noticeable difference in performance between each of the four scenarios when using the same browser.

Upvotes: 5

Pointy
Pointy

Reputation: 413720

The values of [].map and Array.prototype.map are (in the absence of shenanigans) identical. The expression [].map involves (at least conceptually; it could be optimized away possibly) the construction of a new Array instance, so that could have (extremely minor) performance implications.

The expression [].map creates a new empty array, and then references its "map" property. Array instances don't have a "map" property unless you add one, so it won't be found on the object itself. The runtime will therefore check the next thing on the prototype chain, which is of course the Array.prototype object. The runtime will find a "map" property there — specifically, Array.prototype.map. That's why they're the same thing.

An analogous equivalence holds for {}.toString and Object.prototype.toString. The main difference in usage patterns may be that {}.toString can cause issues when it appears at the very beginning of an expression, because the leading { in that case will be taken to be a statement block {, not an object initialization {. However, typical uses of {}.toString are such that it's not terribly likely that it'd need to start an expression. Thus

console.log({}.toString.call(someMysteryObject));

works just as well as

console.log(Object.prototype.toString.call(someMysteryObject));

Performance-wise, in the case of .map(), the overhead of the function calls implicit in the use of that method will almost certainly completely overwhelm the performance difference between the two ways of finding a reference to the .map() function at the start.

Upvotes: 13

Related Questions