Mixin

JavaScript performance comparison

Revision 3 of this test case created

Info

Native Array vs. Emitter Array vs. Collection push method testing (not measuring the initial object creation, removed console.log)

Preparation code

 
<script>
Benchmark.prototype.setup = function() {
    /**
     * Require the given path.
     *
     * @param {String} path
     * @return {Object} exports
     * @api public
     */

   
    function require(p, parent, orig) {
      var path = require.resolve(p),
          mod = require.modules[path];
   
      // lookup failed
      if (null == path) {
        orig = orig || p;
        parent = parent || 'root';
        throw new Error('failed to require "' + orig + '" from "' + parent + '"');
      }
   
      // perform real require()
      // by invoking the module's
      // registered function
      if (!mod.exports) {
        mod.exports = {};
        mod.client = mod.component = true;
        mod.call(this, mod, mod.exports, require.relative(path));
      }
   
      return mod.exports;
    }
   
    /**
     * Registered modules.
     */

   
    require.modules = {};
   
    /**
     * Registered aliases.
     */

   
    require.aliases = {};
   
    /**
     * Resolve `path`.
     *
     * Lookup:
     *
     *   - PATH/index.js
     *   - PATH.js
     *   - PATH
     *
     * @param {String} path
     * @return {String} path or null
     * @api private
     */

   
    require.resolve = function(path) {
      var orig = path,
          reg = path + '.js',
          regJSON = path + '.json',
          index = path + '/index.js',
          indexJSON = path + '/index.json';
   
      return require.modules[reg] && reg || require.modules[regJSON] && regJSON || require.modules[index] && index || require.modules[indexJSON] && indexJSON || require.modules[orig] && orig || require.aliases[index];
    };
   
    /**
     * Normalize `path` relative to the current path.
     *
     * @param {String} curr
     * @param {String} path
     * @return {String}
     * @api private
     */

   
    require.normalize = function(curr, path) {
      var segs = [];
   
      if ('.' != path.charAt(0)) return path;
   
      curr = curr.split('/');
      path = path.split('/');
   
      for (var i = 0; i < path.length; ++i) {
        if ('..' == path[i]) {
          curr.pop();
        } else if ('.' != path[i] && '' != path[i]) {
          segs.push(path[i]);
        }
      }
   
      return curr.concat(segs).join('/');
    };
   
    /**
     * Register module at `path` with callback `fn`.
     *
     * @param {String} path
     * @param {Function} fn
     * @api private
     */

   
    require.register = function(path, fn) {
      require.modules[path] = fn;
    };
   
    /**
     * Alias a module definition.
     *
     * @param {String} from
     * @param {String} to
     * @api private
     */

   
    require.alias = function(from, to) {
      var fn = require.modules[from];
      if (!fn) throw new Error('failed to alias "' + from + '", it does not exist');
      require.aliases[to] = from;
    };
   
    /**
     * Return a require function relative to the `parent` path.
     *
     * @param {String} parent
     * @return {Function}
     * @api private
     */

   
    require.relative = function(parent) {
      var p = require.normalize(parent, '..');
   
      /**
       * lastIndexOf helper.
       */

   
      function lastIndexOf(arr, obj) {
        var i = arr.length;
        while (i--) {
          if (arr[i] === obj) return i;
        }
        return -1;
      }
   
      /**
       * The relative require() itself.
       */

   
      function fn(path) {
        var orig = path;
        path = fn.resolve(path);
        return require(path, parent, orig);
      }
   
      /**
       * Resolve relative to the parent.
       */

   
      fn.resolve = function(path) {
        // resolve deps by returning
        // the dep in the nearest "deps"
        // directory
        if ('.' != path.charAt(0)) {
          var segs = parent.split('/');
          var i = lastIndexOf(segs, 'deps') + 1;
          if (!i) i = 0;
          path = segs.slice(0, i + 1).join('/') + '/deps/' + path;
          return path;
        }
        return require.normalize(p, path);
      };
   
      /**
       * Check if module is defined at `path`.
       */

   
      fn.exists = function(path) {
        return !!require.modules[fn.resolve(path)];
      };
   
      return fn;
    };
    require.register("component-emitter/index.js", function(module, exports, require) {
   
      /**
       * Expose `Emitter`.
       */

   
      module.exports = Emitter;
   
      /**
       * Initialize a new `Emitter`.
       *
       * @api public
       */

   
      function Emitter(obj) {
        if (obj) return mixin(obj);
      };
   
      /**
       * Mixin the emitter properties.
       *
       * @param {Object} obj
       * @return {Object}
       * @api private
       */

   
      function mixin(obj) {
        for (var key in Emitter.prototype) {
          obj[key] = Emitter.prototype[key];
        }
        return obj;
      }
   
      /**
       * Listen on the given `event` with `fn`.
       *
       * @param {String} event
       * @param {Function} fn
       * @return {Emitter}
       * @api public
       */

   
      Emitter.prototype.on = function(event, fn) {
        this._callbacks = this._callbacks || {};
        (this._callbacks[event] = this._callbacks[event] || []).push(fn);
        return this;
      };
   
      /**
       * Adds an `event` listener that will be invoked a single
       * time then automatically removed.
       *
       * @param {String} event
       * @param {Function} fn
       * @return {Emitter}
       * @api public
       */

   
      Emitter.prototype.once = function(event, fn) {
        var self = this;
        this._callbacks = this._callbacks || {};
   
        function on() {
          self.off(event, on);
          fn.apply(this, arguments);
        }
   
        fn._off = on;
        this.on(event, on);
        return this;
      };
   
      /**
       * Remove the given callback for `event` or all
       * registered callbacks.
       *
       * @param {String} event
       * @param {Function} fn
       * @return {Emitter}
       * @api public
       */

   
      Emitter.prototype.off = function(event, fn) {
        this._callbacks = this._callbacks || {};
        var callbacks = this._callbacks[event];
        if (!callbacks) return this;
   
        // remove all handlers
        if (1 == arguments.length) {
          delete this._callbacks[event];
          return this;
        }
   
        // remove specific handler
        var i = callbacks.indexOf(fn._off || fn);
        if (~i) callbacks.splice(i, 1);
        return this;
      };
   
      /**
       * Emit `event` with the given args.
       *
       * @param {String} event
       * @param {Mixed} ...
       * @return {Emitter}
       */

   
      Emitter.prototype.emit = function(event) {
        this._callbacks = this._callbacks || {};
        var args = [].slice.call(arguments, 1),
            callbacks = this._callbacks[event];
   
        if (callbacks) {
          callbacks = callbacks.slice(0);
          for (var i = 0, len = callbacks.length; i < len; ++i) {
            callbacks[i].apply(this, args);
          }
        }
   
        return this;
      };
   
      /**
       * Return array of callbacks for `event`.
       *
       * @param {String} event
       * @return {Array}
       * @api public
       */

   
      Emitter.prototype.listeners = function(event) {
        this._callbacks = this._callbacks || {};
        return this._callbacks[event] || [];
      };
   
      /**
       * Check if this emitter has `event` handlers.
       *
       * @param {String} event
       * @return {Boolean}
       * @api public
       */

   
      Emitter.prototype.hasListeners = function(event) {
        return !!this.listeners(event).length;
      };
   
   
    });
    require.register("array/array.js", function(module, exports, require) {
      /**
       * Module dependencies
       */

   
      var Emitter = require('emitter');
   
      /**
       * Expose `array`
       */

   
      module.exports = array;
   
      /**
       * Array prototype
       */

   
      var proto = Array.prototype;
   
      /**
       * Initalize `array`
       *
       * @param {Array} arr
       * @return {array}
       */

   
      function array(arr) {
        if (!(this instanceof array)) return new array(arr);
        return mixin(arr);
      }
   
      /**
       * Inherit from `Emitter`
       */

   
      Emitter(array.prototype);
   
      /**
       * Removes the last element from an array and returns that element
       *
       * @return {Mixed} removed element
       */

   
      array.prototype.pop = function() {
        var ret = proto.pop.apply(this, arguments);
        this.emit('pop', ret);
        this.emit('remove', ret);
        return ret;
      };
   
      /**
       * Push a value onto the end of the array,
       * returning the length of the array
       *
       * @param {Mixed, ...} elements
       * @return {Number}
       */

   
      array.prototype.push = function() {
        var ret = proto.push.apply(this, arguments),
            args = [].slice.call(arguments);
        this.emit('push', ret);
        for (var i = 0, len = args.length; i < len; i++) this.emit('add', args[i]);
        return ret;
      };
   
      /**
       * Reverses an array in place.
       *
       * @return {Array}
       */

   
      array.prototype.reverse = function() {
        var ret = proto.reverse.apply(this, arguments);
        this.emit('reverse', ret);
        return ret;
      };
   
      /**
       * Removes the first element from an array and returns that element.
       *
       * @return {Mixed}
       */

   
      array.prototype.shift = function() {
        var ret = proto.shift.apply(this, arguments);
        this.emit('shift', ret);
        this.emit('remove', ret);
        return ret;
      };
   
      /**
       * Sorts the elements of an array.
       *
       * @return {Array}
       */

   
      array.prototype.sort = function() {
        var ret = proto.sort.apply(this, arguments);
        this.emit('sort', ret);
        return ret;
      };
   
      /**
       * Adds and/or removes elements from an array.
       *
       * @param {Number} index
       * @param {Number} howMany
       * @param {Mixed, ...} elements
       * @return {Array} removed elements
       */

   
      array.prototype.splice = function() {
        var ret = proto.splice.apply(this, arguments),
            added = [].slice.call(arguments, 2);
        this.emit('splice', ret);
        for (var i = 0, len = ret.length; i < len; i++) this.emit('remove', ret[i]);
        for (i = 0, len = added.length; i < len; i++) this.emit('add', added[i]);
        return ret;
      };
   
      /**
       * Adds one or more elements to the front of an array
       * and returns the new length of the array.
       *
       * @param {Mixed, ...} elements
       * @return {Number} length
       */

   
      array.prototype.unshift = function() {
        var ret = proto.unshift.apply(this, arguments),
            args = [].slice.call(arguments);
        this.emit('unshift', ret);
        for (var i = 0, len = args.length; i < len; i++) this.emit('add', args[i]);
        return ret;
      };
   
      /**
       * Mixin to `arr`.
       *
       *    var array = require('array');
       *    array(Something.prototype);
       *
       * @param {Object} arr
       * @return {Object} arr
       */

   
      function mixin(arr) {
        for (var key in array.prototype)
        arr[key] = array.prototype[key];
        return arr;
      }
   
    });
    require.alias("component-emitter/index.js", "array/deps/emitter/index.js");
   
    require.alias("array/array.js", "array/index.js");
    var array = require('array');
    var arrr = array([]);
   
    require.register("component-to-function/index.js", function(module, exports, require) {
   
      /**
       * Expose `toFunction()`.
       */

   
      module.exports = toFunction;
   
      /**
       * Convert `obj` to a `Function`.
       *
       * TODO: consider compiling to functions.
       *
       * @param {Mixed} obj
       * @return {Function}
       * @api private
       */

   
      function toFunction(obj) {
        switch (typeof obj) {
        case 'function':
          return obj;
        case 'string':
          return stringToFunction(obj);
        default:
          throw new TypeError('invalid callback "' + obj + '"');
        }
      }
   
      /**
       * Convert property `str` to a function.
       *
       * @param {String} str
       * @return {Function}
       * @api private
       */

   
      function stringToFunction(str) {
        var props = str.split('.');
        return function(obj) {
          for (var i = 0; i < props.length; ++i) {
            if (null == obj) return;
            var name = props[i];
            if ('function' == typeof obj[name]) {
              obj = obj[name]();
            } else {
              obj = obj[name];
            }
          }
          return obj;
        }
      }
    });
    require.register("component-enumerable/index.js", function(module, exports, require) {
   
      /**
       * Module dependencies.
       */

   
      var toFunction = require('to-function'),
          proto = {};
   
      /**
       * Expose `Enumerable`.
       */

   
      module.exports = Enumerable;
   
      /**
       * Mixin to `obj`.
       *
       *    var Enumerable = require('enumerable');
       *    Enumerable(Something.prototype);
       *
       * @param {Object} obj
       * @return {Object} obj
       */

   
      function mixin(obj) {
        for (var key in proto) obj[key] = proto[key];
        obj.__iterate__ = obj.__iterate__ || defaultIterator;
        return obj;
      }
   
      /**
       * Initialize a new `Enumerable` with the given `obj`.
       *
       * @param {Object} obj
       * @api private
       */

   
      function Enumerable(obj) {
        if (!(this instanceof Enumerable)) {
          if (Array.isArray(obj)) return new Enumerable(obj);
          return mixin(obj);
        }
        this.obj = obj;
      }
   
    /*!
     * Default iterator utilizing `.length` and subscripts.
     */

   
      function defaultIterator() {
        var self = this;
        return {
          length: function() {
            return self.length
          },
          get: function(i) {
            return self[i]
          }
        }
      }
   
      /**
       * Return a string representation of this enumerable.
       *
       *    [Enumerable [1,2,3]]
       *
       * @return {String}
       * @api public
       */

   
      Enumerable.prototype.inspect = Enumerable.prototype.toString = function() {
        return '[Enumerable ' + JSON.stringify(this.obj) + ']';
      };
   
      /**
       * Iterate enumerable.
       *
       * @return {Object}
       * @api private
       */

   
      Enumerable.prototype.__iterate__ = function() {
        var obj = this.obj;
        obj.__iterate__ = obj.__iterate__ || defaultIterator;
        return obj.__iterate__();
      };
   
      /**
       * Iterate each value and invoke `fn(val, i)`.
       *
       *    users.each(function(val, i){
       *      
       *    })
       *
       * @param {Function} fn
       * @return {Object} self
       * @api public
       */

   
      proto.each = function(fn) {
        var vals = this.__iterate__();
        var len = vals.length();
        for (var i = 0; i < len; ++i) {
          fn(vals.get(i), i);
        }
        return this;
      };
   
      /**
       * Map each return value from `fn(val, i)`.
       *
       * Passing a callback function:
       *
       *    users.map(function(user){
       *      return user.name.first
       *    })
       *
       * Passing a property string:
       *
       *    users.map('name.first')
       *
       * @param {Function} fn
       * @return {Enumerable}
       * @api public
       */

   
      proto.map = function(fn) {
        fn = toFunction(fn);
        var vals = this.__iterate__();
        var len = vals.length();
        var arr = [];
        for (var i = 0; i < len; ++i) {
          arr.push(fn(vals.get(i), i));
        }
        return new Enumerable(arr);
      };
   
      /**
       * Select all values that return a truthy value of `fn(val, i)`.
       *
       *    users.select(function(user){
       *      return user.age > 20
       *    })
       *
       *  With a property:
       *
       *    items.select('complete')
       *
       * @param {Function|String} fn
       * @return {Enumerable}
       * @api public
       */

   
      proto.select = function(fn) {
        fn = toFunction(fn);
        var val;
        var arr = [];
        var vals = this.__iterate__();
        var len = vals.length();
        for (var i = 0; i < len; ++i) {
          val = vals.get(i);
          if (fn(val, i)) arr.push(val);
        }
        return new Enumerable(arr);
      };
   
      /**
       * Select all unique values.
       *
       *    nums.unique()
       *
       * @return {Enumerable}
       * @api public
       */

   
      proto.unique = function() {
        var val;
        var arr = [];
        var vals = this.__iterate__();
        var len = vals.length();
        for (var i = 0; i < len; ++i) {
          val = vals.get(i);
          if (~arr.indexOf(val)) continue;
          arr.push(val);
        }
        return new Enumerable(arr);
      };
   
      /**
       * Reject all values that return a truthy value of `fn(val, i)`.
       *
       * Rejecting using a callback:
       *
       *    users.reject(function(user){
       *      return user.age < 20
       *    })
       *
       * Rejecting with a property:
       *
       *    items.reject('complete')
       *
       * Rejecting values via `==`:
       *
       *    data.reject(null)
       *    users.reject(tobi)
       *
       * @param {Function|String|Mixed} fn
       * @return {Enumerable}
       * @api public
       */

   
      proto.reject = function(fn) {
        var val;
        var arr = [];
        var vals = this.__iterate__();
        var len = vals.length();
   
        if ('string' == typeof fn) fn = toFunction(fn);
   
        if (fn) {
          for (var i = 0; i < len; ++i) {
            val = vals.get(i);
            if (!fn(val, i)) arr.push(val);
          }
        } else {
          for (var i = 0; i < len; ++i) {
            val = vals.get(i);
            if (val != fn) arr.push(val);
          }
        }
   
        return new Enumerable(arr);
      };
   
      /**
       * Reject `null` and `undefined`.
       *
       *    [1, null, 5, undefined].compact()
       *    // => [1,5]
       *
       * @return {Enumerable}
       * @api public
       */

   
   
      proto.compact = function() {
        return this.reject(null);
      };
   
      /**
       * Return the first value when `fn(val, i)` is truthy,
       * otherwise return `undefined`.
       *
       *    users.find(function(user){
       *      return user.role == 'admin'
       *    })
       *
       * @param {Function} fn
       * @return {Mixed}
       * @api public
       */

   
      proto.find = function(fn) {
        var val;
        var vals = this.__iterate__();
        var len = vals.length();
        for (var i = 0; i < len; ++i) {
          val = vals.get(i);
          if (fn(val, i)) return val;
        }
      };
   
      /**
       * Return the last value when `fn(val, i)` is truthy,
       * otherwise return `undefined`.
       *
       *    users.findLast(function(user){
       *      return user.role == 'admin'
       *    })
       *
       * @param {Function} fn
       * @return {Mixed}
       * @api public
       */

   
      proto.findLast = function(fn) {
        var ret;
        var val;
        var vals = this.__iterate__();
        var len = vals.length();
        for (var i = 0; i < len; ++i) {
          val = vals.get(i);
          if (fn(val, i)) ret = val;
        }
        return ret;
      };
   
      /**
       * Assert that all invocations of `fn(val, i)` are truthy.
       *
       * For example ensuring that all pets are ferrets:
       *
       *    pets.all(function(pet){
       *      return pet.species == 'ferret'
       *    })
       *
       *    users.all('admin')
       *
       * @param {Function|String} fn
       * @return {Boolean}
       * @api public
       */

   
      proto.all = proto.every = function(fn) {
        fn = toFunction(fn);
        var val;
        var vals = this.__iterate__();
        var len = vals.length();
        for (var i = 0; i < len; ++i) {
          val = vals.get(i);
          if (!fn(val, i)) return false;
        }
        return true;
      };
   
      /**
       * Assert that none of the invocations of `fn(val, i)` are truthy.
       *
       * For example ensuring that no pets are admins:
       *
       *    pets.none(function(p){ return p.admin })
       *    pets.none('admin')
       *
       * @param {Function|String} fn
       * @return {Boolean}
       * @api public
       */

   
      proto.none = function(fn) {
        fn = toFunction(fn);
        var val;
        var vals = this.__iterate__();
        var len = vals.length();
        for (var i = 0; i < len; ++i) {
          val = vals.get(i);
          if (fn(val, i)) return false;
        }
        return true;
      };
   
      /**
       * Assert that at least one invocation of `fn(val, i)` is truthy.
       *
       * For example checking to see if any pets are ferrets:
       *
       *    pets.any(function(pet){
       *      return pet.species == 'ferret'
       *    })
       *
       * @param {Function} fn
       * @return {Boolean}
       * @api public
       */

   
      proto.any = function(fn) {
        fn = toFunction(fn);
        var val;
        var vals = this.__iterate__();
        var len = vals.length();
        for (var i = 0; i < len; ++i) {
          val = vals.get(i);
          if (fn(val, i)) return true;
        }
        return false;
      };
   
      /**
       * Count the number of times `fn(val, i)` returns true.
       *
       *    var n = pets.count(function(pet){
       *      return pet.species == 'ferret'
       *    })
       *
       * @param {Function} fn
       * @return {Number}
       * @api public
       */

   
      proto.count = function(fn) {
        var val;
        var vals = this.__iterate__();
        var len = vals.length();
        var n = 0;
        for (var i = 0; i < len; ++i) {
          val = vals.get(i);
          if (fn(val, i))++n;
        }
        return n;
      };
   
      /**
       * Determine the indexof `obj` or return `-1`.
       *
       * @param {Mixed} obj
       * @return {Number}
       * @api public
       */

   
      proto.indexOf = function(obj) {
        var val;
        var vals = this.__iterate__();
        var len = vals.length();
        for (var i = 0; i < len; ++i) {
          val = vals.get(i);
          if (val === obj) return i;
        }
        return -1;
      };
   
      /**
       * Check if `obj` is present in this enumerable.
       *
       * @param {Mixed} obj
       * @return {Boolean}
       * @api public
       */

   
      proto.has = function(obj) {
        return !!~this.indexOf(obj);
      };
   
      /**
       * Grep values using the given `re`.
       *
       *    users.map('name').grep(/^tobi/i)
       *
       * @param {RegExp} re
       * @return {Enumerable}
       * @api public
       */

   
      proto.grep = function(re) {
        var val;
        var vals = this.__iterate__();
        var len = vals.length();
        var arr = [];
        for (var i = 0; i < len; ++i) {
          val = vals.get(i);
          if (re.test(val)) arr.push(val);
        }
        return new Enumerable(arr);
      };
   
      /**
       * Reduce with `fn(accumulator, val, i)` using
       * optional `init` value defaulting to the first
       * enumerable value.
       *
       * @param {Function} fn
       * @param {Mixed} [val]
       * @return {Mixed}
       * @api public
       */

   
      proto.reduce = function(fn, init) {
        var val;
        var i = 0;
        var vals = this.__iterate__();
        var len = vals.length();
   
        val = null == init ? vals.get(i++) : init;
   
        for (; i < len; ++i) {
          val = fn(val, vals.get(i), i);
        }
   
        return val;
      };
   
      /**
       * Determine the max value.
       *
       * With a callback function:
       *
       *    pets.max(function(pet){
       *      return pet.age
       *    })
       *
       * With property strings:
       *
       *    pets.max('age')
       *
       * With immediate values:
       *
       *    nums.max()
       *
       * @param {Function|String} fn
       * @return {Number}
       * @api public
       */

   
      proto.max = function(fn) {
        var val;
        var n = 0;
        var max = 0;
        var vals = this.__iterate__();
        var len = vals.length();
   
        if (fn) {
          fn = toFunction(fn);
          for (var i = 0; i < len; ++i) {
            n = fn(vals.get(i), i);
            max = n > max ? n : max;
          }
        } else {
          for (var i = 0; i < len; ++i) {
            n = vals.get(i);
            max = n > max ? n : max;
          }
        }
   
        return max;
      };
   
      /**
       * Determine the sum.
       *
       * With a callback function:
       *
       *    pets.sum(function(pet){
       *      return pet.age
       *    })
       *
       * With property strings:
       *
       *    pets.sum('age')
       *
       * With immediate values:
       *
       *    nums.sum()
       *
       * @param {Function|String} fn
       * @return {Number}
       * @api public
       */

   
      proto.sum = function(fn) {
        var ret;
        var n = 0;
        var vals = this.__iterate__();
        var len = vals.length();
   
        if (fn) {
          fn = toFunction(fn);
          for (var i = 0; i < len; ++i) {
            n += fn(vals.get(i), i);
          }
        } else {
          for (var i = 0; i < len; ++i) {
            n += vals.get(i);
          }
        }
   
        return n;
      };
   
      /**
       * Determine the average value.
       *
       * With a callback function:
       *
       *    pets.avg(function(pet){
       *      return pet.age
       *    })
       *
       * With property strings:
       *
       *    pets.avg('age')
       *
       * With immediate values:
       *
       *    nums.avg()
       *
       * @param {Function|String} fn
       * @return {Number}
       * @api public
       */

   
      proto.avg = proto.mean = function(fn) {
        var ret;
        var n = 0;
        var vals = this.__iterate__();
        var len = vals.length();
   
        if (fn) {
          fn = toFunction(fn);
          for (var i = 0; i < len; ++i) {
            n += fn(vals.get(i), i);
          }
        } else {
          for (var i = 0; i < len; ++i) {
            n += vals.get(i);
          }
        }
   
        return n / len;
      };
   
      /**
       * Return the first value, or first `n` values.
       *
       * @param {Number|Function} [n]
       * @return {Array|Mixed}
       * @api public
       */

   
      proto.first = function(n) {
        if ('function' == typeof n) return this.find(n);
        var vals = this.__iterate__();
   
        if (n) {
          var len = Math.min(n, vals.length());
          var arr = new Array(len);
          for (var i = 0; i < len; ++i) {
            arr[i] = vals.get(i);
          }
          return arr;
        }
   
        return vals.get(0);
      };
   
      /**
       * Return the last value, or last `n` values.
       *
       * @param {Number|Function} [n]
       * @return {Array|Mixed}
       * @api public
       */

   
      proto.last = function(n) {
        if ('function' == typeof n) return this.findLast(n);
        var vals = this.__iterate__();
        var len = vals.length();
   
        if (n) {
          var i = Math.max(0, len - n);
          var arr = [];
          for (; i < len; ++i) {
            arr.push(vals.get(i));
          }
          return arr;
        }
   
        return vals.get(len - 1);
      };
   
      /**
       * Return values in groups of `n`.
       *
       * @param {Number} n
       * @return {Enumerable}
       * @api public
       */

   
      proto.inGroupsOf = function(n) {
        var arr = [];
        var group = [];
        var vals = this.__iterate__();
        var len = vals.length();
   
        for (var i = 0; i < len; ++i) {
          group.push(vals.get(i));
          if ((i + 1) % n == 0) {
            arr.push(group);
            group = [];
          }
        }
   
        if (group.length) arr.push(group);
   
        return new Enumerable(arr);
      };
   
      /**
       * Return the value at the given index.
       *
       * @param {Number} i
       * @return {Mixed}
       * @api public
       */

   
      proto.at = function(i) {
        return this.__iterate__().get(i);
      };
   
      /**
       * Return a regular `Array`.
       *
       * @return {Array}
       * @api public
       */

   
      proto.toJSON = proto.array = function() {
        var arr = [];
        var vals = this.__iterate__();
        var len = vals.length();
        for (var i = 0; i < len; ++i) {
          arr.push(vals.get(i));
        }
        return arr;
      };
   
      /**
       * Return the enumerable value.
       *
       * @return {Mixed}
       * @api public
       */

   
      proto.value = function() {
        return this.obj;
      };
   
      /**
       * Mixin enumerable.
       */

   
      mixin(Enumerable.prototype);
   
      // TODO:
      //   docs
      //   optional async?
      //   .equal()
      //   toFunction for most
      //   .sort
      //   .median
      //   .mean
      //   .mode
      //   .groupBy
      //   .minmax
      //   .flatten
      //   .union
      //   .intersect
      //   .unique
      //   .none
      //   .without(val)
      //   .compact == .without(null)
      //   .has / .contains
      //   .difference .diff
    });
    require.register("component-emitter/index.js", function(module, exports, require) {
   
      /**
       * Expose `Emitter`.
       */

   
      module.exports = Emitter;
   
      /**
       * Initialize a new `Emitter`.
       *
       * @api public
       */

   
      function Emitter(obj) {
        if (obj) return mixin(obj);
      };
   
      /**
       * Mixin the emitter properties.
       *
       * @param {Object} obj
       * @return {Object}
       * @api private
       */

   
      function mixin(obj) {
        for (var key in Emitter.prototype) {
          obj[key] = Emitter.prototype[key];
        }
        return obj;
      }
   
      /**
       * Listen on the given `event` with `fn`.
       *
       * @param {String} event
       * @param {Function} fn
       * @return {Emitter}
       * @api public
       */

   
      Emitter.prototype.on = function(event, fn) {
        this._callbacks = this._callbacks || {};
        (this._callbacks[event] = this._callbacks[event] || []).push(fn);
        return this;
      };
   
      /**
       * Adds an `event` listener that will be invoked a single
       * time then automatically removed.
       *
       * @param {String} event
       * @param {Function} fn
       * @return {Emitter}
       * @api public
       */

   
      Emitter.prototype.once = function(event, fn) {
        var self = this;
        this._callbacks = this._callbacks || {};
   
        function on() {
          self.off(event, on);
          fn.apply(this, arguments);
        }
   
        fn._off = on;
        this.on(event, on);
        return this;
      };
   
      /**
       * Remove the given callback for `event` or all
       * registered callbacks.
       *
       * @param {String} event
       * @param {Function} fn
       * @return {Emitter}
       * @api public
       */

   
      Emitter.prototype.off = function(event, fn) {
        this._callbacks = this._callbacks || {};
        var callbacks = this._callbacks[event];
        if (!callbacks) return this;
   
        // remove all handlers
        if (1 == arguments.length) {
          delete this._callbacks[event];
          return this;
        }
   
        // remove specific handler
        var i = callbacks.indexOf(fn._off || fn);
        if (~i) callbacks.splice(i, 1);
        return this;
      };
   
      /**
       * Emit `event` with the given args.
       *
       * @param {String} event
       * @param {Mixed} ...
       * @return {Emitter}
       */

   
      Emitter.prototype.emit = function(event) {
        this._callbacks = this._callbacks || {};
        var args = [].slice.call(arguments, 1),
            callbacks = this._callbacks[event];
   
        if (callbacks) {
          callbacks = callbacks.slice(0);
          for (var i = 0, len = callbacks.length; i < len; ++i) {
            callbacks[i].apply(this, args);
          }
        }
   
        return this;
      };
   
      /**
       * Return array of callbacks for `event`.
       *
       * @param {String} event
       * @return {Array}
       * @api public
       */

   
      Emitter.prototype.listeners = function(event) {
        this._callbacks = this._callbacks || {};
        return this._callbacks[event] || [];
      };
   
      /**
       * Check if this emitter has `event` handlers.
       *
       * @param {String} event
       * @return {Boolean}
       * @api public
       */

   
      Emitter.prototype.hasListeners = function(event) {
        return !!this.listeners(event).length;
      };
   
   
    });
    require.register("component-type/index.js", function(module, exports, require) {
   
      /**
       * toString ref.
       */

   
      var toString = Object.prototype.toString;
   
      /**
       * Return the type of `val`.
       *
       * @param {Mixed} val
       * @return {String}
       * @api public
       */

   
      module.exports = function(val) {
        switch (toString.call(val)) {
        case '[object Function]':
          return 'function';
        case '[object Date]':
          return 'date';
        case '[object RegExp]':
          return 'regexp';
        case '[object Arguments]':
          return 'arguments';
        case '[object Array]':
          return 'array';
        }
   
        if (val === null) return 'null';
        if (val === undefined) return 'undefined';
        if (val === Object(val)) return 'object';
   
        return typeof val;
      };
   
    });
    require.register("component-each/index.js", function(module, exports, require) {
   
      /**
       * Module dependencies.
       */

   
      var type = require('type');
   
      /**
       * HOP reference.
       */

   
      var has = Object.prototype.hasOwnProperty;
   
      /**
       * Iterate the given `obj` and invoke `fn(val, i)`.
       *
       * @param {String|Array|Object} obj
       * @param {Function} fn
       * @api public
       */

   
      module.exports = function(obj, fn) {
        switch (type(obj)) {
        case 'array':
          return array(obj, fn);
        case 'object':
          if ('number' == typeof obj.length) return array(obj, fn);
          return object(obj, fn);
        case 'string':
          return string(obj, fn);
        }
      };
   
      /**
       * Iterate string chars.
       *
       * @param {String} obj
       * @param {Function} fn
       * @api private
       */

   
      function string(obj, fn) {
        for (var i = 0; i < obj.length; ++i) {
          fn(obj.charAt(i), i);
        }
      }
   
      /**
       * Iterate object keys.
       *
       * @param {Object} obj
       * @param {Function} fn
       * @api private
       */

   
      function object(obj, fn) {
        for (var key in obj) {
          if (has.call(obj, key)) {
            fn(key, obj[key]);
          }
        }
      }
   
      /**
       * Iterate array-ish.
       *
       * @param {Array|Object} obj
       * @param {Function} fn
       * @api private
       */

   
      function array(obj, fn) {
        for (var i = 0; i < obj.length; ++i) {
          fn(obj[i], i);
        }
      }
    });
    require.register("component-json/index.js", function(module, exports, require) {
   
      module.exports = 'undefined' == typeof JSON ? require('json-fallback') : JSON;
   
    });
    require.register("component-collection/index.js", function(module, exports, require) {
   
      /**
       * Module dependencies.
       */

   
      var Enumerable = require('enumerable');
   
      /**
       * Expose `Collection`.
       */

   
      module.exports = Collection;
   
      /**
       * Initialize a new collection with the given `models`.
       *
       * @param {Array} models
       * @api public
       */

   
      function Collection(models) {
        this.models = models || [];
      }
   
      /**
       * Mixin enumerable.
       */

   
      Enumerable(Collection.prototype);
   
      /**
       * Iterator implementation.
       */

   
      Collection.prototype.__iterate__ = function() {
        var self = this;
        return {
          length: function() {
            return self.length()
          },
          get: function(i) {
            return self.models[i]
          }
        }
      };
   
      /**
       * Return the collection length.
       *
       * @return {Number}
       * @api public
       */

   
      Collection.prototype.length = function() {
        return this.models.length;
      };
   
      /**
       * Add `model` to the collection and return the index.
       *
       * @param {Object} model
       * @return {Number}
       * @api public
       */

   
      Collection.prototype.push = function(model) {
        return this.models.push(model);
      };
   
    });
    require.register("visionmedia-superagent/lib/client.js", function(module, exports, require) {
   
      /**
       * Module dependencies.
       */

   
      var Emitter = require('emitter');
   
      /**
       * Root reference for iframes.
       */

   
      var root = 'undefined' == typeof window ? this : window;
   
      /**
       * Noop.
       */

   
      function noop() {};
   
      /**
       * Determine XHR.
       */

   
      function getXHR() {
        if (root.XMLHttpRequest && ('file:' != root.location.protocol || !root.ActiveXObject)) {
          return new XMLHttpRequest;
        } else {
          try {
            return new ActiveXObject('Microsoft.XMLHTTP');
          } catch (e) {}
          try {
            return new ActiveXObject('Msxml2.XMLHTTP.6.0');
          } catch (e) {}
          try {
            return new ActiveXObject('Msxml2.XMLHTTP.3.0');
          } catch (e) {}
          try {
            return new ActiveXObject('Msxml2.XMLHTTP');
          } catch (e) {}
        }
        return false;
      }
   
      /**
       * Removes leading and trailing whitespace, added to support IE.
       *
       * @param {String} s
       * @return {String}
       * @api private
       */

   
      var trim = ''.trim ?
      function(s) {
        return s.trim();
      } : function(s) {
        return s.replace(/(^\s*|\s*$)/g, '');
      };
   
      /**
       * Check if `obj` is an object.
       *
       * @param {Object} obj
       * @return {Boolean}
       * @api private
       */

   
      function isObject(obj) {
        return obj === Object(obj);
      }
   
      /**
       * Serialize the given `obj`.
       *
       * @param {Object} obj
       * @return {String}
       * @api private
       */

   
      function serialize(obj) {
        if (!isObject(obj)) return obj;
        var pairs = [];
        for (var key in obj) {
          pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(obj[key]));
        }
        return pairs.join('&');
      }
   
      /**
       * Expose serialization method.
       */

   
      request.serializeObject = serialize;
   
      /**
       * Parse the given x-www-form-urlencoded `str`.
       *
       * @param {String} str
       * @return {Object}
       * @api private
       */

   
      function parseString(str) {
        var obj = {};
        var pairs = str.split('&');
        var parts;
        var pair;
   
        for (var i = 0, len = pairs.length; i < len; ++i) {
          pair = pairs[i];
          parts = pair.split('=');
          obj[decodeURIComponent(parts[0])] = decodeURIComponent(parts[1]);
        }
   
        return obj;
      }
   
      /**
       * Expose parser.
       */

   
      request.parseString = parseString;
   
      /**
       * Default MIME type map.
       *
       *     superagent.types.xml = 'application/xml';
       *
       */

   
      request.types = {
        html: 'text/html',
        json: 'application/json',
        urlencoded: 'application/x-www-form-urlencoded',
        'form': 'application/x-www-form-urlencoded',
        'form-data': 'application/x-www-form-urlencoded'
      };
   
      /**
       * Default serialization map.
       *
       *     superagent.serialize['application/xml'] = function(obj){
       *       return 'generated xml here';
       *     };
       *
       */

   
      request.serialize = {
        'application/x-www-form-urlencoded': serialize,
        'application/json': JSON.stringify
      };
   
      /**
       * Default parsers.
       *
       *     superagent.parse['application/xml'] = function(str){
       *       return { object parsed from str };
       *     };
       *
       */

   
      request.parse = {
        'application/x-www-form-urlencoded': parseString,
        'application/json': JSON.parse
      };
   
      /**
       * Parse the given header `str` into
       * an object containing the mapped fields.
       *
       * @param {String} str
       * @return {Object}
       * @api private
       */

   
      function parseHeader(str) {
        var lines = str.split(/\r?\n/);
        var fields = {};
        var index;
        var line;
        var field;
        var val;
   
        lines.pop(); // trailing CRLF
        for (var i = 0, len = lines.length; i < len; ++i) {
          line = lines[i];
          index = line.indexOf(':');
          field = line.slice(0, index).toLowerCase();
          val = trim(line.slice(index + 1));
          fields[field] = val;
        }
   
        return fields;
      }
   
      /**
       * Return the mime type for the given `str`.
       *
       * @param {String} str
       * @return {String}
       * @api private
       */

   
      function type(str) {
        return str.split(/ *; */).shift();
      };
   
      /**
       * Return header field parameters.
       *
       * @param {String} str
       * @return {Object}
       * @api private
       */

   
      function params(str) {
        return str.split(/ *; */).reduce(function(obj, str) {
          var parts = str.split(/ *= */),
              key = parts.shift(),
              val = parts.shift();
   
          if (key && val) obj[key] = val;
          return obj;
        }, {});
      };
   
      /**
       * Initialize a new `Response` with the given `xhr`.
       *
       *  - set flags (.ok, .error, etc)
       *  - parse header
       *
       * Examples:
       *
       *  Aliasing `superagent` as `request` is nice:
       *
       *      request = superagent;
       *
       *  We can use the promise-like API, or pass callbacks:
       *
       *      request.get('/').end(function(res){});
       *      request.get('/', function(res){});
       *
       *  Sending data can be chained:
       *
       *      request
       *        .post('/user')
       *        .send({ name: 'tj' })
       *        .end(function(res){});
       *
       *  Or passed to `.send()`:
       *
       *      request
       *        .post('/user')
       *        .send({ name: 'tj' }, function(res){});
       *
       *  Or passed to `.post()`:
       *
       *      request
       *        .post('/user', { name: 'tj' })
       *        .end(function(res){});
       *
       * Or further reduced to a single call for simple cases:
       *
       *      request
       *        .post('/user', { name: 'tj' }, function(res){});
       *
       * @param {XMLHTTPRequest} xhr
       * @param {Object} options
       * @api private
       */

   
      function Response(xhr, options) {
        options = options || {};
        this.xhr = xhr;
        this.text = xhr.responseText;
        this.setStatusProperties(xhr.status);
        this.header = parseHeader(xhr.getAllResponseHeaders());
        this.setHeaderProperties(this.header);
        this.body = this.parseBody(this.text);
      }
   
      /**
       * Set header related properties:
       *
       *   - `.type` the content type without params
       *
       * A response of "Content-Type: text/plain; charset=utf-8"
       * will provide you with a `.type` of "text/plain".
       *
       * @param {Object} header
       * @api private
       */

   
      Response.prototype.setHeaderProperties = function(header) {
        // content-type
        var ct = this.header['content-type'] || '';
        this.type = type(ct);
   
        // params
        var obj = params(ct);
        for (var key in obj) this[key] = obj[key];
      };
   
      /**
       * Parse the given body `str`.
       *
       * Used for auto-parsing of bodies. Parsers
       * are defined on the `superagent.parse` object.
       *
       * @param {String} str
       * @return {Mixed}
       * @api private
       */

   
      Response.prototype.parseBody = function(str) {
        var parse = request.parse[this.type];
        return parse ? parse(str) : null;
      };
   
      /**
       * Set flags such as `.ok` based on `status`.
       *
       * For example a 2xx response will give you a `.ok` of __true__
       * whereas 5xx will be __false__ and `.error` will be __true__. The
       * `.clientError` and `.serverError` are also available to be more
       * specific, and `.statusType` is the class of error ranging from 1..5
       * sometimes useful for mapping respond colors etc.
       *
       * "sugar" properties are also defined for common cases. Currently providing:
       *
       *   - .noContent
       *   - .badRequest
       *   - .unauthorized
       *   - .notAcceptable
       *   - .notFound
       *
       * @param {Number} status
       * @api private
       */

   
      Response.prototype.setStatusProperties = function(status) {
        var type = status / 100 | 0;
   
        // status / class
        this.status = status;
        this.statusType = type;
   
        // basics
        this.info = 1 == type;
        this.ok = 2 == type;
        this.clientError = 4 == type;
        this.serverError = 5 == type;
        this.error = 4 == type || 5 == type;
   
        // sugar
        this.accepted = 202 == status;
        this.noContent = 204 == status || 1223 == status;
        this.badRequest = 400 == status;
        this.unauthorized = 401 == status;
        this.notAcceptable = 406 == status;
        this.notFound = 404 == status;
        this.forbidden = 403 == status;
      };
   
      /**
       * Expose `Response`.
       */

   
      request.Response = Response;
   
      /**
       * Initialize a new `Request` with the given `method` and `url`.
       *
       * @param {String} method
       * @param {String} url
       * @api public
       */

   
      function Request(method, url) {
        var self = this;
        Emitter.call(this);
        this.method = method;
        this.url = url;
        this.header = {};
        this.set('X-Requested-With', 'XMLHttpRequest');
        this.on('end', function() {
          self.callback(new Response(self.xhr));
        });
      }
   
      /**
       * Inherit from `Emitter.prototype`.
       */

   
      Request.prototype = new Emitter;
      Request.prototype.constructor = Request;
   
      /**
       * Abort the request.
       *
       * @return {Request}
       * @api public
       */

   
      Request.prototype.abort = function() {
        if (this.aborted) return;
        this.xhr.abort();
        this.emit('abort');
        this.aborted = true;
        return this;
      };
   
      /**
       * Set header `field` to `val`, or multiple fields with one object.
       *
       * Examples:
       *
       *      req.get('/')
       *        .set('Accept', 'application/json')
       *        .set('X-API-Key', 'foobar')
       *        .end(callback);
       *
       *      req.get('/')
       *        .set({ Accept: 'application/json', 'X-API-Key': 'foobar' })
       *        .end(callback);
       *
       * @param {String|Object} field
       * @param {String} val
       * @return {Request} for chaining
       * @api public
       */

   
      Request.prototype.set = function(field, val) {
        if (isObject(field)) {
          for (var key in field) {
            this.set(key, field[key]);
          }
          return this;
        }
        this.header[field.toLowerCase()] = val;
        return this;
      };
   
      /**
       * Set Content-Type to `type`, mapping values from `request.types`.
       *
       * Examples:
       *
       *      superagent.types.xml = 'application/xml';
       *
       *      request.post('/')
       *        .type('xml')
       *        .send(xmlstring)
       *        .end(callback);
       *      
       *      request.post('/')
       *        .type('application/xml')
       *        .send(xmlstring)
       *        .end(callback);
       *
       * @param {String} type
       * @return {Request} for chaining
       * @api public
       */

   
      Request.prototype.type = function(type) {
        this.set('Content-Type', request.types[type] || type);
        return this;
      };
   
      /**
       * Add `obj` to the query-string, later formatted
       * in `.end()`.
       *
       * @param {Object} obj
       * @return {Request} for chaining
       * @api public
       */

   
      Request.prototype.query = function(obj) {
        this._query = this._query || {};
        for (var key in obj) {
          this._query[key] = obj[key];
        }
        return this;
      };
   
      /**
       * Send `data`, defaulting the `.type()` to "json" when
       * an object is given.
       *
       * Examples:
       *
       *       // querystring
       *       request.get('/search')
       *         .end(callback)
       *
       *       // multiple data "writes"
       *       request.get('/search')
       *         .send({ search: 'query' })
       *         .send({ range: '1..5' })
       *         .send({ order: 'desc' })
       *         .end(callback)
       *
       *       // manual json
       *       request.post('/user')
       *         .type('json')
       *         .send('{"name":"tj"})
       *         .end(callback)
       *      
       *       // auto json
       *       request.post('/user')
       *         .send({ name: 'tj' })
       *         .end(callback)
       *      
       *       // manual x-www-form-urlencoded
       *       request.post('/user')
       *         .type('form')
       *         .send('name=tj')
       *         .end(callback)
       *      
       *       // auto x-www-form-urlencoded
       *       request.post('/user')
       *         .type('form')
       *         .send({ name: 'tj' })
       *         .end(callback)
       *
       *       // defaults to x-www-form-urlencoded
       *      request.post('/user')
       *        .send('name=tobi')
       *        .send('species=ferret')
       *        .end(callback)
       *
       * @param {String|Object} data
       * @return {Request} for chaining
       * @api public
       */

   
      Request.prototype.send = function(data) {
        var obj = isObject(data);
        var type = this.header['content-type'];
   
        // merge
        if (obj && isObject(this._data)) {
          for (var key in data) {
            this._data[key] = data[key];
          }
        } else if ('string' == typeof data) {
          if (!type) this.type('form');
          type = this.header['content-type'];
          if ('application/x-www-form-urlencoded' == type) {
            this._data = this._data ? this._data + '&' + data : data;
          } else {
            this._data = (this._data || '') + data;
          }
        } else {
          this._data = data;
        }
   
        if (!obj) return this;
        if (!type) this.type('json');
        return this;
      };
   
      /**
       * Initiate request, invoking callback `fn(res)`
       * with an instanceof `Response`.
       *
       * @param {Function} fn
       * @return {Request} for chaining
       * @api public
       */

   
      Request.prototype.end = function(fn) {
        var self = this;
        var xhr = this.xhr = getXHR();
        var query = this._query;
        var data = this._data;
   
        // store callback
        this.callback = fn || noop;
   
        // state change
        xhr.onreadystatechange = function() {
          if (4 == xhr.readyState) self.emit('end');
        };
   
        // querystring
        if (query) {
          query = request.serializeObject(query);
          this.url += ~this.url.indexOf('?') ? '&' + query : '?' + query;
        }
   
        // initiate request
        xhr.open(this.method, this.url, true);
   
        // body
        if ('GET' != this.method && 'HEAD' != this.method && 'string' != typeof data) {
          // serialize stuff
          var serialize = request.serialize[this.header['content-type']];
          if (serialize) data = serialize(data);
        }
   
        // set header fields
        for (var field in this.header) {
          xhr.setRequestHeader(field, this.header[field]);
        }
   
        // send stuff
        xhr.send(data);
        return this;
      };
   
      /**
       * Expose `Request`.
       */

   
      request.Request = Request;
   
      /**
       * Issue a request:
       *
       * Examples:
       *
       *    request('GET', '/users').end(callback)
       *    request('/users').end(callback)
       *    request('/users', callback)
       *
       * @param {String} method
       * @param {String|Function} url or callback
       * @return {Request}
       * @api public
       */

   
      function request(method, url) {
        // callback
        if ('function' == typeof url) {
          return new Request('GET', method).end(url);
        }
   
        // url first
        if (1 == arguments.length) {
          return new Request('GET', method);
        }
   
        return new Request(method, url);
      }
   
      /**
       * GET `url` with optional callback `fn(res)`.
       *
       * @param {String} url
       * @param {Mixed|Function} data or fn
       * @param {Function} fn
       * @return {Request}
       * @api public
       */

   
      request.get = function(url, data, fn) {
        var req = request('GET', url);
        if ('function' == typeof data) fn = data, data = null;
        if (data) req.query(data);
        if (fn) req.end(fn);
        return req;
      };
   
      /**
       * GET `url` with optional callback `fn(res)`.
       *
       * @param {String} url
       * @param {Mixed|Function} data or fn
       * @param {Function} fn
       * @return {Request}
       * @api public
       */

   
      request.head = function(url, data, fn) {
        var req = request('HEAD', url);
        if ('function' == typeof data) fn = data, data = null;
        if (data) req.send(data);
        if (fn) req.end(fn);
        return req;
      };
   
      /**
       * DELETE `url` with optional callback `fn(res)`.
       *
       * @param {String} url
       * @param {Function} fn
       * @return {Request}
       * @api public
       */

   
      request.del = function(url, fn) {
        var req = request('DELETE', url);
        if (fn) req.end(fn);
        return req;
      };
   
      /**
       * PATCH `url` with optional `data` and callback `fn(res)`.
       *
       * @param {String} url
       * @param {Mixed} data
       * @param {Function} fn
       * @return {Request}
       * @api public
       */

   
      request.patch = function(url, data, fn) {
        var req = request('PATCH', url);
        if ('function' == typeof data) fn = data, data = null;
        if (data) req.send(data);
        if (fn) req.end(fn);
        return req;
      };
   
      /**
       * POST `url` with optional `data` and callback `fn(res)`.
       *
       * @param {String} url
       * @param {Mixed} data
       * @param {Function} fn
       * @return {Request}
       * @api public
       */

   
      request.post = function(url, data, fn) {
        var req = request('POST', url);
        if ('function' == typeof data) fn = data, data = null;
        if (data) req.send(data);
        if (fn) req.end(fn);
        return req;
      };
   
      /**
       * PUT `url` with optional `data` and callback `fn(res)`.
       *
       * @param {String} url
       * @param {Mixed|Function} data or fn
       * @param {Function} fn
       * @return {Request}
       * @api public
       */

   
      request.put = function(url, data, fn) {
        var req = request('PUT', url);
        if ('function' == typeof data) fn = data, data = null;
        if (data) req.send(data);
        if (fn) req.end(fn);
        return req;
      };
   
      /**
       * Expose `request`.
       */

   
      module.exports = request;
    });
    require.register("component-model/lib/index.js", function(module, exports, require) {
   
      /**
       * Module dependencies.
       */

   
      var proto = require('./proto'),
          statics = require('./static'),
          Emitter = require('emitter');
   
      /**
       * Expose `createModel`.
       */

   
      module.exports = createModel;
   
      /**
       * Create a new model constructor with the given `name`.
       *
       * @param {String} name
       * @return {Function}
       * @api public
       */

   
      function createModel(name) {
        if ('string' != typeof name) throw new TypeError('model name required');
   
        /**
         * Initialize a new model with the given `attrs`.
         *
         * @param {Object} attrs
         * @api public
         */

   
        function model(attrs) {
          if (!(this instanceof model)) return new model(attrs);
          this._callbacks = {};
          this.attrs = {};
          this.dirty = {};
          if (attrs) this.set(attrs);
        }
   
        // mixin emitte
        Emitter(model);
   
        // statics
        model.modelName = name;
        model.base = '/' + name.toLowerCase();
        model.attrs = {};
        model.validators = [];
        for (var key in statics) model[key] = statics[key];
   
        // prototype
        model.prototype = {};
        model.prototype.model = model;
        for (var key in proto) model.prototype[key] = proto[key];
   
        return model;
      }
   
   
    });
    require.register("component-model/lib/static.js", function(module, exports, require) {
   
      /**
       * Module dependencies.
       */

   
      var request = require('superagent'),
          Collection = require('collection'),
          noop = function() {};
   
      /**
       * Construct a url to the given `path`.
       *
       * Example:
       *
       *    User.url('add')
       *    // => "/users/add"
       *
       * @param {String} path
       * @return {String}
       * @api public
       */

   
      exports.url = function(path) {
        var url = this.base;
        if (0 == arguments.length) return url;
        return url + '/' + path;
      };
   
      /**
       * Add validation `fn()`.
       *
       * @param {Function} fn
       * @return {Function} self
       * @api public
       */

   
      exports.validate = function(fn) {
        this.validators.push(fn);
        return this;
      };
   
      /**
       * Use the given plugin `fn()`.
       *
       * @param {Function} fn
       * @return {Function} self
       * @api public
       */

   
      exports.use = function(fn) {
        fn(this);
        return this;
      };
   
      /**
       * Define attr with the given `name` and `options`.
       *
       * @param {String} name
       * @param {Object} options
       * @return {Function} self
       * @api public
       */

   
      exports.attr = function(name, options) {
        this.attrs[name] = options || {};
   
        // implied pk
        if ('_id' == name || 'id' == name) {
          this.attrs[name].primaryKey = true;
          this.primaryKey = name;
        }
   
        // getter / setter method
        this.prototype[name] = function(val) {
          if (0 == arguments.length) return this.attrs[name];
          var prev = this.attrs[name];
          this.dirty[name] = val;
          this.attrs[name] = val;
          this.emit('change', name, val, prev);
          this.emit('change ' + name, val, prev);
          return this;
        };
   
        return this;
      };
   
      /**
       * Remove all and invoke `fn(err)`.
       *
       * @param {Function} [fn]
       * @api public
       */

   
      exports.removeAll = function(fn) {
        fn = fn || noop;
        var self = this;
        var url = this.url('all');
        request.del(url, function(res) {
          if (res.error) return fn(error(res));
          fn();
        });
      };
   
      /**
       * Get all and invoke `fn(err, array)`.
       *
       * @param {Function} fn
       * @api public
       */

   
      exports.all = function(fn) {
        var self = this;
        var url = this.url('all');
        request.get(url, function(res) {
          if (res.error) return fn(error(res));
          var col = new Collection;
          for (var i = 0, len = res.body.length; i < len; ++i) {
            col.push(new self(res.body[i]));
          }
          fn(null, col);
        });
      };
   
      /**
       * Get `id` and invoke `fn(err, model)`.
       *
       * @param {Mixed} id
       * @param {Function} fn
       * @api public
       */

   
      exports.get = function(id, fn) {
        var self = this;
        var url = this.url(id);
        request.get(url, function(res) {
          if (res.error) return fn(error(res));
          var model = new self(res.body);
          fn(null, model);
        });
      };
   
      /**
       * Response error helper.
       *
       * @param {Response} er
       * @return {Error}
       * @api private
       */

   
      function error(res) {
        return new Error('got ' + res.status + ' response');
      }
    });
    require.register("component-model/lib/proto.js", function(module, exports, require) {
   
      /**
       * Module dependencies.
       */

   
      var Emitter = require('emitter'),
          request = require('superagent'),
          JSON = require('json'),
          each = require('each'),
          noop = function() {};
   
      /**
       * Mixin emitter.
       */

   
      Emitter(exports);
   
      /**
       * Register an error `msg` on `attr`.
       *
       * @param {String} attr
       * @param {String} msg
       * @return {Object} self
       * @api public
       */

   
      exports.error = function(attr, msg) {
        this.errors.push({
          attr: attr,
          message: msg
        });
        return this;
      };
   
      /**
       * Check if this model is new.
       *
       * @return {Boolean}
       * @api public
       */

   
      exports.isNew = function() {
        var key = this.model.primaryKey;
        return !this.has(key);
      };
   
      /**
       * Get / set the primary key.
       *
       * @param {Mixed} val
       * @return {Mixed}
       * @api public
       */

   
      exports.primary = function(val) {
        var key = this.model.primaryKey;
        if (0 == arguments.length) return this[key]();
        return this[key](val);
      };
   
      /**
       * Validate the model and return a boolean.
       *
       * Example:
       *
       *    user.isValid()
       *    // => false
       *
       *    user.errors
       *    // => [{ attr: ..., message: ... }]
       *
       * @return {Boolean}
       * @api public
       */

   
      exports.isValid = function() {
        this.validate();
        return 0 == this.errors.length;
      };
   
      /**
       * Return `false` or an object
       * containing the "dirty" attributes.
       *
       * Optionally check for a specific `attr`.
       *
       * @param {String} [attr]
       * @return {Object|Boolean}
       * @api public
       */

   
      exports.changed = function(attr) {
        var dirty = this.dirty;
        if (Object.keys(dirty).length) {
          if (attr) return !!dirty[attr];
          return dirty;
        }
        return false;
      };
   
      /**
       * Perform validations.
       *
       * @api private
       */

   
      exports.validate = function() {
        var self = this;
        var fns = this.model.validators;
        this.errors = [];
        each(fns, function(fn) {
          fn(self)
        });
      };
   
      /**
       * Destroy the model and mark it as `.removed`
       * and invoke `fn(err)`.
       *
       * Events:
       *
       *  - `removing` before deletion
       *  - `remove` on deletion
       *
       * @param {Function} [fn]
       * @api public
       */

   
      exports.remove = function(fn) {
        fn = fn || noop;
        if (this.isNew()) return fn(new Error('not saved'));
        var self = this;
        var url = this.url();
        this.model.emit('removing', this);
        this.emit('removing');
        request.del(url, function(res) {
          if (res.error) return fn(error(res));
          self.removed = true;
          self.model.emit('remove', self);
          self.emit('remove');
          fn();
        });
      };
   
      /**
       * Save and invoke `fn(err)`.
       *
       * Events:
       *
       *  - `save` on updates and saves
       *  - `saving` pre-update or save, after validation
       *
       * @param {Function} [fn]
       * @api public
       */

   
      exports.save = function(fn) {
        if (!this.isNew()) return this.update(fn);
        var self = this;
        var url = this.model.url();
        fn = fn || noop;
        if (!this.isValid()) return fn(new Error('validation failed'));
        this.model.emit('saving', this);
        this.emit('saving');
        request.post(url, self, function(res) {
          if (res.error) return fn(error(res));
          if (res.body) self.primary(res.body.id);
          self.dirty = {};
          self.model.emit('save', self);
          self.emit('save');
          fn();
        });
      };
   
      /**
       * Update and invoke `fn(err)`.
       *
       * @param {Function} [fn]
       * @api private
       */

   
      exports.update = function(fn) {
        var self = this;
        var url = this.url();
        fn = fn || noop;
        if (!this.isValid()) return fn(new Error('validation failed'));
        this.model.emit('saving', this);
        this.emit('saving');
        request.put(url, self, function(res) {
          if (res.error) return fn(error(res));
          self.dirty = {};
          self.model.emit('save', self);
          self.emit('save');
          fn();
        });
      };
   
      /**
       * Return a url for `path` relative to this model.
       *
       * Example:
       *
       *    var user = new User({ id: 5 });
       *    user.url('edit');
       *    // => "/users/5/edit"
       *
       * @param {String} path
       * @return {String}
       * @api public
       */

   
      exports.url = function(path) {
        var model = this.model;
        var url = model.base;
        var id = this.primary();
        if (0 == arguments.length) return url + '/' + id;
        return url + '/' + id + '/' + path;
      };
   
      /**
       * Set multiple `attrs`.
       *
       * @param {Object} attrs
       * @return {Object} self
       * @api public
       */

   
      exports.set = function(attrs) {
        for (var key in attrs) {
          this[key](attrs[key]);
        }
        return this;
      };
   
      /**
       * Get `attr` value.
       *
       * @param {String} attr
       * @return {Mixed}
       * @api public
       */

   
      exports.get = function(attr) {
        return this.attrs[attr];
      };
   
      /**
       * Check if `attr` is present (not `null` or `undefined`).
       *
       * @param {String} attr
       * @return {Boolean}
       * @api public
       */

   
      exports.has = function(attr) {
        return null != this.attrs[attr];
      };
   
      /**
       * Return the JSON representation of the model.
       *
       * @return {Object}
       * @api public
       */

   
      exports.toJSON = function() {
        return this.attrs;
      };
   
      /**
       * Response error helper.
       *
       * @param {Response} er
       * @return {Error}
       * @api private
       */

   
      function error(res) {
        return new Error('got ' + res.status + ' response');
      }
    });
    require.register("collection/index.js", function(module, exports, require) {
   
      /**
       * Module dependencies.
       */

   
      var Enumerable = require('enumerable'),
          Emitter = require('emitter');
   
      /**
       * Expose `Collection`.
       */

   
      module.exports = Collection;
   
      /**
       * Initialize a new collection with the given `models`.
       *
       * @param {Array} models
       * @api public
       */

   
      function Collection(models) {
        this.models = models || [];
      }
   
      /**
       * Mixin enumerable.
       */

   
      Enumerable(Collection.prototype);
   
      /**
       * Mixin emitter
       */

   
      Emitter(Collection.prototype);
   
      /**
       * Iterator implementation.
       */

   
      Collection.prototype.__iterate__ = function() {
        var self = this;
        return {
          length: function() {
            return self.length()
          },
          get: function(i) {
            return self.models[i]
          }
        }
      };
   
      /**
       * Return the collection length.
       *
       * @return {Number}
       * @api public
       */

   
      Collection.prototype.length = function() {
        return this.models.length;
      };
   
      /**
       * Removes the last element from an array and returns that element
       *
       * @return {Mixed} removed element
       */

   
      Collection.prototype.pop = function() {
        var ret = this.models.pop.apply(this, arguments);
        this.emit('pop', ret);
        this.emit('remove', ret);
        return ret;
      };
   
      /**
       * Push a value onto the end of the array,
       * returning the length of the array
       *
       * @param {Mixed, ...} elements
       * @return {Number}
       */

   
      Collection.prototype.push = function() {
        var ret = this.models.push.apply(this, arguments),
            args = [].slice.call(arguments);
        this.emit('push', ret);
        for (var i = 0, len = args.length; i < len; i++) this.emit('add', args[i]);
        return ret;
      };
   
      /**
       * Reverses an array in place.
       *
       * @return {Array}
       */

   
      Collection.prototype.reverse = function() {
        var ret = this.models.reverse.apply(this, arguments);
        this.emit('reverse', ret);
        return ret;
      };
   
      /**
       * Removes the first element from an array and returns that element.
       *
       * @return {Mixed}
       */

   
      Collection.prototype.shift = function() {
        var ret = this.models.shift.apply(this, arguments);
        this.emit('shift', ret);
        this.emit('remove', ret);
        return ret;
      };
   
      /**
       * Sorts the elements of an array.
       *
       * @return {Array}
       */

   
      Collection.prototype.sort = function() {
        var ret = this.models.sort.apply(this, arguments);
        this.emit('sort', ret);
        return ret;
      };
   
      /**
       * Adds and/or removes elements from an array.
       *
       * @param {Number} index
       * @param {Number} howMany
       * @param {Mixed, ...} elements
       * @return {Array} removed elements
       */

   
      Collection.prototype.splice = function() {
        var ret = this.models.splice.apply(this, arguments),
            added = [].slice.call(arguments, 2);
        this.emit('splice', ret);
        for (var i = 0, len = ret.length; i < len; i++) this.emit('remove', ret[i]);
        for (i = 0, len = added.length; i < len; i++) this.emit('add', added[i]);
        return ret;
      };
   
      /**
       * Adds one or more elements to the front of an array
       * and returns the new length of the array.
       *
       * @param {Mixed, ...} elements
       * @return {Number} length
       */

   
      Collection.prototype.unshift = function() {
        var ret = this.models.unshift.apply(this, arguments),
            args = [].slice.call(arguments);
        this.emit('unshift', ret);
        for (var i = 0, len = args.length; i < len; i++) this.emit('add', args[i]);
        return ret;
      };
   
    });
    require.alias("component-enumerable/index.js", "collection/deps/enumerable/index.js");
    require.alias("component-to-function/index.js", "component-enumerable/deps/to-function/index.js");
   
    require.alias("component-emitter/index.js", "collection/deps/emitter/index.js");
   
    require.alias("component-model/lib/index.js", "collection/deps/model/lib/index.js");
    require.alias("component-model/lib/static.js", "collection/deps/model/lib/static.js");
    require.alias("component-model/lib/proto.js", "collection/deps/model/lib/proto.js");
    require.alias("component-model/lib/index.js", "collection/deps/model/index.js");
    require.alias("component-each/index.js", "component-model/deps/each/index.js");
    require.alias("component-type/index.js", "component-each/deps/type/index.js");
   
    require.alias("component-json/index.js", "component-model/deps/json/index.js");
   
    require.alias("component-emitter/index.js", "component-model/deps/emitter/index.js");
   
    require.alias("component-collection/index.js", "component-model/deps/collection/index.js");
    require.alias("component-enumerable/index.js", "component-collection/deps/enumerable/index.js");
    require.alias("component-to-function/index.js", "component-enumerable/deps/to-function/index.js");
   
    require.alias("visionmedia-superagent/lib/client.js", "component-model/deps/superagent/lib/client.js");
    require.alias("visionmedia-superagent/lib/client.js", "component-model/deps/superagent/index.js");
    require.alias("component-emitter/index.js", "visionmedia-superagent/deps/emitter/index.js");
   
    var Collection = require('collection')
   
    var arrA = [],
        arrA2 = [];
    var arrB = array([])
    var arrB2 = array([])
    var arrC = new Collection;
    var arrC2 = new Collection
    var lastResult
};
</script>

Test runner

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

Java applet disabled.

Testing in unknown unknown
Test Ops/sec
Normal Array
arrA.push('1', '2');
arrA2.push('a', 'b');
lastResult='1'
lastResult='2'
lastResult='a'
lastResult='b'
 
pending…
Emitting Array
arrB.on('add', function(v) {
  lastResult=v
})

arrB.push('1', '2');


arrB2.push('a', 'b')
pending…
Collection
arrC.on('add', function(v) {
  lastResult=v
})

arrC.push('1', '2')

arrC2.push('a', 'b')
pending…

Compare results of other browsers

Revisions

You can edit these tests or add even more tests to this page by appending /edit to the URL. Here’s a list of current revisions for this page:

0 comments

Add a comment