props or proto on fns

JavaScript performance comparison

Test case created by Devin Rhode

Info

In some random google talk I watch on youtube, it was described that it's fastest to prefer using as many literals as possible, instead of adding things as properties. For example, this is bad: var a = new Array() a[1] = 'a' a[3] = 2 ... vs the one line literal var a = [undefined, 'a', undefined, 2];

So how does this apply to functions that are modules, like jQuery for example?

Preparation code

//some real code! function historicalConsole(fn, optionalName) { if (optionalName
&& historicalConsole[optionalName]) { //names not supported quite yet //return historicalConsole[optionalName];
} //validate callback: if (Object.prototype.toString.call(fn) != '[object Function]'
|| arguments.length !== 1 || fn.length !== 1) { var message = 'historicalConsole
expects one function argument like this: ' + 'historicalConsole(function(console){/*your
whole program*/});'; alert(message); console.error(message + ' You passed in these
arguments: ', arguments); return; } //returning a function allows for more flexible
use of `historicalConsole` return function historicalConsoleClosure() { if (arguments.length
> 0) { console.warn( 'The function returned from historicalConsole does not take
arguments. ' + 'These arguments will be ignored. You\'re probably doing something
wrong' ); } //override global console temporarily so that console.* calls from externals
//are logged too. This could be developer feedback from external libraries //regarding
the callbacks use of that library var oldConsole = window.console; window.console
= console; try { fn(console); } catch (e) { if (typeof onuncaughtException !== 'undefined')
{ //probably window.onuncaughtError but maybe not. you can var over it onuncaughtException(e);
//this function can choose to re-throw or not, anyone's choice } else { console.warn(
'You should define a window.onuncaughtException handler for exceptions,' + ' or use
a library like Sheild.js' ); throw e; } } finally { //finally block so we can restore
window.console even if we re-throw an error window.console = oldConsole; } }; }
<script>
Benchmark.prototype.setup = function() {
    //some real code!
   
   
    function historicalConsole(fn, optionalName) {
   
      if (optionalName && historicalConsole[optionalName]) {
        //names not supported quite yet
        //return historicalConsole[optionalName];
      }
   
      //validate callback:
      if (Object.prototype.toString.call(fn) != '[object Function]' || arguments.length !== 1 || fn.length !== 1) {
        var message = 'historicalConsole expects one function argument like this: ' + 'historicalConsole(function(console){/*your whole program*/});';
        alert(message);
        console.error(message + ' You passed in these arguments: ', arguments);
        return;
      }
   
      //returning a function allows for more flexible use of `historicalConsole`
      return function historicalConsoleClosure() {
        if (arguments.length > 0) {
          console.warn('The function returned from historicalConsole does not take arguments. ' + 'These arguments will be ignored. You\'re probably doing something wrong');
        }
        //override global console temporarily so that console.* calls from externals
        //are logged too. This could be developer feedback from external libraries
        //regarding the callbacks use of that library
        var oldConsole = window.console;
        window.console = console;
        try {
          fn(console);
        } catch (e) {
          if (typeof onuncaughtException !== 'undefined') { //probably window.onuncaughtError but maybe not. you can var over it
            onuncaughtException(e); //this function can choose to re-throw or not, anyone's choice
          } else {
            console.warn('You should define a window.onuncaughtException handler for exceptions,' + ' or use a library like Sheild.js');
            throw e;
          }
        } finally { //finally block so we can restore window.console even if we re-throw an error
          window.console = oldConsole;
        }
      };
    }
};

Benchmark.prototype.teardown = function() {
    historicalConsole.prototype = {};
    for (var method in historicalConsole) {
      if (historicalConsole.hasOwnProperty(method)) {
        historicalConsole[method] = null;
      }
    }
};
</script>

Preparation code output

//some real code! function historicalConsole(fn, optionalName) { if (optionalName && historicalConsole[optionalName]) { //names not supported quite yet //return historicalConsole[optionalName]; } //validate callback: if (Object.prototype.toString.call(fn) != '[object Function]' || arguments.length !== 1 || fn.length !== 1) { var message = 'historicalConsole expects one function argument like this: ' + 'historicalConsole(function(console){/*your whole program*/});'; alert(message); console.error(message + ' You passed in these arguments: ', arguments); return; } //returning a function allows for more flexible use of `historicalConsole` return function historicalConsoleClosure() { if (arguments.length > 0) { console.warn( 'The function returned from historicalConsole does not take arguments. ' + 'These arguments will be ignored. You\'re probably doing something wrong' ); } //override global console temporarily so that console.* calls from externals //are logged too. This could be developer feedback from external libraries //regarding the callbacks use of that library var oldConsole = window.console; window.console = console; try { fn(console); } catch (e) { if (typeof onuncaughtException !== 'undefined') { //probably window.onuncaughtError but maybe not. you can var over it onuncaughtException(e); //this function can choose to re-throw or not, anyone's choice } else { console.warn( 'You should define a window.onuncaughtException handler for exceptions,' + ' or use a library like Sheild.js' ); throw e; } } finally { //finally block so we can restore window.console even if we re-throw an error window.console = oldConsole; } }; }

Test runner

Warning! For accurate results, please disable Firebug before running the tests. (Why?)

Java applet disabled.

Testing in unknown unknown
Test Ops/sec
proto
historicalConsole.prototype = {
  resolveFunctionName: function historicalConsole_resolveFunctionName(func) {
    if (!func) {
      return 'null'; //null is when there is not caller (global scope)
    }
    var toString = func.toString().substring(0, 250) //so the regex doesn't become a performance hog
    .replace(/\s+/g, ' ');
    //collapse excess whitespace so function snippits for
    //non-named functions are more much more informative
    if (window.ie) {
      func.name = toString
      //all chars be 'function' and '(' (if there are no chars, then '')
      .substring(8, toString.indexOf('(', 8)).replace(/^\s+|\s+$/g, ''); //'function (){' => ' ' becomes the falsey ''
    }
    return func.name || toString.substring(0, internalOptions.functionSnippetLength);
  },
  // Big thanks to @NV for console.js: github.com/NV/console.js
  // saveHooks modify arguments before being saved to console.history
  // the return values are the modified args
  saveHooks: {
    assert: function console_saveHook_assert(isOk, message) {
      return ['Assertion ' + (isOk ? 'successful' : 'failed') + ': ' + message];
    },
    count: (function() {
      var counters = {};
      return function console_saveHook_count(title) {
        if (!title) {
          //this is the *key* to counters, not the *value*
          title = Math.floor((Math.random() * 100000) + 1).toString();
        }
        if (counters[title]) {
          counters[title]++;
        } else {
          counters[title] = 1;
        }
        return title + ' ' + counters[title];
      };
    })(),
/*
      //stringifyArguments/_source_of(argument)
      //https://github.com/eriwen/javascript-stacktrace/blob/master/stacktrace.js#L272-L300
      //https://github.com/NV/console.js/blob/gh-pages/console.js#L70-L131
      dir: function console_saveHook_dir() {
        var result = [];
        for (var i = 0; i < arguments.length; i++) {
          result.push(console._source_of(arguments[i], console.dimensions_limit, []));
        }
        return result.join(console._args_separator);
      },
      */

/*
      //try to glean something from jsperf:
      profile: function console_saveHook_profile() {
      },
      profileEnd: function console_saveHook_profileEnd() {
      },
      */

    time: function console_saveHook_time(name) {
      if (name === undefined) {
        throw new Error('console.time needs a title for your timing like console.time(\'lookup\')');
      }
      startTimes[name] = now();
      return [name];
    },
    timeEnd: function console_saveHook_timeEnd(name) {
      return [(name + ': ' + (now() - startTimes[name]) + 'ms')];
    },
    timeStamp: function console_saveHook_timeStamp(optionalLabel) {
      return [now(), optionalLabel];
    },
    trace: function console_saveHook_trace() {
      return [ /*counter?*/ (new Error('console.trace()')).stack || 'stack traces not supported'];
    }
  },

  noConflict: function historicalConsole_noConflict() {
    window.historicalConsole = _oldHistoricalConsole;
    trackAction('noConflict');
    return historicalConsole;
  }
};
pending…
props
historicalConsole.resolveFunctionName = function historicalConsole_resolveFunctionName(func) {
  if (!func) {
    return 'null'; //null is when there is not caller (global scope)
  }
  var toString = func.toString().substring(0, 250) //so the regex doesn't become a performance hog
  .replace(/\s+/g, ' ');
  //collapse excess whitespace so function snippits for
  //non-named functions are more much more informative
  if (window.ie) {
    func.name = toString
    //all chars be 'function' and '(' (if there are no chars, then '')
    .substring(8, toString.indexOf('(', 8)).replace(/^\s+|\s+$/g, ''); //'function (){' => ' ' becomes the falsey ''
  }
  return func.name || toString.substring(0, internalOptions.functionSnippetLength);
};
// Big thanks to @NV for console.js: github.com/NV/console.js
// saveHooks modify arguments before being saved to console.history
// the return values are the modified args
historicalConsole.saveHooks = {
  assert: function console_saveHook_assert(isOk, message) {
    return ['Assertion ' + (isOk ? 'successful' : 'failed') + ': ' + message];
  },
  count: (function() {
    var counters = {};
    return function console_saveHook_count(title) {
      if (!title) {
        //this is the *key* to counters, not the *value*
        title = Math.floor((Math.random() * 100000) + 1).toString();
      }
      if (counters[title]) {
        counters[title]++;
      } else {
        counters[title] = 1;
      }
      return title + ' ' + counters[title];
    };
  })(),
/*
      //stringifyArguments/_source_of(argument)
      //https://github.com/eriwen/javascript-stacktrace/blob/master/stacktrace.js#L272-L300
      //https://github.com/NV/console.js/blob/gh-pages/console.js#L70-L131
      dir: function console_saveHook_dir() {
        var result = [];
        for (var i = 0; i < arguments.length; i++) {
          result.push(console._source_of(arguments[i], console.dimensions_limit, []));
        }
        return result.join(console._args_separator);
      },
      */

/*
      //try to glean something from jsperf:
      profile: function console_saveHook_profile() {
      },
      profileEnd: function console_saveHook_profileEnd() {
      },
      */

  time: function console_saveHook_time(name) {
    if (name === undefined) {
      throw new Error('console.time needs a title for your timing like console.time(\'lookup\')');
    }
    startTimes[name] = now();
    return [name];
  },
  timeEnd: function console_saveHook_timeEnd(name) {
    return [(name + ': ' + (now() - startTimes[name]) + 'ms')];
  },
  timeStamp: function console_saveHook_timeStamp(optionalLabel) {
    return [now(), optionalLabel];
  },
  trace: function console_saveHook_trace() {
    return [ /*counter?*/ (new Error('console.trace()')).stack || 'stack traces not supported'];
  }
};

historicalConsole.noConflict = function historicalConsole_noConflict() {
  window.historicalConsole = _oldHistoricalConsole;
  trackAction('noConflict');
  return historicalConsole;
};
pending…

You can edit these tests or add even more tests to this page by appending /edit to the URL.

Compare results of other browsers

0 comments

Add a comment