diff --git a/.npmignore b/.npmignore index 08210e2d72..2529b8039b 100644 --- a/.npmignore +++ b/.npmignore @@ -18,6 +18,7 @@ vendor/docdown/ vendor/firebug-lite/ vendor/json3/ vendor/jquery/ +vendor/platform.js/ vendor/qunit/qunit/*.css vendor/qunit/qunit/*-1.8.0.js vendor/requirejs/ diff --git a/README.md b/README.md index 6181c7790f..11aefd27ed 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,29 @@ -# Lo-Dash v1.0.1 +# Lo-Dash v1.1.0 [![build status](https://secure.travis-ci.org/bestiejs/lodash.png)](http://travis-ci.org/bestiejs/lodash) -An alternative to Underscore.js, delivering consistency, [customization](https://github.com/bestiejs/lodash#custom-builds), [performance](http://lodash.com/benchmarks), and [extra features](https://github.com/bestiejs/lodash#features). +A low-level utility library delivering consistency, [customization](https://github.com/bestiejs/lodash#custom-builds), [performance](http://lodash.com/benchmarks), and [extra features](https://github.com/bestiejs/lodash#features). ## Download * Lo-Dash builds (for modern environments):
-[Development](https://raw.github.com/bestiejs/lodash/v1.0.1/dist/lodash.js) and -[Production](https://raw.github.com/bestiejs/lodash/v1.0.1/dist/lodash.min.js) +[Development](https://raw.github.com/bestiejs/lodash/v1.1.0/dist/lodash.js) and +[Production](https://raw.github.com/bestiejs/lodash/v1.1.0/dist/lodash.min.js) * Lo-Dash compatibility builds (for legacy and modern environments):
-[Development](https://raw.github.com/bestiejs/lodash/v1.0.1/dist/lodash.compat.js) and -[Production](https://raw.github.com/bestiejs/lodash/v1.0.1/dist/lodash.compat.min.js) +[Development](https://raw.github.com/bestiejs/lodash/v1.1.0/dist/lodash.compat.js) and +[Production](https://raw.github.com/bestiejs/lodash/v1.1.0/dist/lodash.compat.min.js) * Underscore compatibility builds:
-[Development](https://raw.github.com/bestiejs/lodash/v1.0.1/dist/lodash.underscore.js) and -[Production](https://raw.github.com/bestiejs/lodash/v1.0.1/dist/lodash.underscore.min.js) +[Development](https://raw.github.com/bestiejs/lodash/v1.1.0/dist/lodash.underscore.js) and +[Production](https://raw.github.com/bestiejs/lodash/v1.1.0/dist/lodash.underscore.min.js) -* CDN copies of ≤ v1.0.1’s builds are available on [cdnjs](http://cdnjs.com/) thanks to [CloudFlare](http://www.cloudflare.com/):
-[Lo-Dash dev](http://cdnjs.cloudflare.com/ajax/libs/lodash.js/1.0.1/lodash.js), -[Lo-Dash prod](http://cdnjs.cloudflare.com/ajax/libs/lodash.js/1.0.1/lodash.min.js),
-[Lo-Dash compat-dev](http://cdnjs.cloudflare.com/ajax/libs/lodash.js/1.0.1/lodash.compat.js), -[Lo-Dash compat-prod](http://cdnjs.cloudflare.com/ajax/libs/lodash.js/1.0.1/lodash.compat.min.js),
-[Underscore compat-dev](http://cdnjs.cloudflare.com/ajax/libs/lodash.js/1.0.1/lodash.underscore.js), and -[Underscore compat-prod](http://cdnjs.cloudflare.com/ajax/libs/lodash.js/1.0.1/lodash.underscore.min.js) +* CDN copies of ≤ v1.1.0’s builds are available on [cdnjs](http://cdnjs.com/) thanks to [CloudFlare](http://www.cloudflare.com/):
+[Lo-Dash dev](http://cdnjs.cloudflare.com/ajax/libs/lodash.js/1.1.0/lodash.js), +[Lo-Dash prod](http://cdnjs.cloudflare.com/ajax/libs/lodash.js/1.1.0/lodash.min.js),
+[Lo-Dash compat-dev](http://cdnjs.cloudflare.com/ajax/libs/lodash.js/1.1.0/lodash.compat.js), +[Lo-Dash compat-prod](http://cdnjs.cloudflare.com/ajax/libs/lodash.js/1.1.0/lodash.compat.min.js),
+[Underscore compat-dev](http://cdnjs.cloudflare.com/ajax/libs/lodash.js/1.1.0/lodash.underscore.js), and +[Underscore compat-prod](http://cdnjs.cloudflare.com/ajax/libs/lodash.js/1.1.0/lodash.underscore.min.js) * For optimal file size, [create a custom build](https://github.com/bestiejs/lodash#custom-builds) with only the features you need @@ -56,12 +56,17 @@ For more information check out these articles, screencasts, and other videos ove * [_.bindKey](http://lodash.com/docs#bindKey) for binding [*“lazy”* defined](http://michaux.ca/articles/lazy-function-definition-pattern) methods * [_.cloneDeep](http://lodash.com/docs#cloneDeep) for deep cloning arrays and objects * [_.contains](http://lodash.com/docs#contains) accepts a `fromIndex` argument + * [_.createCallback](http://lodash.com/docs#createCallback) to customize how callback arguments are handled and support callback shorthands in mixins + * [_.findIndex](http://lodash.com/docs#findIndex) and [_.findKey](http://lodash.com/docs#findKey) for finding indexes and keys of collections * [_.forEach](http://lodash.com/docs#forEach) is chainable and supports exiting iteration early * [_.forIn](http://lodash.com/docs#forIn) for iterating over an object’s own and inherited properties * [_.forOwn](http://lodash.com/docs#forOwn) for iterating over an object’s own properties * [_.isPlainObject](http://lodash.com/docs#isPlainObject) checks if values are created by the `Object` constructor * [_.merge](http://lodash.com/docs#merge) for a deep [_.extend](http://lodash.com/docs#extend) + * [_.parseInt](http://lodash.com/docs#parseInt) for consistent cross-environment behavior * [_.partial](http://lodash.com/docs#partial) and [_.partialRight](http://lodash.com/docs#partialRight) for partial application without `this` binding + * [_.runInContext](http://lodash.com/docs#runInContext) for easier mocking and extended environment support + * [_.support](http://lodash.com/docs#support) to flag environment features * [_.template](http://lodash.com/docs#template) supports [*“imports”* options](http://lodash.com/docs#templateSettings_imports), [ES6 template delimiters](http://people.mozilla.org/~jorendorff/es6-draft.html#sec-7.8.6), and [sourceURLs](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl) * [_.where](http://lodash.com/docs#where) supports deep object comparisons * [_.clone](http://lodash.com/docs#clone), [_.omit](http://lodash.com/docs#omit), [_.pick](http://lodash.com/docs#pick), @@ -73,7 +78,7 @@ For more information check out these articles, screencasts, and other videos ove ## Support -Lo-Dash has been tested in at least Chrome 5~24, Firefox 1~18, IE 6-10, Opera 9.25-12, Safari 3-6, Node.js 0.4.8-0.8.20, Narwhal 0.3.2, PhantomJS 1.8.1, RingoJS 0.9, and Rhino 1.7RC5. +Lo-Dash has been tested in at least Chrome 5~25, Firefox 2~19, IE 6-10, Opera 9.25-12, Safari 3-6, Node.js 0.4.8-0.10.1, Narwhal 0.3.2, PhantomJS 1.8.1, RingoJS 0.9, and Rhino 1.7RC5. ## Custom builds @@ -86,7 +91,7 @@ lodash backbone ``` * CSP builds, supporting default [Content Security Policy](https://dvcs.w3.org/hg/content-security-policy/raw-file/tip/csp-specification.dev.html) restrictions, may be created using the `csp` modifier argument. - The `csp` modifier is an alais of the `mobile` modifier. Lo-Dash may be used in Chrome extensions by using either the `csp`, `mobile`, or `underscore` build and using precompiled templates, or loading Lo-Dash in a [sandbox](http://developer.chrome.com/stable/extensions/sandboxingEval.html). + The `csp` modifier is an alias of the `mobile` modifier. Lo-Dash may be used in Chrome extensions by using either the `csp`, `mobile`, or `underscore` build and using precompiled templates, or loading Lo-Dash in a [sandbox](http://developer.chrome.com/stable/extensions/sandboxingEval.html). ```bash lodash csp ``` @@ -209,18 +214,18 @@ To avoid potential issues, update `npm` before installing Lo-Dash: npm install npm -g ``` -In [Node.js](http://nodejs.org/) and [RingoJS v0.8.0+](http://ringojs.org/): +In [Node.js](http://nodejs.org/) and [RingoJS ≥ v0.8.0](http://ringojs.org/): ```js var _ = require('lodash'); // or as a drop-in replacement for Underscore -var _ = require('lodash/lodash.underscore'); +var _ = require('lodash/dist/lodash.underscore'); ``` **Note:** If Lo-Dash is installed globally, run [`npm link lodash`](http://blog.nodejs.org/2011/03/23/npm-1-0-global-vs-local-installation/) in your project’s root directory before requiring it. -In [RingoJS v0.7.0-](http://ringojs.org/): +In [RingoJS ≤ v0.7.0](http://ringojs.org/): ```js var _ = require('lodash')._; @@ -247,21 +252,25 @@ require({ ## Release Notes -### v1.0.1 +### v1.1.0 - * Add support for specifying source map URLs in `-p`/`--source-map` build options - * Ensured the second argument passed to `_.assign` is not treated as a `callback` - * Ensured `-p`/`--source-map` build options correctly set the `sourceMappingURL` - * Made `-p`/`--source-map` build options set source map *“sources”* keys based on the builds performed - * Made `_.defer` use `setImmediate`, in Node.js, when available - * Made `_.where` search arrays for values regardless of their index position - * Removed dead code from `_.template` + * Added `rhino -require` support + * Added `_.createCallback`, `_findIndex`, `_.findKey`, `_.parseInt`, `_.runInContext`, and `_.support` + * Added support for `callback` and `thisArg` arguments to `_.flatten` + * Added CommonJS/Node support to precompiled templates + * Ensured the `exports` object is not a DOM element + * Ensured `_.isPlainObject` returns `false` for objects without a `[[Class]]` of “Object” + * Made `_.cloneDeep`’s `callback` support more closely follow its documentation + * Made the template precompiler create nonexistent directories of `--output` paths + * Made `_.object` an alias of `_.zipObject` + * Optimized method chaining, object iteration, `_.find`, and `_.pluck` (an average of 18% overall better performance) + * Updated `backbone` build Lo-Dash method dependencies The full changelog is available [here](https://github.com/bestiejs/lodash/wiki/Changelog). ## BestieJS -Lo-Dash is part of the BestieJS *“Best in Class”* module collection. This means we promote solid browser/environment support, ES5 precedents, unit testing, and plenty of documentation. +Lo-Dash is part of the BestieJS *“Best in Class”* module collection. This means we promote solid browser/environment support, ES5+ precedents, unit testing, and plenty of documentation. ## Author diff --git a/build.js b/build.js index 4e34c827da..b49647aadb 100755 --- a/build.js +++ b/build.js @@ -2,12 +2,18 @@ ;(function() { 'use strict'; - /** Load modules */ + /** Load Node.js modules */ var fs = require('fs'), path = require('path'), - vm = require('vm'), + vm = require('vm'); + + /** Load other modules */ + var _ = require(path.join(__dirname, 'lodash.js')), minify = require(path.join(__dirname, 'build', 'minify.js')), - _ = require(path.join(__dirname, 'lodash.js')); + mkdirpSync = require(path.join(__dirname, 'build', 'mkdirp-sync.js')); + + /** Add `path.sep` for older versions of Node.js */ + path.sep || (path.sep = process.platform == 'win32' ? '\\' : '/'); /** The current working directory */ var cwd = process.cwd(); @@ -18,6 +24,9 @@ /** Shortcut used to push arrays of values to an array */ var push = arrayRef.push; + /** Used to detect the Node.js executable in command-line arguments */ + var reNode = RegExp('(?:^|' + path.sep + ')node(?:\\.exe)?$'); + /** Shortcut used to convert array-like objects to arrays */ var slice = arrayRef.slice; @@ -39,6 +48,7 @@ 'include': 'contains', 'inject': 'reduce', 'methods': 'functions', + 'object': 'zipObject', 'select': 'filter', 'tail': 'rest', 'take': 'first', @@ -60,13 +70,14 @@ 'reduceRight': ['foldr'], 'rest': ['drop', 'tail'], 'some': ['any'], - 'uniq': ['unique'] + 'uniq': ['unique'], + 'zipObject': ['object'] }; /** Used to track function dependencies */ var dependencyMap = { 'after': [], - 'assign': ['isArray', 'forEach', 'forOwn'], + 'assign': ['isArray', 'keys'], 'at': ['isString'], 'bind': ['isFunction', 'isObject'], 'bindAll': ['bind', 'functions'], @@ -76,23 +87,26 @@ 'compact': [], 'compose': [], 'contains': ['indexOf', 'isString'], - 'countBy': ['forEach', 'identity', 'isEqual', 'keys'], + 'countBy': ['createCallback', 'forEach'], + 'createCallback': ['identity', 'isEqual', 'keys'], 'debounce': [], - 'defaults': ['isArray', 'forEach', 'forOwn'], + 'defaults': ['isArray', 'keys'], 'defer': ['bind'], 'delay': [], 'difference': ['indexOf'], 'escape': [], - 'every': ['identity', 'isArray', 'isEqual', 'keys'], - 'filter': ['identity', 'isArray', 'isEqual', 'keys'], - 'find': ['forEach', 'identity', 'isEqual', 'keys'], + 'every': ['createCallback', 'isArray'], + 'filter': ['createCallback', 'isArray'], + 'find': ['createCallback', 'forEach', 'isArray'], + 'findIndex': ['createCallback'], + 'findKey': ['createCallback'], 'first': [], - 'flatten': ['isArray'], - 'forEach': ['identity', 'isArguments', 'isArray', 'isString'], - 'forIn': ['identity', 'isArguments'], - 'forOwn': ['identity', 'isArguments'], + 'flatten': ['createCallback', 'isArray'], + 'forEach': ['createCallback', 'isArguments', 'isArray', 'isString', 'keys'], + 'forIn': ['createCallback', 'isArguments'], + 'forOwn': ['createCallback', 'isArguments', 'keys'], 'functions': ['forIn', 'isFunction'], - 'groupBy': ['forEach', 'identity', 'isEqual', 'keys'], + 'groupBy': ['createCallback', 'forEach'], 'has': [], 'identity': [], 'indexOf': ['sortedIndex'], @@ -120,51 +134,53 @@ 'keys': ['forOwn', 'isArguments', 'isObject'], 'last': [], 'lastIndexOf': [], - 'map': ['identity', 'isArray', 'isEqual', 'keys'], - 'max': ['isArray', 'isEqual', 'isString', 'keys'], + 'map': ['createCallback', 'isArray'], + 'max': ['createCallback', 'isArray', 'isString'], 'memoize': [], 'merge': ['forEach', 'forOwn', 'isArray', 'isObject', 'isPlainObject'], - 'min': ['isArray', 'isEqual', 'isString', 'keys'], - 'mixin': ['forEach', 'forOwn', 'functions'], + 'min': ['createCallback', 'isArray', 'isString'], + 'mixin': ['forEach', 'functions'], 'noConflict': [], - 'object': [], 'omit': ['forIn', 'indexOf'], 'once': [], 'pairs': ['keys'], + 'parseInt': ['isString'], 'partial': ['isFunction', 'isObject'], 'partialRight': ['isFunction', 'isObject'], 'pick': ['forIn', 'isObject'], 'pluck': ['map'], 'random': [], 'range': [], - 'reduce': ['identity', 'isArray', 'isEqual', 'keys'], - 'reduceRight': ['forEach', 'identity', 'isEqual', 'isString', 'keys'], - 'reject': ['filter', 'identity', 'isEqual', 'keys'], + 'reduce': ['createCallback', 'isArray'], + 'reduceRight': ['createCallback', 'forEach', 'isString', 'keys'], + 'reject': ['createCallback', 'filter'], 'rest': [], 'result': ['isFunction'], + 'runInContext': ['defaults', 'pick'], 'shuffle': ['forEach'], 'size': ['keys'], - 'some': ['identity', 'isArray', 'isEqual', 'keys'], - 'sortBy': ['forEach', 'identity', 'isEqual', 'keys'], - 'sortedIndex': ['identity', 'isEqual', 'keys'], - 'tap': ['mixin'], + 'some': ['createCallback', 'isArray'], + 'sortBy': ['createCallback', 'forEach'], + 'sortedIndex': ['createCallback', 'identity'], + 'tap': ['value'], 'template': ['defaults', 'escape', 'keys', 'values'], 'throttle': [], - 'times': [], + 'times': ['createCallback'], 'toArray': ['isString', 'values'], 'unescape': [], 'union': ['uniq'], - 'uniq': ['indexOf', 'isEqual', 'keys'], + 'uniq': ['createCallback', 'indexOf'], 'uniqueId': [], - 'value': ['mixin'], + 'value': ['forOwn', 'isArray'], 'values': ['keys'], 'where': ['filter'], 'without': ['indexOf'], 'wrap': [], 'zip': ['max', 'pluck'], + 'zipObject': [], // method used by the `backbone` and `underscore` builds - 'chain': ['mixin'], + 'chain': ['value'], 'findWhere': ['where'] }; @@ -174,15 +190,12 @@ 'arrays', 'bottom', 'firstArg', - 'hasDontEnumBug', - 'hasEnumPrototype', - 'isKeysFast', + 'init', 'loop', - 'nonEnumArgs', - 'noCharByIndex', - 'shadowed', + 'shadowedProps', 'top', - 'useHas' + 'useHas', + 'useKeys' ]; /** List of all Lo-Dash methods */ @@ -208,6 +221,7 @@ 'has', 'indexOf', 'initial', + 'invert', 'invoke', 'isArray', 'isEmpty', @@ -223,7 +237,9 @@ 'max', 'min', 'mixin', + 'omit', 'once', + 'pairs', 'pick', 'reduce', 'reduceRight', @@ -238,6 +254,7 @@ 'toArray', 'uniqueId', 'value', + 'values', 'without' ]; @@ -246,11 +263,16 @@ 'at', 'bindKey', 'cloneDeep', + 'createCallback', + 'findIndex', + 'findKey', 'forIn', 'forOwn', 'isPlainObject', 'merge', - 'partialRight' + 'parseInt', + 'partialRight', + 'runInContext' ])); /** List of ways to export the `lodash` function */ @@ -261,6 +283,9 @@ 'node' ]; + /** Add `path.sep` for older versions of Node.js */ + path.sep || (path.sep = process.platform == 'win32' ? '\\' : '/'); + /*--------------------------------------------------------------------------*/ /** @@ -273,66 +298,68 @@ function addChainMethods(source) { // add `_.chain` source = source.replace(matchFunction(source, 'tap'), function(match) { - return [ + var indent = getIndent(match); + return match && (indent + [ '', - ' /**', - ' * Creates a `lodash` object that wraps the given `value`.', - ' *', - ' * @static', - ' * @memberOf _', - ' * @category Chaining', - ' * @param {Mixed} value The value to wrap.', - ' * @returns {Object} Returns the wrapper object.', - ' * @example', - ' *', - ' * var stooges = [', - " * { 'name': 'moe', 'age': 40 },", - " * { 'name': 'larry', 'age': 50 },", - " * { 'name': 'curly', 'age': 60 }", - ' * ];', - ' *', - ' * var youngest = _.chain(stooges)', - ' * .sortBy(function(stooge) { return stooge.age; })', - " * .map(function(stooge) { return stooge.name + ' is ' + stooge.age; })", - ' * .first();', - " * // => 'moe is 40'", - ' */', - ' function chain(value) {', - ' value = new lodash(value);', - ' value.__chain__ = true;', - ' return value;', - ' }', + '/**', + ' * Creates a `lodash` object that wraps the given `value`.', + ' *', + ' * @static', + ' * @memberOf _', + ' * @category Chaining', + ' * @param {Mixed} value The value to wrap.', + ' * @returns {Object} Returns the wrapper object.', + ' * @example', + ' *', + ' * var stooges = [', + " * { 'name': 'moe', 'age': 40 },", + " * { 'name': 'larry', 'age': 50 },", + " * { 'name': 'curly', 'age': 60 }", + ' * ];', + ' *', + ' * var youngest = _.chain(stooges)', + ' * .sortBy(function(stooge) { return stooge.age; })', + " * .map(function(stooge) { return stooge.name + ' is ' + stooge.age; })", + ' * .first();', + " * // => 'moe is 40'", + ' */', + 'function chain(value) {', + ' value = new lodashWrapper(value);', + ' value.__chain__ = true;', + ' return value;', + '}', '', match - ].join('\n'); + ].join('\n' + indent)); }); // add `wrapperChain` source = source.replace(matchFunction(source, 'wrapperToString'), function(match) { - return [ + var indent = getIndent(match); + return match && (indent + [ '', - ' /**', - ' * Enables method chaining on the wrapper object.', - ' *', - ' * @name chain', - ' * @memberOf _', - ' * @category Chaining', - ' * @returns {Mixed} Returns the wrapper object.', - ' * @example', - ' *', - ' * var sum = _([1, 2, 3])', - ' * .chain()', - ' * .reduce(function(sum, num) { return sum + num; })', - ' * .value()', - ' * // => 6`', - ' */', - ' function wrapperChain() {', - ' this.__chain__ = true;', - ' return this;', - ' }', + '/**', + ' * Enables method chaining on the wrapper object.', + ' *', + ' * @name chain', + ' * @memberOf _', + ' * @category Chaining', + ' * @returns {Mixed} Returns the wrapper object.', + ' * @example', + ' *', + ' * var sum = _([1, 2, 3])', + ' * .chain()', + ' * .reduce(function(sum, num) { return sum + num; })', + ' * .value()', + ' * // => 6`', + ' */', + 'function wrapperChain() {', + ' this.__chain__ = true;', + ' return this;', + '}', '', match - ].join('\n'); + ].join('\n' + indent)); }); // add `lodash.chain` assignment @@ -350,66 +377,72 @@ source = source.replace(/(?:\s*\/\/.*)*\n( *)forOwn\(lodash, *function\(func, *methodName\)[\s\S]+?\n\1}.+/g, ''); // move `mixin(lodash)` to after the method assignments - source = source.replace(/(?:\s*\/\/.*)*\s*mixin\(lodash\).+/, ''); + source = source.replace(/(?:\s*\/\/.*)*\n( *)mixin\(lodash\).+/, ''); source = source.replace(getMethodAssignments(source), function(match) { + var indent = /^ *(?=lodash\.)/m.exec(match)[0]; return match + [ '', '', - ' // add functions to `lodash.prototype`', - ' mixin(lodash);' - ].join('\n'); + '// add functions to `lodash.prototype`', + 'mixin(lodash);' + ].join('\n' + indent); }); - // add `__chain__` checks to `_.mixin` - source = source.replace(matchFunction(source, 'mixin'), function(match) { - return match.replace(/^( *)return new lodash.+/m, function() { - var indent = arguments[1]; - return indent + [ - '', - 'var result = func.apply(lodash, args);', - 'if (this.__chain__) {', - ' result = new lodash(result);', - ' result.__chain__ = true;', - '}', - 'return result;' - ].join('\n' + indent); - }); - }); + // replace `_.mixin` + source = replaceFunction(source, 'mixin', [ + 'function mixin(object) {', + ' forEach(functions(object), function(methodName) {', + ' var func = lodash[methodName] = object[methodName];', + '', + ' lodash.prototype[methodName] = function() {', + ' var args = [this.__wrapped__];', + ' push.apply(args, arguments);', + '', + ' var result = func.apply(lodash, args);', + ' if (this.__chain__) {', + ' result = new lodashWrapper(result);', + ' result.__chain__ = true;', + ' }', + ' return result;', + ' };', + ' });', + '}' + ].join('\n')); // replace wrapper `Array` method assignments - source = source.replace(/^(?: *\/\/.*\n)*( *)each\(\['[\s\S]+?\n\1}$/m, function() { - return [ - ' // add `Array` mutator functions to the wrapper', - " each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(methodName) {", - ' var func = arrayRef[methodName];', - ' lodash.prototype[methodName] = function() {', - ' var value = this.__wrapped__;', - ' func.apply(value, arguments);', + source = source.replace(/^(?: *\/\/.*\n)*( *)each\(\['[\s\S]+?\n\1}$/m, function(match, indent) { + return indent + [ + '// add `Array` mutator functions to the wrapper', + "each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(methodName) {", + ' var func = arrayRef[methodName];', + ' lodash.prototype[methodName] = function() {', + ' var value = this.__wrapped__;', + ' func.apply(value, arguments);', '', - ' // avoid array-like object bugs with `Array#shift` and `Array#splice`', - ' // in Firefox < 10 and IE < 9', - ' if (hasObjectSpliceBug && value.length === 0) {', - ' delete value[0];', - ' }', - ' return this;', - ' };', - ' });', + ' // avoid array-like object bugs with `Array#shift` and `Array#splice`', + ' // in Firefox < 10 and IE < 9', + ' if (!support.spliceObjects && value.length === 0) {', + ' delete value[0];', + ' }', + ' return this;', + ' };', + '});', '', - ' // add `Array` accessor functions to the wrapper', - " each(['concat', 'join', 'slice'], function(methodName) {", - ' var func = arrayRef[methodName];', - ' lodash.prototype[methodName] = function() {', - ' var value = this.__wrapped__,', - ' result = func.apply(value, arguments);', + '// add `Array` accessor functions to the wrapper', + "each(['concat', 'join', 'slice'], function(methodName) {", + ' var func = arrayRef[methodName];', + ' lodash.prototype[methodName] = function() {', + ' var value = this.__wrapped__,', + ' result = func.apply(value, arguments);', '', - ' if (this.__chain__) {', - ' result = new lodash(result);', - ' result.__chain__ = true;', - ' }', - ' return result;', - ' };', - ' });' - ].join('\n'); + ' if (this.__chain__) {', + ' result = new lodashWrapper(result);', + ' result.__chain__ = true;', + ' }', + ' return result;', + ' };', + '});' + ].join('\n' + indent); }); return source; @@ -426,7 +459,7 @@ function addCommandsToHeader(source, commands) { return source.replace(/(\/\**\n)( \*)( *@license[\s*]+)( *Lo-Dash [\w.-]+)(.*)/, function() { // remove `node path/to/build.js` from `commands` - if (commands[0] == 'node') { + if (reNode.test(commands[0])) { commands.splice(0, 2); } // add quotes to commands with spaces or equals signs @@ -472,7 +505,7 @@ var source = [ ';(function(window) {', - " var freeExports = typeof exports == 'object' && exports;", + " var freeExports = typeof exports == 'object' && typeof require == 'function' && exports;", '', " var freeModule = typeof module == 'object' && module && module.exports == freeExports && module;", '', @@ -510,7 +543,8 @@ ' _ = lodash;', ' lodash.templates = lodash.extend(lodash.templates || {}, templates);', ' });', - " } else if (freeExports) {", + " } else if (freeExports && !freeExports.nodeType) {", + " _ = require('" + options.moduleId + "');", " if (freeModule) {", ' (freeModule.exports = templates).templates = templates;', ' } else {', @@ -547,6 +581,8 @@ return source // remove pseudo private properties .replace(/(?:(?:\s*\/\/.*)*\s*lodash\._[^=]+=.+\n)+/g, '\n') + // remove extraneous whitespace + .replace(/^ *\n/gm, '\n') // remove lines with just whitespace and semicolons .replace(/^ *;\n/gm, '') // consolidate multiple newlines @@ -582,7 +618,7 @@ ' lodash exports=... Comma separated names of ways to export the `lodash` function', ' (i.e. “amd”, “commonjs”, “global”, “node”, and “none”)', ' lodash iife=... Code to replace the immediately-invoked function expression that wraps Lo-Dash', - ' (e.g. `lodash iife="!function(window,undefined){%output%}(this)"`)', + ' (e.g. `lodash iife="!function(window){%output%}(this)"`)', '', ' lodash template=... File path pattern used to match template files to precompile', ' (e.g. `lodash template=./*.jst`)', @@ -680,17 +716,23 @@ * @private * @param {Array|String} methodName A single method name or array of * dependencies to query. + * @param- {Object} [stackA=[]] Internally used track queried methods. * @returns {Array} Returns an array of method dependencies. */ - function getDependencies(methodName) { + function getDependencies(methodName, stack) { var dependencies = Array.isArray(methodName) ? methodName : dependencyMap[methodName]; if (!dependencies) { return []; } + stack || (stack = []); + // recursively accumulate the dependencies of the `methodName` function, and // the dependencies of its dependencies, and so on return _.uniq(dependencies.reduce(function(result, otherName) { - result.push.apply(result, getDependencies(otherName).concat(otherName)); + if (!_.contains(stack, otherName)) { + stack.push(otherName); + result.push.apply(result, getDependencies(otherName, stack).concat(otherName)); + } return result; }, [])); } @@ -700,20 +742,35 @@ * * @private * @param {Function} func The function to process. + * @param {String} indent The function indent. * @returns {String} Returns the formatted source. */ - function getFunctionSource(func) { + function getFunctionSource(func, indent) { var source = func.source || (func + ''); - + if (indent == null) { + indent = ' '; + } // format leading whitespace return source.replace(/\n(?:.*)/g, function(match, index) { match = match.slice(1); return ( - match == '}' && source.indexOf('}', index + 2) < 0 ? '\n ' : '\n ' + '\n' + indent + + (match == '}' && !_.contains(source, '}', index + 2) ? '' : ' ') ) + match; }); } + /** + * Gets the indent of the given function. + * + * @private + * @param {Function} func The function to process. + * @returns {String} Returns the indent. + */ + function getIndent(func) { + return /^ *(?=\S)/m.exec(func.source || func)[0]; + } + /** * Gets the `_.isArguments` fallback from `source`. * @@ -722,7 +779,7 @@ * @returns {String} Returns the `isArguments` fallback. */ function getIsArgumentsFallback(source) { - return (source.match(/(?:\s*\/\/.*)*\n( *)if *\((?:noArgsClass|!isArguments)[\s\S]+?};\n\1}/) || [''])[0]; + return (source.match(/(?:\s*\/\/.*)*\n( *)if *\((?:!support\.argsClass|!isArguments)[\s\S]+?};\n\1}/) || [''])[0]; } /** @@ -793,7 +850,10 @@ */ function isRemoved(source) { return slice.call(arguments, 1).every(function(funcName) { - return !matchFunction(source, funcName); + return !( + matchFunction(source, funcName) || + RegExp('^ *lodash\\.prototype\\.' + funcName + ' *=.+', 'm').test(source) + ); }); } @@ -808,8 +868,8 @@ */ function matchFunction(source, funcName) { var result = source.match(RegExp( - // match multi-line comment block (could be on a single line) - '(?:\\n +/\\*[^*]*\\*+(?:[^/][^*]*\\*+)*/\\n)?' + + // match multi-line comment block + '(?:\\n +/\\*[^*]*\\*+(?:[^/][^*]*\\*+)*/)?\\n' + // begin non-capturing group '( *)(?:' + // match a function declaration @@ -839,7 +899,10 @@ * @returns {Array} Returns the new converted array. */ function optionToArray(value) { - return value.match(/\w+=(.*)$/)[1].split(/, */); + return _.compact(_.isArray(value) + ? value + : value.match(/\w+=(.*)$/)[1].split(/, */) + ); } /** @@ -854,11 +917,6 @@ function optionToMethodsArray(source, value) { var methodNames = optionToArray(value); - // convert categories to method names - methodNames.forEach(function(category) { - push.apply(methodNames, getMethodsByCategory(source, category)); - }); - // convert aliases to real method names methodNames = methodNames.map(getRealName); @@ -867,53 +925,39 @@ } /** - * Removes all `argsAreObjects` references from `source`. + * Removes the all references to `varName` from `createIterator` in `source`. * * @private * @param {String} source The source to process. + * @param {String} identifier The name of the variable or property to remove. * @returns {String} Returns the modified source. */ - function removeArgsAreObjects(source) { - source = removeVar(source, 'argsAreObjects'); + function removeFromCreateIterator(source, identifier) { + var snippet = matchFunction(source, 'createIterator'); + if (!snippet) { + return source; + } + // remove data object property assignment + var modified = snippet + .replace(RegExp("^(?: *\\/\\/.*\\n)* *'" + identifier + "':.+\\n+", 'm'), '') + .replace(/,(?=\s*})/, ''); - // remove `argsAreObjects` from `_.isArray` - source = source.replace(matchFunction(source, 'isArray'), function(match) { - return match.replace(/\(argsAreObjects && *([^)]+)\)/g, '$1'); + source = source.replace(snippet, function() { + return modified; }); - // remove `argsAreObjects` from `_.isEqual` - source = source.replace(matchFunction(source, 'isEqual'), function(match) { - return match.replace(/!argsAreObjects[^:]+:\s*/g, ''); - }); + // clip at the `factory` assignment + snippet = modified.match(/Function\([\s\S]+$/)[0]; - return source; - } + modified = snippet + .replace(RegExp('\\b' + identifier + '\\b,? *', 'g'), '') + .replace(/, *(?=',)/, '') + .replace(/,(?=\s*\))/, '') + + source = source.replace(snippet, function() { + return modified; + }); - /** - * Removes the all references to `varName` from `createIterator` in `source`. - * - * @private - * @param {String} source The source to process. - * @param {String} varName The name of the variable to remove. - * @returns {String} Returns the modified source. - */ - function removeFromCreateIterator(source, varName) { - var snippet = matchFunction(source, 'createIterator'); - if ( snippet) { - // remove data object property assignment - var modified = snippet.replace(RegExp("^ *'" + varName + "': *" + varName + '.+\\n', 'm'), ''); - source = source.replace(snippet, modified); - - // clip at the `factory` assignment - snippet = modified.match(/Function\([\s\S]+$/)[0]; - - modified = snippet - .replace(RegExp('\\b' + varName + '\\b,? *', 'g'), '') - .replace(/, *',/, "',") - .replace(/,\s*\)/, ')') - - source = source.replace(snippet, modified); - } return source; } @@ -927,278 +971,379 @@ * @returns {String} Returns the modified source. */ function removeFunction(source, funcName) { + var snippet; + // remove function - var snippet = matchFunction(source, funcName); - if (snippet) { + if (funcName == 'runInContext') { + source = removeRunInContext(source, funcName); + } else if (funcName != 'each' && (snippet = matchFunction(source, funcName))) { source = source.replace(snippet, ''); } // grab the method assignments snippet snippet = getMethodAssignments(source); + // remove method assignment from `lodash.prototype` + source = source.replace(RegExp('^ *lodash\\.prototype\\.' + funcName + ' *=.+\\n', 'm'), ''); + // remove assignment and aliases var modified = getAliases(funcName).concat(funcName).reduce(function(result, otherName) { - return result.replace(RegExp('(?:\\n *//.*\\s*)* *lodash\\.' + otherName + ' *= *.+\\n'), ''); + return result.replace(RegExp('^(?: *//.*\\s*)* *lodash\\.' + otherName + ' *=.+\\n', 'm'), ''); }, snippet); // replace with the modified snippet - source = source.replace(snippet, modified); + source = source.replace(snippet, function() { + return modified; + }); return removeFromCreateIterator(source, funcName); } /** - * Removes all `hasDontEnumBug` references from `source`. + * Removes the `_.isArguments` fallback from `source`. * * @private * @param {String} source The source to process. * @returns {String} Returns the modified source. */ - function removeHasDontEnumBug(source) { - source = removeFromCreateIterator(source, 'hasDontEnumBug'); - source = removeFromCreateIterator(source, 'shadowed'); + function removeIsArgumentsFallback(source) { + return source.replace(getIsArgumentsFallback(source), ''); + } - // remove `hasDontEnumBug` declaration and assignment - source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var hasDontEnumBug\b.*|.+?hasDontEnumBug *=.+/g, ''); + /** + * Removes the `_.isFunction` fallback from `source`. + * + * @private + * @param {String} source The source to process. + * @returns {String} Returns the modified source. + */ + function removeIsFunctionFallback(source) { + return source.replace(getIsFunctionFallback(source), ''); + } - // remove `shadowed` variable - source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var shadowed[\s\S]+?;\n/, ''); + /** + * Removes the `Object.keys` object iteration optimization from `source`. + * + * @private + * @param {String} source The source to process. + * @returns {String} Returns the modified source. + */ + function removeKeysOptimization(source) { + source = removeFromCreateIterator(source, 'useKeys'); - // remove `hasDontEnumBug` from `iteratorTemplate` + // remove optimized branch in `iteratorTemplate` source = source.replace(getIteratorTemplate(source), function(match) { - return match.replace(/(?: *\/\/.*\n)* *["']( *)<% *if *\(hasDontEnumBug[\s\S]+?["']\1<% *} *%>.+/, ''); + return match.replace(/^(?: *\/\/.*\n)* *["']( *)<% *if *\(useHas *&& *useKeys[\s\S]+?["']\1<% *} *else *{ *%>.+\n([\s\S]+?) *["']\1<% *} *%>.+/m, "'\\n' +\n$2"); }); return source; } /** - * Removes all `hasEnumPrototype` references from `source`. + * Removes all `lodashWrapper` references from `source`. * * @private * @param {String} source The source to process. * @returns {String} Returns the modified source. */ - function removeHasEnumPrototype(source) { - source = removeFromCreateIterator(source, 'hasEnumPrototype'); + function removeLodashWrapper(source) { + source = removeFunction(source, 'lodashWrapper'); - // remove `hasEnumPrototype` declaration and assignment - source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var hasEnumPrototype\b.*|.+?hasEnumPrototype *=.+/g, ''); + // remove `lodashWrapper.prototype` assignment + source = source.replace(/(?:\s*\/\/.*)*\n *lodashWrapper\.prototype *=.+/, ''); - // remove `hasEnumPrototype` from `_.keys` - source = source.replace(matchFunction(source, 'keys'), function(match) { - return match - .replace(/\(hasEnumPrototype[^)]+\)(?:\s*\|\|\s*)?/, '') - .replace(/\s*if *\(\s*\)[^}]+}/, ''); + // replace `new lodashWrapper` with `new lodash` + source = source.replace(/\bnew lodashWrapper\b/g, 'new lodash'); + + return source; + } + + /** + * Removes all `support.argsObject` references from `source`. + * + * @private + * @param {String} source The source to process. + * @returns {String} Returns the modified source. + */ + function removeSupportArgsObject(source) { + source = removeSupportProp(source, 'argsObject'); + + // remove `argsAreObjects` from `_.isArray` + source = source.replace(matchFunction(source, 'isArray'), function(match) { + return match.replace(/\(support\.argsObject && *([^)]+)\)/g, '$1'); }); - // remove `hasEnumPrototype` from `iteratorTemplate` - source = source.replace(getIteratorTemplate(source), function(match) { - return match - .replace(/(?: *\/\/.*\n)* *["'] *(?:<% *)?if *\(hasEnumPrototype *(?:&&|\))[\s\S]+?<% *} *(?:%>|["']).+/g, '') - .replace(/hasEnumPrototype *\|\|\s*/g, ''); + // remove `argsAreObjects` from `_.isEqual` + source = source.replace(matchFunction(source, 'isEqual'), function(match) { + return match.replace(/!support.\argsObject[^:]+:\s*/g, ''); }); return source; } /** - * Removes the `_.isArguments` fallback from `source`. + * Removes all `support.argsClass` references from `source`. * * @private * @param {String} source The source to process. * @returns {String} Returns the modified source. */ - function removeIsArgumentsFallback(source) { - return source.replace(getIsArgumentsFallback(source), ''); + function removeSupportArgsClass(source) { + source = removeSupportProp(source, 'argsClass'); + + // replace `support.argsClass` in the `_.isArguments` fallback + source = source.replace(getIsArgumentsFallback(source), function(match) { + return match.replace(/!support\.argsClass/g, '!isArguments(arguments)'); + }); + + // remove `support.argsClass` from `_.isEmpty` + source = source.replace(matchFunction(source, 'isEmpty'), function(match) { + return match.replace(/\s*\(support\.argsClass\s*\?([^:]+):.+?\)\)/g, '$1'); + }); + + // remove `support.argsClass` from `_.isPlainObject` + _.each(['shimIsPlainObject', 'isPlainObject'], function(methodName) { + source = source.replace(matchFunction(source, methodName), function(match) { + return match.replace(/\s*\|\|\s*\(!support\.argsClass[\s\S]+?\)\)/, ''); + }); + }); + + return source; } /** - * Removes the `_.isFunction` fallback from `source`. + * Removes all `support.enumPrototypes` references from `source`. * * @private * @param {String} source The source to process. * @returns {String} Returns the modified source. */ - function removeIsFunctionFallback(source) { - return source.replace(getIsFunctionFallback(source), ''); + function removeSupportEnumPrototypes(source) { + source = removeSupportProp(source, 'enumPrototypes'); + + // remove `support.enumPrototypes` from `_.keys` + source = source.replace(matchFunction(source, 'keys'), function(match) { + return match + .replace(/\(support\.enumPrototypes[^)]+\)(?:\s*\|\|\s*)?/, '') + .replace(/\s*if *\(\s*\)[^}]+}/, ''); + }); + + // remove `support.enumPrototypes` from `iteratorTemplate` + source = source.replace(getIteratorTemplate(source), function(match) { + return match + .replace(/(?: *\/\/.*\n)* *["'] *(?:<% *)?if *\(support\.enumPrototypes *(?:&&|\))[\s\S]+?<% *} *(?:%>|["']).+/g, '') + .replace(/support\.enumPrototypes\s*\|\|\s*/g, ''); + }); + + return source; } /** - * Removes all `iteratesOwnLast` references from `source`. + * Removes all `support.nodeClass` references from `source`. * * @private * @param {String} source The source to process. * @returns {String} Returns the modified source. */ - function removeIteratesOwnLast(source) { - // remove `iteratesOwnLast` declaration and assignment - source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var iteratesOwnLast\b.*|.+?iteratesOwnLast *=.+/g, ''); + function removeSupportNodeClass(source) { + source = removeFunction(source, 'isNode'); + source = removeSupportProp(source, 'nodeClass'); - // remove `iteratesOwnLast` from `shimIsPlainObject` + // remove `support.nodeClass` from `shimIsPlainObject` source = source.replace(matchFunction(source, 'shimIsPlainObject'), function(match) { - return match.replace(/(?:\s*\/\/.*)*\n( *)if *\(iteratesOwnLast[\s\S]+?\n\1}/, ''); + return match.replace(/ *&& *\(support\.nodeClass[\s\S]+?\)\)/, ''); + }); + + // remove `support.nodeClass` from `_.clone` + source = source.replace(matchFunction(source, 'clone'), function(match) { + return match.replace(/\s*\|\|\s*\(!support\.nodeClass[\s\S]+?\)\)/, ''); + }); + + // remove `support.nodeClass` from `_.isEqual` + source = source.replace(matchFunction(source, 'isEqual'), function(match) { + return match.replace(/\s*\|\|\s*\(!support\.nodeClass[\s\S]+?\)\)\)/, ''); }); return source; } /** - * Removes the `Object.keys` object iteration optimization from `source`. + * Removes all `support.nonEnumArgs` references from `source`. * * @private * @param {String} source The source to process. * @returns {String} Returns the modified source. */ - function removeKeysOptimization(source) { - source = removeVar(source, 'isKeysFast'); + function removeSupportNonEnumArgs(source) { + source = removeSupportProp(source, 'nonEnumArgs'); - // remove optimized branch in `iteratorTemplate` + // remove `support.nonEnumArgs` from `_.keys` + source = source.replace(matchFunction(source, 'keys'), function(match) { + return match + .replace(/(?:\s*\|\|\s*)?\(support\.nonEnumArgs[^)]+\)\)/, '') + .replace(/\s*if *\(\s*\)[^}]+}/, ''); + }); + + // remove `nonEnumArgs` from `iteratorTemplate` source = source.replace(getIteratorTemplate(source), function(match) { - return match.replace(/(?: *\/\/.*\n)* *["']( *)<% *if *\(isKeysFast[\s\S]+?["']\1<% *} *else *{ *%>.+\n([\s\S]+?) *["']\1<% *} *%>.+/, "'\\n' +\n$2"); + return match + .replace(/(?: *\/\/.*\n)*( *["'] *)<% *} *else *if *\(support\.nonEnumArgs[\s\S]+?(\1<% *} *%>.+)/, '$2') + .replace(/\s*\|\|\s*support\.nonEnumArgs/, ''); }); return source; } /** - * Removes all `noArgsClass` references from `source`. + * Removes all `support.nonEnumShadows` references from `source`. * * @private * @param {String} source The source to process. * @returns {String} Returns the modified source. */ - function removeNoArgsClass(source) { - source = removeVar(source, 'noArgsClass'); - - // replace `noArgsClass` in the `_.isArguments` fallback - source = source.replace(getIsArgumentsFallback(source), function(match) { - return match.replace(/noArgsClass/g, '!isArguments(arguments)'); - }); + function removeSupportNonEnumShadows(source) { + source = removeSupportProp(source, 'nonEnumShadows'); + source = removeVar(source, 'shadowedProps'); + source = removeFromCreateIterator(source, 'shadowedProps'); - // remove `noArgsClass` from `_.isEmpty` - source = source.replace(matchFunction(source, 'isEmpty'), function(match) { - return match.replace(/ *\|\|\s*\(noArgsClass *&&[^)]+?\)\)/g, ''); + // remove `support.nonEnumShadows` from `iteratorTemplate` + source = source.replace(getIteratorTemplate(source), function(match) { + return match.replace(/(?: *\/\/.*\n)* *["']( *)<% *if *\(support\.nonEnumShadows[\s\S]+?["']\1<% *} *%>.+/, ''); }); return source; } /** - * Removes all `noCharByIndex` references from `source`. + * Removes all `support.ownLast` references from `source`. * * @private * @param {String} source The source to process. * @returns {String} Returns the modified source. */ - function removeNoCharByIndex(source) { - source = removeVar(source, 'noCharByIndex'); + function removeSupportOwnLast(source) { + source = removeSupportProp(source, 'ownLast'); - // remove `noCharByIndex` from `_.at` - source = source.replace(matchFunction(source, 'at'), function(match) { - return match.replace(/^ *if *\(noCharByIndex[^}]+}\n/m, ''); + // remove `support.ownLast` from `shimIsPlainObject` + source = source.replace(matchFunction(source, 'shimIsPlainObject'), function(match) { + return match.replace(/(?:\s*\/\/.*)*\n( *)if *\(support\.ownLast[\s\S]+?\n\1}/, ''); }); - // remove `noCharByIndex` from `_.reduceRight` - source = source.replace(matchFunction(source, 'reduceRight'), function(match) { - return match.replace(/}\s*else if *\(noCharByIndex[^}]+/, ''); - }); + return source; + } - // remove `noCharByIndex` from `_.toArray` - source = source.replace(matchFunction(source, 'toArray'), function(match) { - return match.replace(/noCharByIndex[^:]+:/, ''); - }); + /** + * Removes all `support.spliceObjects` references from `source`. + * + * @private + * @param {String} source The source to process. + * @returns {String} Returns the modified source. + */ + function removeSupportSpliceObjects(source) { + source = removeSupportProp(source, 'spliceObjects'); - // `noCharByIndex` from `iteratorTemplate` - source = source.replace(getIteratorTemplate(source), function(match) { - return match - .replace(/'if *\(<%= *arrays *%>[^']*/, '$&\\n') - .replace(/(?: *\/\/.*\n)* *["']( *)<% *if *\(noCharByIndex[\s\S]+?["']\1<% *} *%>.+/, ''); - }); + // remove `support.spliceObjects` fix from the `Array` function mixins + source = source.replace(/(?:\s*\/\/.*)*\n( *)if *\(!support\.spliceObjects[\s\S]+?(?:{\s*}|\n\1})/, ''); return source; } /** - * Removes all `nonEnumArgs` references from `source`. + * Removes all `support.unindexedChars` references from `source`. * * @private * @param {String} source The source to process. * @returns {String} Returns the modified source. */ - function removeNonEnumArgs(source) { - source = removeFromCreateIterator(source, 'nonEnumArgs'); + function removeSupportUnindexedChars(source) { + source = removeSupportProp(source, 'unindexedChars'); - // remove `nonEnumArgs` declaration and assignment - source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var nonEnumArgs\b.*|.+?nonEnumArgs *=.+/g, ''); + // remove `support.unindexedChars` from `_.at` + source = source.replace(matchFunction(source, 'at'), function(match) { + return match.replace(/^ *if *\(support\.unindexedChars[^}]+}\n+/m, ''); + }); - // remove `nonEnumArgs` from `_.keys` - source = source.replace(matchFunction(source, 'keys'), function(match) { - return match - .replace(/(?:\s*\|\|\s*)?\(nonEnumArgs[^)]+\)\)/, '') - .replace(/\s*if *\(\s*\)[^}]+}/, ''); + // remove `support.unindexedChars` from `_.reduceRight` + source = source.replace(matchFunction(source, 'reduceRight'), function(match) { + return match.replace(/}\s*else if *\(support\.unindexedChars[^}]+/, ''); }); - // remove `nonEnumArgs` from `iteratorTemplate` + // remove `support.unindexedChars` from `_.toArray` + source = source.replace(matchFunction(source, 'toArray'), function(match) { + return match.replace(/(return\b).+?support\.unindexedChars[^:]+:\s*/, '$1 '); + }); + + // remove `support.unindexedChars` from `iteratorTemplate` source = source.replace(getIteratorTemplate(source), function(match) { return match - .replace(/(?: *\/\/.*\n)*( *["'] *)<% *} *else *if *\(nonEnumArgs[\s\S]+?(\1<% *} *%>.+)/, '$2') - .replace(/ *\|\|\s*nonEnumArgs/, ''); + .replace(/'if *\(<%= *arrays *%>[^']*/, '$&\\n') + .replace(/(?: *\/\/.*\n)* *["']( *)<% *if *\(support\.unindexedChars[\s\S]+?["']\1<% *} *%>.+/, ''); }); return source; } /** - * Removes all `noNodeClass` references from `source`. + * Removes all `runInContext` references from `source`. * * @private * @param {String} source The source to process. * @returns {String} Returns the modified source. */ - function removeNoNodeClass(source) { - // remove `noNodeClass` assignment - source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *try *{(?:\s*\/\/.*)*\n *var noNodeClass[\s\S]+?catch[^}]+}\n/, ''); + function removeRunInContext(source) { + source = removeVar(source, 'contextProps'); - // remove `noNodeClass` from `shimIsPlainObject` - source = source.replace(matchFunction(source, 'shimIsPlainObject'), function(match) { - return match.replace(/ *&& *\(!noNodeClass[\s\S]+?\)\)/, ''); + // remove function scaffolding, leaving most of its content + source = source.replace(matchFunction(source, 'runInContext'), function(match) { + return match + .replace(/^[\s\S]+?function runInContext[\s\S]+?context *= *context.+| *return lodash[\s\S]+$/g, '') + .replace(/^ {4}/gm, ' '); }); - // remove `noNodeClass` from `_.clone` - source = source.replace(matchFunction(source, 'clone'), function(match) { - return match.replace(/ *\|\|\s*\(noNodeClass[\s\S]+?\)\)/, ''); - }); - - // remove `noNodeClass` from `_.isEqual` - source = source.replace(matchFunction(source, 'isEqual'), function(match) { - return match.replace(/ *\|\|\s*\(noNodeClass[\s\S]+?\)\)\)/, ''); - }); + // cleanup adjusted source + source = source + .replace(/\bcontext\b/g, 'window') + .replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var Array *=[\s\S]+?;\n/, '') + .replace(/(return *|= *)_([;)])/g, '$1lodash$2') + .replace(/^ *var _ *=.+\n+/m, ''); return source; } /** - * Removes all `hasObjectSpliceByg` references from `source`. + * Removes all `setImmediate` references from `source`. * * @private * @param {String} source The source to process. * @returns {String} Returns the modified source. */ - function removeHasObjectSpliceBug(source) { - return removeVar(source, 'hasObjectSpliceBug') - // remove `hasObjectSpliceBug` fix from the `Array` function mixins - .replace(/(?:\s*\/\/.*)*\n( *)if *\(hasObjectSpliceBug[\s\S]+?(?:{\s*}|\n\1})/, ''); + function removeSetImmediate(source) { + source = removeVar(source, 'setImmediate'); + + // remove the `setImmediate` fork of `_.defer`. + source = source.replace(/(?:\s*\/\/.*)*\n( *)if *\(isV8 *&& *freeModule[\s\S]+?\n\1}/, ''); + + return source; } /** - * Removes the `setImmediate` fork of `_.defer`. + * Removes a given property from the `support` object in `source`. * * @private * @param {String} source The source to process. + * @param {String} varName The name of the `support` property to remove. * @returns {String} Returns the modified source. */ - function removeSetImmediate(source) { - return source.replace(/(?:\s*\/\/.*)*\n( *)if *\(isV8 *&& *freeModule[\s\S]+?\n\1}/, ''); + function removeSupportProp(source, propName) { + return source.replace(RegExp( + // match multi-line comment block + '(?:\\n +/\\*[^*]*\\*+(?:[^/][^*]*\\*+)*/)?\\n' + + // match a `try` block + '(?: *try\\b.+\\n)?' + + // match the `support` property assignment + ' *support\\.' + propName + ' *=.+\\n' + + // match `catch` block + '(?:( *).+?catch\\b[\\s\\S]+?\\n\\1}\\n)?' + ), ''); } /** @@ -1210,17 +1355,21 @@ * @returns {String} Returns the modified source. */ function removeVar(source, varName) { - // simplify `cloneableClasses`, `ctorByClass`, or `hasObjectSpliceBug` - if (/^(?:cloneableClasses|ctorByClass|hasObjectSpliceBug)$/.test(varName)) { + // simplify complex variable assignments + if (/^(?:cloneableClasses|contextProps|ctorByClass|shadowedProps)$/.test(varName)) { source = source.replace(RegExp('(var ' + varName + ' *=)[\\s\\S]+?\\n\\n'), '$1=null;\n\n'); } + + source = removeFunction(source, varName); + source = source.replace(RegExp( // match multi-line comment block '(?:\\n +/\\*[^*]*\\*+(?:[^/][^*]*\\*+)*/)?\\n' + // match a variable declaration that's not part of a declaration list '( *)var ' + varName + ' *= *(?:.+?(?:;|&&\\n[^;]+;)|(?:\\w+\\(|{)[\\s\\S]+?\\n\\1.+?;)\\n|' + // match a variable in a declaration list - '\\n +' + varName + ' *=.+?,' + '^ *' + varName + ' *=.+?,\\n', + 'm' ), ''); // remove a varaible at the start of a variable declaration list @@ -1229,29 +1378,56 @@ // remove a variable at the end of a variable declaration list source = source.replace(RegExp(',\\s*' + varName + ' *=.+?;'), ';'); - return removeFromCreateIterator(source, varName); + return source; } /** * Replaces the `funcName` function body in `source` with `funcValue`. * * @private - * @param {String} source The source to inspect. + * @param {String} source The source to process. * @param {String} varName The name of the function to replace. * @returns {String} Returns the modified source. */ function replaceFunction(source, funcName, funcValue) { - var match = matchFunction(source, funcName); - if (match) { - // clip snippet after the JSDoc comment block - match = match.replace(/^\s*(?:\/\/.*|\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)\n/, ''); - source = source.replace(match, function() { - return funcValue.trimRight() + '\n'; - }); + var snippet = matchFunction(source, funcName); + if (!snippet) { + return source; } + // clip snippet after the JSDoc comment block + snippet = snippet.replace(/^\s*(?:\/\/.*|\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)\n/, ''); + + source = source.replace(snippet, function() { + return funcValue + .replace(RegExp('^' + getIndent(funcValue), 'gm'), getIndent(snippet)) + .trimRight() + '\n'; + }); + return source; } + + /** + * Replaces the `support` object `propName` property value in `source` with `propValue`. + * + * @private + * @param {String} source The source to process. + * @param {String} varName The name of the `support` property to replace. + * @returns {String} Returns the modified source. + */ + function replaceSupportProp(source, propName, propValue) { + return source.replace(RegExp( + // match a `try` block + '(?: *try\\b.+\\n)?' + + // match the `support` property assignment + '( *support\\.' + propName + ' *=).+\\n' + + // match `catch` block + '(?:( *).+?catch\\b[\\s\\S]+?\\n\\2}\\n)?' + ), function(match, left) { + return left + ' ' + propValue + ';\n'; + }); + } + /** * Replaces the `varName` variable declaration value in `source` with `varValue`. * @@ -1263,22 +1439,22 @@ function replaceVar(source, varName, varValue) { // replace a variable that's not part of a declaration list var result = source.replace(RegExp( - '(( *)var ' + varName + ' *= *)' + + '(( *)var ' + varName + ' *=)' + '(?:.+?;|(?:Function\\(.+?|.*?[^,])\\n[\\s\\S]+?\\n\\2.+?;)\\n' - ), function(match, captured) { - return captured + varValue + ';\n'; + ), function(match, left) { + return left + ' ' + varValue + ';\n'; }); if (source == result) { // replace a varaible at the start or middle of a declaration list - result = source.replace(RegExp('((?:var|\\n) +' + varName + ' *=).+?,'), function(match, captured) { - return captured + ' ' + varValue + ','; + result = source.replace(RegExp('((?:var|\\n) +' + varName + ' *=).+?,'), function(match, left) { + return left + ' ' + varValue + ','; }); } if (source == result) { // replace a variable at the end of a variable declaration list - result = source.replace(RegExp('(,\\s*' + varName + ' *=).+?;'), function(match, captured) { - return captured + ' ' + varValue + ';'; + result = source.replace(RegExp('(,\\s*' + varName + ' *=).+?;'), function(match, left) { + return left + ' ' + varValue + ';'; }); } return result; @@ -1325,12 +1501,12 @@ var sourceMapURL; // used to report invalid command-line arguments - var invalidArgs = _.reject(options.slice(options[0] == 'node' ? 2 : 0), function(value, index, options) { + var invalidArgs = _.reject(options.slice(reNode.test(options[0]) ? 2 : 0), function(value, index, options) { if (/^(?:-o|--output)$/.test(options[index - 1]) || /^(?:category|exclude|exports|iife|include|moduleId|minus|plus|settings|template)=.*$/.test(value)) { return true; } - var result = [ + var result = _.contains([ 'backbone', 'csp', 'legacy', @@ -1347,7 +1523,7 @@ '-p', '--source-map', '-s', '--silent', '-V', '--version' - ].indexOf(value) > -1; + ], value); if (!result && /^(?:-p|--source-map)$/.test(options[index - 1])) { result = true; @@ -1398,52 +1574,47 @@ var filePath = path.join(__dirname, 'lodash.js'); // flag to specify a Backbone build - var isBackbone = options.indexOf('backbone') > -1; + var isBackbone = _.contains(options, 'backbone'); // flag to specify a Content Security Policy build - var isCSP = options.indexOf('csp') > -1 || options.indexOf('CSP') > -1; + var isCSP = _.contains(options, 'csp') || _.contains(options, 'CSP'); // flag to specify only creating the debug build - var isDebug = options.indexOf('-d') > -1 || options.indexOf('--debug') > -1; + var isDebug = _.contains(options, '-d') || _.contains(options, '--debug'); // flag to indicate that a custom IIFE was specified var isIIFE = typeof iife == 'string'; // flag to specify creating a source map for the minified source - var isMapped = options.indexOf('-p') > -1 || options.indexOf('--source-map') > -1; + var isMapped = _.contains(options, '-p') || _.contains(options, '--source-map'); // flag to specify only creating the minified build - var isMinify = options.indexOf('-m') > -1 || options.indexOf('--minify') > -1; + var isMinify = _.contains(options, '-m') || _.contains(options, '--minify'); // flag to specify a mobile build - var isMobile = isCSP || options.indexOf('mobile') > -1; + var isMobile = isCSP || _.contains(options, 'mobile'); // flag to specify a modern build - var isModern = isMobile || options.indexOf('modern') > -1; + var isModern = isMobile || _.contains(options, 'modern'); // flag to specify a modularize build - var isModularize = options.indexOf('modularize') > -1; + var isModularize = _.contains(options, 'modularize'); // flag to specify writing output to standard output - var isStdOut = options.indexOf('-c') > -1 || options.indexOf('--stdout') > -1; + var isStdOut = _.contains(options, '-c') || _.contains(options, '--stdout'); // flag to specify skipping status updates normally logged to the console - var isSilent = isStdOut || options.indexOf('-s') > -1 || options.indexOf('--silent') > -1; + var isSilent = isStdOut || _.contains(options, '-s') || _.contains(options, '--silent'); // flag to specify `_.assign`, `_.bindAll`, and `_.defaults` are // constructed using the "use strict" directive - var isStrict = options.indexOf('strict') > -1; + var isStrict = _.contains(options, 'strict'); // flag to specify an Underscore build - var isUnderscore = isBackbone || options.indexOf('underscore') > -1; + var isUnderscore = isBackbone || _.contains(options, 'underscore'); // flag to specify a legacy build - var isLegacy = !(isModern || isUnderscore) && options.indexOf('legacy') > -1; - - // used to specify methods of specific categories - var categories = options.reduce(function(result, value) { - return /category/.test(value) ? optionToArray(value) : result; - }, []); + var isLegacy = !(isModern || isUnderscore) && _.contains(options, 'legacy'); // used to specify the ways to export the `lodash` function var exportsOptions = options.reduce(function(result, value) { @@ -1463,7 +1634,9 @@ var outputPath = options.reduce(function(result, value, index) { if (/-o|--output/.test(value)) { result = options[index + 1]; - result = path.join(fs.realpathSync(path.dirname(result)), path.basename(result)); + var dirname = path.dirname(result); + mkdirpSync(dirname); + result = path.join(fs.realpathSync(dirname), path.basename(result)); } return result; }, ''); @@ -1476,7 +1649,7 @@ : result; }, ''); - // used when precompiling template files + // used as the template settings for precompiled templates var templateSettings = options.reduce(function(result, value) { var match = value.match(/settings=(.+)$/); return match @@ -1497,15 +1670,17 @@ // flags to specify exposing Lo-Dash methods in an Underscore build var exposeAssign = !isUnderscore, + exposeCreateCallback = !isUnderscore, exposeForIn = !isUnderscore, exposeForOwn = !isUnderscore, - exposeIsPlainObject = !isUnderscore; + exposeIsPlainObject = !isUnderscore, + exposeZipObject = !isUnderscore; // flags to specify export options - var isAMD = exportsOptions.indexOf('amd') > -1, - isCommonJS = exportsOptions.indexOf('commonjs') > -1, - isGlobal = exportsOptions.indexOf('global') > -1, - isNode = exportsOptions.indexOf('node') > -1; + var isAMD = _.contains(exportsOptions, 'amd'), + isCommonJS = _.contains(exportsOptions, 'commonjs'), + isGlobal = _.contains(exportsOptions, 'global'), + isNode = _.contains(exportsOptions, 'node'); /*------------------------------------------------------------------------*/ @@ -1531,41 +1706,54 @@ : accumulator; }, []); + var categories = options.reduce(function(accumulator, value) { + if (/category|exclude|include|minus|plus/.test(value)) { + var array = optionToArray(value); + accumulator = _.union(accumulator, /category/.test(value) + ? array + : array.filter(function(category) { return /^[A-Z]/.test(category); }) + ); + } + return accumulator; + }, []); + // set flags to include Lo-Dash's methods if explicitly requested if (isUnderscore) { var methods = _.without.apply(_, [_.union(includeMethods, plusMethods)].concat(minusMethods)); - exposeAssign = methods.indexOf('assign') > -1; - exposeForIn = methods.indexOf('forIn') > -1; - exposeForOwn = methods.indexOf('forOwn') > -1; - exposeIsPlainObject = methods.indexOf('isPlainObject') > -1; + exposeAssign = _.contains(methods, 'assign'); + exposeCreateCallback = _.contains(methods, 'createCallback'); + exposeForIn = _.contains(methods, 'forIn'); + exposeForOwn = _.contains(methods, 'forOwn'); + exposeIsPlainObject = _.contains(methods, 'isPlainObject'); + exposeZipObject = _.contains(methods, 'zipObject'); methods = _.without.apply(_, [plusMethods].concat(minusMethods)); - useUnderscoreClone = methods.indexOf('clone') < 0; + useUnderscoreClone = !_.contains(methods, 'clone') && !_.contains(methods, 'cloneDeep'); } // update dependencies if (isLegacy) { dependencyMap.defer = _.without(dependencyMap.defer, 'bind'); } + if (isModern) { + dependencyMap.isEmpty = _.without(dependencyMap.isEmpty, 'isArguments'); + dependencyMap.isEqual = _.without(dependencyMap.isEqual, 'isArguments'); + dependencyMap.isPlainObject = _.without(dependencyMap.isPlainObject, 'isArguments'); + dependencyMap.keys = _.without(dependencyMap.keys, 'isArguments'); + dependencyMap.reduceRight = _.without(dependencyMap.reduceRight, 'isString'); + } if (isUnderscore) { dependencyMap.contains = _.without(dependencyMap.contains, 'isString'); - dependencyMap.countBy = _.without(dependencyMap.countBy, 'isEqual', 'keys'); - dependencyMap.every = _.without(dependencyMap.every, 'isEqual', 'keys'); - dependencyMap.filter = _.without(dependencyMap.filter, 'isEqual'); - dependencyMap.find = _.without(dependencyMap.find, 'isEqual', 'keys'); - dependencyMap.groupBy = _.without(dependencyMap.groupBy, 'isEqual', 'keys'); - dependencyMap.isEqual = _.without(dependencyMap.isEqual, 'forIn', 'isArguments'); + dependencyMap.createCallback = _.without(dependencyMap.createCallback, 'isEqual'); + dependencyMap.flatten = _.without(dependencyMap.flatten, 'createCallback'); dependencyMap.isEmpty = ['isArray', 'isString']; - dependencyMap.map = _.without(dependencyMap.map, 'isEqual', 'keys'); - dependencyMap.max = _.without(dependencyMap.max, 'isEqual', 'isString', 'keys'); - dependencyMap.min = _.without(dependencyMap.min, 'isEqual', 'isString', 'keys'); + dependencyMap.isEqual = _.without(dependencyMap.isEqual, 'forIn', 'isArguments'); + dependencyMap.max = _.without(dependencyMap.max, 'isArray', 'isString'); + dependencyMap.min = _.without(dependencyMap.min, 'isArray', 'isString'); dependencyMap.pick = _.without(dependencyMap.pick, 'forIn', 'isObject'); - dependencyMap.reduce = _.without(dependencyMap.reduce, 'isEqual', 'keys'); - dependencyMap.reject = _.without(dependencyMap.reject, 'isEqual', 'keys'); - dependencyMap.some = _.without(dependencyMap.some, 'isEqual', 'keys'); - dependencyMap.sortBy = _.without(dependencyMap.sortBy, 'isEqual', 'keys'); - dependencyMap.sortedIndex = _.without(dependencyMap.sortedIndex, 'isEqual', 'keys'); + dependencyMap.reduceRight = _.without(dependencyMap.reduceRight, 'isString'); dependencyMap.template = _.without(dependencyMap.template, 'keys', 'values'); - dependencyMap.uniq = _.without(dependencyMap.uniq, 'isEqual', 'keys'); + dependencyMap.toArray.push('isArray', 'map'); + dependencyMap.value = _.without(dependencyMap.value, 'isArray'); dependencyMap.where.push('find', 'isEmpty'); if (useUnderscoreClone) { @@ -1573,7 +1761,22 @@ } } if (isModern || isUnderscore) { - dependencyMap.reduceRight = _.without(dependencyMap.reduceRight, 'isEqual', 'isString'); + dependencyMap.at = _.without(dependencyMap.at, 'isString'); + dependencyMap.forEach = _.without(dependencyMap.forEach, 'isArguments', 'isString'); + dependencyMap.forIn = _.without(dependencyMap.forIn, 'isArguments'); + dependencyMap.forOwn = _.without(dependencyMap.forOwn, 'isArguments'); + dependencyMap.toArray = _.without(dependencyMap.toArray, 'isString'); + + if (!isMobile) { + dependencyMap.every = _.without(dependencyMap.every, 'isArray'); + dependencyMap.find = _.without(dependencyMap.find, 'isArray'); + dependencyMap.filter = _.without(dependencyMap.filter, 'isArray'); + dependencyMap.forEach = _.without(dependencyMap.forEach, 'isArray'); + dependencyMap.map = _.without(dependencyMap.map, 'isArray'); + dependencyMap.max.push('forEach'); + dependencyMap.min.push('forEach'); + dependencyMap.reduce = _.without(dependencyMap.reduce, 'isArray'); + } } // add method names explicitly @@ -1590,8 +1793,21 @@ // add method names by category if (categories.length) { result = _.union(result || [], getDependencies(categories.reduce(function(accumulator, category) { - // resolve method names belonging to each category (case-insensitive) - return accumulator.concat(getMethodsByCategory(source, capitalize(category))); + // get method names belonging to each category (case-insensitive) + var methodNames = getMethodsByCategory(source, capitalize(category)); + + // limit category methods to those available for specific builds + if (isBackbone) { + methodNames = methodNames.filter(function(methodName) { + return _.contains(backboneDependencies, methodName); + }); + } + else if (isUnderscore) { + methodNames = methodNames.filter(function(methodName) { + return _.contains(underscoreMethods, methodName); + }); + } + return accumulator.concat(methodNames); }, []))); } if (!result) { @@ -1619,437 +1835,587 @@ source = setUseStrictOption(source, isStrict); if (isLegacy) { - _.each(['getPrototypeOf', 'isBindFast', 'isKeysFast', 'nativeBind', 'nativeIsArray', 'nativeKeys'], function(varName) { + _.each(['getPrototypeOf', 'nativeBind', 'nativeIsArray', 'nativeKeys'], function(varName) { source = replaceVar(source, varName, 'false'); }); - source = replaceVar(source, 'noArgsClass', 'true'); + _.each(['argsClass', 'fastBind'], function(propName) { + source = replaceSupportProp(source, propName, 'false'); + }); + source = removeKeysOptimization(source); } if (isMobile || isUnderscore) { source = removeKeysOptimization(source); + source = removeSetImmediate(source); } if (isModern || isUnderscore) { - source = removeHasDontEnumBug(source); - source = removeHasEnumPrototype(source); - source = removeIteratesOwnLast(source); - source = removeNoCharByIndex(source); - source = removeNoNodeClass(source); + source = removeSupportNonEnumShadows(source); + source = removeSupportEnumPrototypes(source); + source = removeSupportOwnLast(source); + source = removeSupportUnindexedChars(source); + source = removeSupportNodeClass(source); if (!isMobile) { - source = removeNonEnumArgs(source); + source = removeSupportNonEnumArgs(source); + + // replace `_.forEach` + source = replaceFunction(source, 'forEach', [ + 'function forEach(collection, callback, thisArg) {', + ' var index = -1,', + ' length = collection ? collection.length : 0;', + '', + " if (typeof length == 'number') {", + " callback = callback && typeof thisArg == 'undefined' ? callback : lodash.createCallback(callback, thisArg);", + ' while (++index < length) {', + ' if (callback(collection[index], index, collection) === false) {', + ' break;', + ' }', + ' }', + ' } else {', + ' each(collection, callback, thisArg);', + ' }', + ' return collection;', + '}', + ].join('\n')); + + // replace `_.map` + source = replaceFunction(source, 'map', [ + 'function map(collection, callback, thisArg) {', + ' var index = -1,', + ' length = collection ? collection.length : 0;', + '', + ' callback = lodash.createCallback(callback, thisArg);', + " if (typeof length == 'number') {", + ' var result = Array(length);', + ' while (++index < length) {', + ' result[index] = callback(collection[index], index, collection);', + ' }', + ' } else {', + ' result = [];', + ' each(collection, function(value, key, collection) {', + ' result[++index] = callback(value, key, collection);', + ' });', + ' }', + ' return result;', + '}' + ].join('\n')); + + // replace `_.pluck` + source = replaceFunction(source, 'pluck', [ + 'function pluck(collection, property) {', + ' var index = -1,', + ' length = collection ? collection.length : 0;', + '', + " if (typeof length == 'number') {", + ' var result = Array(length);', + ' while (++index < length) {', + ' result[index] = collection[index][property];', + ' }', + ' }', + ' return result || map(collection, property);', + '}' + ].join('\n')); + + // replace `isArray(collection)` checks in "Collections" methods with simpler type checks + _.each(['every', 'filter', 'find', 'max', 'min', 'reduce', 'some'], function(methodName) { + source = source.replace(matchFunction(source, methodName), function(match) { + if (methodName == 'reduce') { + match = match.replace(/^( *)var noaccum\b/m, '$1if (!collection) return accumulator;\n$&'); + } + else if (/^(?:max|min)$/.test(methodName)) { + match = match.replace(/\beach\(/, 'forEach('); + if (!isUnderscore) { + return match; + } + } + return match.replace(/^(( *)if *\(.*?\bisArray\([^\)]+\).*?\) *{\n)(( *)var index[^;]+.+\n+)/m, function(snippet, statement, indent, vars) { + vars = vars + .replace(/\b(length *=)[^;]+/, '$1 collection' + (methodName == 'reduce' ? '.length' : ' ? collection.length : 0')) + .replace(RegExp('^ ' + indent, 'gm'), indent); + + return vars + statement.replace(/\bisArray\([^\)]+\)/, "typeof length == 'number'"); + }); + }); + }); + + // replace `arrays` property value of `eachIteratorOptions` with `false` + source = source.replace(/^( *)var eachIteratorOptions *= *[\s\S]+?\n\1};\n/m, function(match) { + return match.replace(/(^ *'arrays':)[^,]+/m, '$1 false'); + }); } } if (isModern) { // remove `_.isPlainObject` fallback source = source.replace(matchFunction(source, 'isPlainObject'), function(match) { - return match.replace(/!getPrototypeOf.+?: */, ''); + return match.replace(/!getPrototypeOf[^:]+:\s*/, ''); }); + + if (!isMobile) { + source = removeIsFunctionFallback(source); + } } if (isUnderscore) { // replace `_.assign` source = replaceFunction(source, 'assign', [ - ' function assign(object) {', - ' if (!object) {', - ' return object;', - ' }', - ' for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) {', - ' var iterable = arguments[argsIndex];', - ' if (iterable) {', - ' for (var key in iterable) {', - ' object[key] = iterable[key];', - ' }', + 'function assign(object) {', + ' if (!object) {', + ' return object;', + ' }', + ' for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) {', + ' var iterable = arguments[argsIndex];', + ' if (iterable) {', + ' for (var key in iterable) {', + ' object[key] = iterable[key];', ' }', ' }', - ' return object;', - ' }' + ' }', + ' return object;', + '}' ].join('\n')); // replace `_.clone` if (useUnderscoreClone) { source = replaceFunction(source, 'clone', [ - ' function clone(value) {', - ' return isObject(value)', - ' ? (isArray(value) ? slice(value) : assign({}, value))', - ' : value', - ' }' + 'function clone(value) {', + ' return isObject(value)', + ' ? (isArray(value) ? slice(value) : assign({}, value))', + ' : value;', + '}' ].join('\n')); } // replace `_.contains` source = replaceFunction(source, 'contains', [ - ' function contains(collection, target) {', - ' var length = collection ? collection.length : 0,', - ' result = false;', - " if (typeof length == 'number') {", - ' result = indexOf(collection, target) > -1;', - ' } else {', - ' each(collection, function(value) {', - ' return (result = value === target) && indicatorObject;', - ' });', - ' }', - ' return result;', - ' }' + 'function contains(collection, target) {', + ' var length = collection ? collection.length : 0,', + ' result = false;', + " if (typeof length == 'number') {", + ' result = indexOf(collection, target) > -1;', + ' } else {', + ' each(collection, function(value) {', + ' return (result = value === target) && indicatorObject;', + ' });', + ' }', + ' return result;', + '}' ].join('\n')); // replace `_.defaults` source = replaceFunction(source, 'defaults', [ - ' function defaults(object) {', - ' if (!object) {', - ' return object;', - ' }', - ' for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) {', - ' var iterable = arguments[argsIndex];', - ' if (iterable) {', - ' for (var key in iterable) {', - ' if (object[key] == null) {', - ' object[key] = iterable[key];', - ' }', + 'function defaults(object) {', + ' if (!object) {', + ' return object;', + ' }', + ' for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) {', + ' var iterable = arguments[argsIndex];', + ' if (iterable) {', + ' for (var key in iterable) {', + ' if (object[key] == null) {', + ' object[key] = iterable[key];', ' }', ' }', ' }', - ' return object;', - ' }' + ' }', + ' return object;', + '}' ].join('\n')); // replace `_.difference` source = replaceFunction(source, 'difference', [ - ' function difference(array) {', - ' var index = -1,', - ' length = array.length,', - ' flattened = concat.apply(arrayRef, arguments),', - ' result = [];', + 'function difference(array) {', + ' var index = -1,', + ' length = array.length,', + ' flattened = concat.apply(arrayRef, arguments),', + ' result = [];', '', - ' while (++index < length) {', - ' var value = array[index]', - ' if (indexOf(flattened, value, length) < 0) {', - ' result.push(value);', - ' }', + ' while (++index < length) {', + ' var value = array[index];', + ' if (indexOf(flattened, value, length) < 0) {', + ' result.push(value);', ' }', - ' return result', - ' }' + ' }', + ' return result;', + '}' + ].join('\n')); + + // replace `_.flatten` + source = replaceFunction(source, 'flatten', [ + 'function flatten(array, isShallow) {', + ' var index = -1,', + ' length = array ? array.length : 0,', + ' result = [];', + '' , + ' while (++index < length) {', + ' var value = array[index];', + ' if (isArray(value)) {', + ' push.apply(result, isShallow ? value : flatten(value));', + ' } else {', + ' result.push(value);', + ' }', + ' }', + ' return result;', + '}' ].join('\n')); // replace `_.intersection` source = replaceFunction(source, 'intersection', [ - ' function intersection(array) {', - ' var args = arguments,', - ' argsLength = args.length,', - ' index = -1,', - ' length = array ? array.length : 0,', - ' result = [];', + 'function intersection(array) {', + ' var args = arguments,', + ' argsLength = args.length,', + ' index = -1,', + ' length = array ? array.length : 0,', + ' result = [];', '', - ' outer:', - ' while (++index < length) {', - ' var value = array[index];', - ' if (indexOf(result, value) < 0) {', - ' var argsIndex = argsLength;', - ' while (--argsIndex) {', - ' if (indexOf(args[argsIndex], value) < 0) {', - ' continue outer;', - ' }', + ' outer:', + ' while (++index < length) {', + ' var value = array[index];', + ' if (indexOf(result, value) < 0) {', + ' var argsIndex = argsLength;', + ' while (--argsIndex) {', + ' if (indexOf(args[argsIndex], value) < 0) {', + ' continue outer;', ' }', - ' result.push(value);', ' }', + ' result.push(value);', ' }', - ' return result;', - ' }' + ' }', + ' return result;', + '}' ].join('\n')); // replace `_.isEmpty` source = replaceFunction(source, 'isEmpty', [ - ' function isEmpty(value) {', - ' if (!value) {', - ' return true;', - ' }', - ' if (isArray(value) || isString(value)) {', - ' return !value.length;', - ' }', - ' for (var key in value) {', - ' if (hasOwnProperty.call(value, key)) {', - ' return false;', - ' }', - ' }', + 'function isEmpty(value) {', + ' if (!value) {', ' return true;', - ' }' + ' }', + ' if (isArray(value) || isString(value)) {', + ' return !value.length;', + ' }', + ' for (var key in value) {', + ' if (hasOwnProperty.call(value, key)) {', + ' return false;', + ' }', + ' }', + ' return true;', + '}' ].join('\n')); // replace `_.isEqual` source = replaceFunction(source, 'isEqual', [ - ' function isEqual(a, b, stackA, stackB) {', - ' if (a === b) {', - ' return a !== 0 || (1 / a == 1 / b);', - ' }', - ' var type = typeof a,', - ' otherType = typeof b;', + 'function isEqual(a, b, stackA, stackB) {', + ' if (a === b) {', + ' return a !== 0 || (1 / a == 1 / b);', + ' }', + ' var type = typeof a,', + ' otherType = typeof b;', '', - ' if (a === a &&', - " (!a || (type != 'function' && type != 'object')) &&", - " (!b || (otherType != 'function' && otherType != 'object'))) {", - ' return false;', - ' }', - ' if (a == null || b == null) {', - ' return a === b;', - ' }', - ' var className = toString.call(a),', - ' otherClass = toString.call(b);', + ' if (a === a &&', + " (!a || (type != 'function' && type != 'object')) &&", + " (!b || (otherType != 'function' && otherType != 'object'))) {", + ' return false;', + ' }', + ' if (a == null || b == null) {', + ' return a === b;', + ' }', + ' var className = toString.call(a),', + ' otherClass = toString.call(b);', '', - ' if (className != otherClass) {', - ' return false;', - ' }', - ' switch (className) {', - ' case boolClass:', - ' case dateClass:', - ' return +a == +b;', + ' if (className != otherClass) {', + ' return false;', + ' }', + ' switch (className) {', + ' case boolClass:', + ' case dateClass:', + ' return +a == +b;', '', - ' case numberClass:', - ' return a != +a', - ' ? b != +b', - ' : (a == 0 ? (1 / a == 1 / b) : a == +b);', + ' case numberClass:', + ' return a != +a', + ' ? b != +b', + ' : (a == 0 ? (1 / a == 1 / b) : a == +b);', '', - ' case regexpClass:', - ' case stringClass:', - " return a == b + '';", + ' case regexpClass:', + ' case stringClass:', + ' return a == String(b);', + ' }', + ' var isArr = className == arrayClass;', + ' if (!isArr) {', + ' if (a instanceof lodash || b instanceof lodash) {', + ' return isEqual(a.__wrapped__ || a, b.__wrapped__ || b, stackA, stackB);', ' }', - ' var isArr = className == arrayClass;', - ' if (!isArr) {', - ' if (a.__wrapped__ || b.__wrapped__) {', - ' return isEqual(a.__wrapped__ || a, b.__wrapped__ || b, stackA, stackB);', - ' }', - ' if (className != objectClass) {', - ' return false;', - ' }', - ' var ctorA = a.constructor,', - ' ctorB = b.constructor;', + ' if (className != objectClass) {', + ' return false;', + ' }', + ' var ctorA = a.constructor,', + ' ctorB = b.constructor;', '', - ' if (ctorA != ctorB && !(', - ' isFunction(ctorA) && ctorA instanceof ctorA &&', - ' isFunction(ctorB) && ctorB instanceof ctorB', - ' )) {', - ' return false;', - ' }', + ' if (ctorA != ctorB && !(', + ' isFunction(ctorA) && ctorA instanceof ctorA &&', + ' isFunction(ctorB) && ctorB instanceof ctorB', + ' )) {', + ' return false;', ' }', - ' stackA || (stackA = []);', - ' stackB || (stackB = []);', + ' }', + ' stackA || (stackA = []);', + ' stackB || (stackB = []);', '', - ' var length = stackA.length;', - ' while (length--) {', - ' if (stackA[length] == a) {', - ' return stackB[length] == b;', - ' }', + ' var length = stackA.length;', + ' while (length--) {', + ' if (stackA[length] == a) {', + ' return stackB[length] == b;', ' }', - ' var result = true,', - ' size = 0;', + ' }', + ' var result = true,', + ' size = 0;', '', - ' stackA.push(a);', - ' stackB.push(b);', + ' stackA.push(a);', + ' stackB.push(b);', '', - ' if (isArr) {', - ' size = b.length;', - ' result = size == a.length;', + ' if (isArr) {', + ' size = b.length;', + ' result = size == a.length;', '', - ' if (result) {', - ' while (size--) {', - ' if (!(result = isEqual(a[size], b[size], stackA, stackB))) {', - ' break;', - ' }', + ' if (result) {', + ' while (size--) {', + ' if (!(result = isEqual(a[size], b[size], stackA, stackB))) {', + ' break;', ' }', ' }', - ' return result;', ' }', - ' forIn(b, function(value, key, b) {', - ' if (hasOwnProperty.call(b, key)) {', - ' size++;', - ' return !(result = hasOwnProperty.call(a, key) && isEqual(a[key], value, stackA, stackB)) && indicatorObject;', + ' return result;', + ' }', + ' forIn(b, function(value, key, b) {', + ' if (hasOwnProperty.call(b, key)) {', + ' size++;', + ' return !(result = hasOwnProperty.call(a, key) && isEqual(a[key], value, stackA, stackB)) && indicatorObject;', + ' }', + ' });', + '', + ' if (result) {', + ' forIn(a, function(value, key, a) {', + ' if (hasOwnProperty.call(a, key)) {', + ' return !(result = --size > -1) && indicatorObject;', ' }', ' });', - '', - ' if (result) {', - ' forIn(a, function(value, key, a) {', - ' if (hasOwnProperty.call(a, key)) {', - ' return !(result = --size > -1) && indicatorObject;', - ' }', - ' });', - ' }', - ' return result;', - ' }' + ' }', + ' return result;', + '}' + ].join('\n')); + + // replace `lodash` + source = replaceFunction(source, 'lodash', [ + 'function lodash(value) {', + ' return (value instanceof lodash)', + ' ? value', + ' : new lodashWrapper(value);', + '}' ].join('\n')); // replace `_.omit` source = replaceFunction(source, 'omit', [ - ' function omit(object) {', - ' var props = concat.apply(arrayRef, arguments),', - ' result = {};', + 'function omit(object) {', + ' var props = concat.apply(arrayRef, arguments),', + ' result = {};', '', - ' forIn(object, function(value, key) {', - ' if (indexOf(props, key, 1) < 0) {', - ' result[key] = value;', - ' }', - ' });', - ' return result;', - ' }' + ' forIn(object, function(value, key) {', + ' if (indexOf(props, key, 1) < 0) {', + ' result[key] = value;', + ' }', + ' });', + ' return result;', + '}' ].join('\n')); // replace `_.pick` source = replaceFunction(source, 'pick', [ - ' function pick(object) {', - ' var index = 0,', - ' props = concat.apply(arrayRef, arguments),', - ' length = props.length,', - ' result = {};', + 'function pick(object) {', + ' var index = 0,', + ' props = concat.apply(arrayRef, arguments),', + ' length = props.length,', + ' result = {};', '', - ' while (++index < length) {', - ' var prop = props[index];', - ' if (prop in object) {', - ' result[prop] = object[prop];', - ' }', + ' while (++index < length) {', + ' var prop = props[index];', + ' if (prop in object) {', + ' result[prop] = object[prop];', ' }', - ' return result;', - ' }' + ' }', + ' return result;', + '}' ].join('\n')); // replace `_.result` source = replaceFunction(source, 'result', [ - ' function result(object, property) {', - ' var value = object ? object[property] : null;', - ' return isFunction(value) ? object[property]() : value;', - ' }' + 'function result(object, property) {', + ' var value = object ? object[property] : null;', + ' return isFunction(value) ? object[property]() : value;', + '}' ].join('\n')); // replace `_.template` source = replaceFunction(source, 'template', [ - ' function template(text, data, options) {', - " text || (text = '');", - ' options = defaults({}, options, lodash.templateSettings);', + 'function template(text, data, options) {', + " text || (text = '');", + ' options = defaults({}, options, lodash.templateSettings);', '', - ' var index = 0,', - ' source = "__p += \'",', - ' variable = options.variable;', + ' var index = 0,', + ' source = "__p += \'",', + ' variable = options.variable;', '', - ' var reDelimiters = RegExp(', - " (options.escape || reNoMatch).source + '|' +", - " (options.interpolate || reNoMatch).source + '|' +", - " (options.evaluate || reNoMatch).source + '|$'", - " , 'g');", + ' var reDelimiters = RegExp(', + " (options.escape || reNoMatch).source + '|' +", + " (options.interpolate || reNoMatch).source + '|' +", + " (options.evaluate || reNoMatch).source + '|$'", + " , 'g');", '', - ' text.replace(reDelimiters, function(match, escapeValue, interpolateValue, evaluateValue, offset) {', - ' source += text.slice(index, offset).replace(reUnescapedString, escapeStringChar);', - ' if (escapeValue) {', - ' source += "\' +\\n_.escape(" + escapeValue + ") +\\n\'";', - ' }', - ' if (evaluateValue) {', - ' source += "\';\\n" + evaluateValue + ";\\n__p += \'";', - ' }', - ' if (interpolateValue) {', - ' source += "\' +\\n((__t = (" + interpolateValue + ")) == null ? \'\' : __t) +\\n\'";', - ' }', - ' index = offset + match.length;', - ' return match;', - ' });', - '', - ' source += "\';\\n";', - ' if (!variable) {', - " variable = 'obj';", - " source = 'with (' + variable + ' || {}) {\\n' + source + '\\n}\\n';", + ' text.replace(reDelimiters, function(match, escapeValue, interpolateValue, evaluateValue, offset) {', + ' source += text.slice(index, offset).replace(reUnescapedString, escapeStringChar);', + ' if (escapeValue) {', + ' source += "\' +\\n_.escape(" + escapeValue + ") +\\n\'";', ' }', - " source = 'function(' + variable + ') {\\n' +", - ' "var __t, __p = \'\', __j = Array.prototype.join;\\n" +', - ' "function print() { __p += __j.call(arguments, \'\') }\\n" +', - ' source +', - " 'return __p\\n}';", - '', - ' try {', - " var result = Function('_', 'return ' + source)(lodash);", - ' } catch(e) {', - ' e.source = source;', - ' throw e;', + ' if (evaluateValue) {', + ' source += "\';\\n" + evaluateValue + ";\\n__p += \'";', ' }', - ' if (data) {', - ' return result(data);', + ' if (interpolateValue) {', + ' source += "\' +\\n((__t = (" + interpolateValue + ")) == null ? \'\' : __t) +\\n\'";', ' }', - ' result.source = source;', - ' return result;', - ' }' + ' index = offset + match.length;', + ' return match;', + ' });', + '', + ' source += "\';\\n";', + ' if (!variable) {', + " variable = 'obj';", + " source = 'with (' + variable + ' || {}) {\\n' + source + '\\n}\\n';", + ' }', + " source = 'function(' + variable + ') {\\n' +", + ' "var __t, __p = \'\', __j = Array.prototype.join;\\n" +', + ' "function print() { __p += __j.call(arguments, \'\') }\\n" +', + ' source +', + " 'return __p\\n}';", + '', + ' try {', + " var result = Function('_', 'return ' + source)(lodash);", + ' } catch(e) {', + ' e.source = source;', + ' throw e;', + ' }', + ' if (data) {', + ' return result(data);', + ' }', + ' result.source = source;', + ' return result;', + '}' ].join('\n')); + // replace `_.times` + source = replaceFunction(source, 'times', [ + 'function times(n, callback, thisArg) {', + ' var index = -1,', + ' result = Array(n > -1 ? n : 0);', + '', + ' while (++index < n) {', + ' result[index] = callback.call(thisArg, index);', + ' }', + ' return result;', + '}' + ].join('\n')); + + // replace `_.toArray` + if (useUnderscoreClone) { + source = replaceFunction(source, 'toArray', [ + 'function toArray(collection) {', + ' if (isArray(collection)) {', + ' return slice(collection);', + ' }', + " if (collection && typeof collection.length == 'number') {", + ' return map(collection);', + ' }', + ' return values(collection);', + '}' + ].join('\n')); + } + // replace `_.uniq` source = replaceFunction(source, 'uniq', [ - ' function uniq(array, isSorted, callback, thisArg) {', - ' var index = -1,', - ' length = array ? array.length : 0,', - ' result = [],', - ' seen = result;', + 'function uniq(array, isSorted, callback, thisArg) {', + ' var index = -1,', + ' length = array ? array.length : 0,', + ' result = [],', + ' seen = result;', '', - " if (typeof isSorted == 'function') {", - ' thisArg = callback;', - ' callback = isSorted;', - ' isSorted = false;', - ' }', - ' if (callback) {', - ' seen = [];', - ' callback = createCallback(callback, thisArg);', - ' }', - ' while (++index < length) {', - ' var value = array[index],', - ' computed = callback ? callback(value, index, array) : value;', + " if (typeof isSorted != 'boolean' && isSorted != null) {", + ' thisArg = callback;', + ' callback = isSorted;', + ' isSorted = false;', + ' }', + ' if (callback != null) {', + ' seen = [];', + ' callback = lodash.createCallback(callback, thisArg);', + ' }', + ' while (++index < length) {', + ' var value = array[index],', + ' computed = callback ? callback(value, index, array) : value;', '', - ' if (isSorted', - ' ? !index || seen[seen.length - 1] !== computed', - ' : indexOf(seen, computed) < 0', - ' ) {', - ' if (callback) {', - ' seen.push(computed);', - ' }', - ' result.push(value);', + ' if (isSorted', + ' ? !index || seen[seen.length - 1] !== computed', + ' : indexOf(seen, computed) < 0', + ' ) {', + ' if (callback) {', + ' seen.push(computed);', ' }', + ' result.push(value);', ' }', - ' return result;', - ' }' + ' }', + ' return result;', + '}' ].join('\n')); // replace `_.uniqueId` source = replaceFunction(source, 'uniqueId', [ - ' function uniqueId(prefix) {', - " var id = ++idCounter + '';", - ' return prefix ? prefix + id : id;', - ' }' + 'function uniqueId(prefix) {', + " var id = ++idCounter + '';", + ' return prefix ? prefix + id : id;', + '}' ].join('\n')); // replace `_.where` source = replaceFunction(source, 'where', [ - ' function where(collection, properties, first) {', - ' return (first && isEmpty(properties))', - ' ? null', - ' : (first ? find : filter)(collection, properties);', - ' }' + 'function where(collection, properties, first) {', + ' return (first && isEmpty(properties))', + ' ? null', + ' : (first ? find : filter)(collection, properties);', + '}' ].join('\n')); // replace `_.without` source = replaceFunction(source, 'without', [ - ' function without(array) {', - ' var index = -1,', - ' length = array.length,', - ' result = [];', + 'function without(array) {', + ' var index = -1,', + ' length = array.length,', + ' result = [];', '', - ' while (++index < length) {', - ' var value = array[index]', - ' if (indexOf(arguments, value, 1) < 0) {', - ' result.push(value);', - ' }', + ' while (++index < length) {', + ' var value = array[index];', + ' if (indexOf(arguments, value, 1) < 0) {', + ' result.push(value);', ' }', - ' return result', - ' }' + ' }', + ' return result', + '}' ].join('\n')); // add `_.findWhere` - source = source.replace(matchFunction(source, 'find'), function (match) { - return match + [ + source = source.replace(matchFunction(source, 'find'), function(match) { + var indent = getIndent(match); + return match && (match + [ '', - ' function findWhere(object, properties) {', - ' return where(object, properties, true);', - ' }', + 'function findWhere(object, properties) {', + ' return where(object, properties, true);', + '}', '' - ].join('\n') + ].join('\n' + indent)); }); source = source.replace(getMethodAssignments(source), function(match) { @@ -2064,36 +2430,65 @@ // remove large array optimizations source = removeFunction(source, 'cachedContains'); - source = removeVar(source, 'largeArraySize'); // remove `_.isEqual` use from `createCallback` source = source.replace(matchFunction(source, 'createCallback'), function(match) { - return match.replace(/isEqual\(([^,]+), *([^,]+)[^)]+\)/, '$1 === $2'); + return match.replace(/\bisEqual\(([^,]+), *([^,]+)[^)]+\)/, '$1 === $2'); }); // remove conditional `charCodeCallback` use from `_.max` and `_.min` _.each(['max', 'min'], function(methodName) { source = source.replace(matchFunction(source, methodName), function(match) { - return match.replace(/!callback *&& *isString\(collection\)[\s\S]+?: */g, ''); + return match.replace(/=.+?callback *&& *isString[^:]+:\s*/g, '= '); }); }); + // replace `slice` with `slice.call` + source = removeFunction(source, 'slice'); + source = source.replace(/^(( *)setTimeout = context.setTimeout)([,;])/m, '$1,\n$2slice = arrayRef.slice$3'); + source = source.replace(/([^.]\bslice)\(/g, '$1.call('); + + // replace `lodash.createCallback` references with `createCallback` + if (!exposeCreateCallback) { + source = source.replace(/\blodash\.(createCallback\()\b/g, '$1'); + } // remove unneeded variables if (useUnderscoreClone) { source = removeVar(source, 'cloneableClasses'); source = removeVar(source, 'ctorByClass'); } // remove unused features from `createBound` - if (buildMethods.indexOf('partial') < 0 && buildMethods.indexOf('partialRight') < 0) { + if (_.every(['bindKey', 'partial', 'partialRight'], function(methodName) { + return !_.contains(buildMethods, methodName); + })) { source = source.replace(matchFunction(source, 'createBound'), function(match) { return match - .replace(/, *right[^)]*/, '') - .replace(/(function createBound\([^{]+{)[\s\S]+?(\n *function bound)/, '$1$2') + .replace(/, *indicator[^)]*/, '') + .replace(/(function createBound\([^{]+{)[\s\S]+?(\n *)(function bound)/, function(match, part1, indent, part2) { + return [ + part1, + 'if (!isFunction(func)) {', + ' throw new TypeError;', + '}', + part2 + ].join(indent); + }) .replace(/thisBinding *=[^}]+}/, 'thisBinding = thisArg;\n') .replace(/\(args *=.+/, 'partialArgs.concat(slice(args))'); }); } } + // replace `each` references with `forEach` and `forOwn` + if ((isUnderscore || (isModern && !isMobile)) && + _.contains(buildMethods, 'forEach') && + (_.contains(buildMethods, 'forOwn') || !exposeForOwn) + ) { + source = source + .replace(matchFunction(source, 'each'), '') + .replace(/^ *lodash\._each *=.+\n/gm, '') + .replace(/\beach(?=\(collection)/g, 'forOwn') + .replace(/\beach(?=\(\[)/g, 'forEach'); + } vm.runInContext(source, context); return context._; }()); @@ -2121,55 +2516,59 @@ source = source.replace(matchFunction(source, 'template'), function(match) { return match .replace(/iteratorTemplate *&& */g, '') - .replace(/iteratorTemplate *\? *([^:]+?) *:[^,;]+/g, '$1'); + .replace(/iteratorTemplate\s*\?\s*([^:]+?)\s*:[^,;]+/g, '$1'); }); /*----------------------------------------------------------------------*/ if (isLegacy) { source = removeSetImmediate(source); + source = removeSupportProp(source, 'fastBind'); - _.each(['isBindFast', 'isV8', 'nativeBind', 'nativeIsArray', 'nativeKeys', 'reNative'], function(varName) { + _.each(['isIeOpera', 'isV8', 'nativeBind', 'nativeIsArray', 'nativeKeys', 'reNative'], function(varName) { source = removeVar(source, varName); }); // remove native `Function#bind` branch in `_.bind` source = source.replace(matchFunction(source, 'bind'), function(match) { - return match.replace(/(?:\s*\/\/.*)*\s*return isBindFast[^:]+:\s*/, 'return '); + return match.replace(/(?:\s*\/\/.*)*\s*return support\.fastBind[^:]+:\s*/, 'return '); }); // remove native `Array.isArray` branch in `_.isArray` source = source.replace(matchFunction(source, 'isArray'), function(match) { - return match.replace(/nativeIsArray * \|\|\s*/, ''); + return match.replace(/nativeIsArray\s*\|\|\s*/, ''); }); // replace `_.keys` with `shimKeys` if (!isRemoved(source, 'keys')) { source = source.replace( matchFunction(source, 'keys').replace(/[\s\S]+?var keys *= */, ''), - matchFunction(source, 'shimKeys').replace(/[\s\S]+?function shimKeys/, 'function').replace(/}\n$/, '};\n') + matchFunction(source, 'shimKeys').replace(/[\s\S]+?var shimKeys *= */, '') ); source = removeFunction(source, 'shimKeys'); } // replace `_.isArguments` with fallback if (!isRemoved(source, 'isArguments')) { - source = source.replace( - matchFunction(source, 'isArguments').replace(/[\s\S]+?function isArguments/, ''), - getIsArgumentsFallback(source).match(/isArguments *= *function([\s\S]+?) *};/)[1] + ' }\n' - ); + source = source.replace(matchFunction(source, 'isArguments').replace(/[\s\S]+?function isArguments/, ''), function() { + var fallback = getIsArgumentsFallback(source), + body = fallback.match(/isArguments *= *function([\s\S]+? *});/)[1], + indent = getIndent(fallback); + + return body.replace(RegExp('^' + indent, 'gm'), indent.slice(0, -2)) + '\n'; + }); source = removeIsArgumentsFallback(source); } } if (isModern) { - source = removeArgsAreObjects(source); - source = removeHasObjectSpliceBug(source); + source = removeSupportArgsObject(source); + source = removeSupportSpliceObjects(source); source = removeIsArgumentsFallback(source); } if (isModern || isUnderscore) { - source = removeNoArgsClass(source); - source = removeNoNodeClass(source); + source = removeSupportArgsClass(source); + source = removeSupportNodeClass(source); } if (isMobile || isUnderscore) { source = removeVar(source, 'iteratorTemplate'); @@ -2177,36 +2576,48 @@ // inline all functions defined with `createIterator` _.functions(lodash).forEach(function(methodName) { // strip leading underscores to match pseudo private functions - var reFunc = RegExp('(\\bvar ' + methodName.replace(/^_/, '') + ' *= *)createIterator\\(((?:{|[a-zA-Z])[\\s\\S]+?)\\);\\n'); + var reFunc = RegExp('^( *)(var ' + methodName.replace(/^_/, '') + ' *= *)createIterator\\(((?:{|[a-zA-Z])[\\s\\S]+?)\\);\\n', 'm'); if (reFunc.test(source)) { // extract, format, and inject the compiled function's source code - source = source.replace(reFunc, function(match, captured) { - return captured + getFunctionSource(lodash[methodName]) + ';\n'; + source = source.replace(reFunc, function(match, indent, left) { + return (indent + left) + + getFunctionSource(lodash[methodName], indent) + ';\n'; }); } }); } if (isUnderscore) { - // remove `_.assign`, `_.forIn`, `_.forOwn`, and `_.isPlainObject` assignments + // remove `_.assign`, `_.forIn`, `_.forOwn`, `_.isPlainObject`, and `_.zipObject` assignments (function() { var snippet = getMethodAssignments(source), modified = snippet; if (!exposeAssign) { - modified = modified.replace(/^(?: *\/\/.*\s*)* *lodash\.assign *= *.+\n/m, ''); + modified = modified.replace(/^(?: *\/\/.*\s*)* *lodash\.assign *=.+\n/m, ''); + } + if (!exposeCreateCallback) { + modified = modified.replace(/^(?: *\/\/.*\s*)* *lodash\.createCallback *=.+\n/m, ''); } if (!exposeForIn) { - modified = modified.replace(/^(?: *\/\/.*\s*)* *lodash\.forIn *= *.+\n/m, ''); + modified = modified.replace(/^(?: *\/\/.*\s*)* *lodash\.forIn *=.+\n/m, ''); } if (!exposeForOwn) { - modified = modified.replace(/^(?: *\/\/.*\s*)* *lodash\.forOwn *= *.+\n/m, ''); + modified = modified.replace(/^(?: *\/\/.*\s*)* *lodash\.forOwn *=.+\n/m, ''); } if (!exposeIsPlainObject) { - modified = modified.replace(/^(?: *\/\/.*\s*)* *lodash\.isPlainObject *= *.+\n/m, ''); + modified = modified.replace(/^(?: *\/\/.*\s*)* *lodash\.isPlainObject *=.+\n/m, ''); } - source = source.replace(snippet, modified); + if (!exposeZipObject) { + modified = modified.replace(/^(?: *\/\/.*\s*)* *lodash\.zipObject *=.+\n/m, ''); + } + source = source.replace(snippet, function() { + return modified; + }); }()); + // unexpose `lodash.support` + source = source.replace(/lodash\.support *= */, ''); + // remove `thisArg` from unexposed `forIn` and `forOwn` _.each([ { 'methodName': 'forIn', 'flag': exposeForIn }, @@ -2224,7 +2635,9 @@ // remove chainability from `each` and `_.forEach` _.each(['each', 'forEach'], function(methodName) { source = source.replace(matchFunction(source, methodName), function(match) { - return match.replace(/\n *return .+?([};\s]+)$/, '$1'); + return match + .replace(/\n *return .+?([};\s]+)$/, '$1') + .replace(/\b(return) +result\b/, '$1') }); }); @@ -2251,9 +2664,12 @@ }); } if (!(isMobile || isUnderscore)) { + source = removeFromCreateIterator(source, 'support'); + // inline `iteratorTemplate` template - source = source.replace(getIteratorTemplate(source), function() { - var snippet = getFunctionSource(lodash._iteratorTemplate); + source = source.replace(getIteratorTemplate(source), function(match) { + var indent = getIndent(match), + snippet = getFunctionSource(lodash._iteratorTemplate, indent); // prepend data object references to property names to avoid having to // use a with-statement @@ -2267,22 +2683,23 @@ .replace(/function print[^}]+}/, '') .replace(/'(?:\\n|\s)+'/g, "''") .replace(/__p *\+= *' *';/g, '') + .replace(/\s*\+\s*'';/g, ';') .replace(/(__p *\+= *)' *' *\+/g, '$1') - .replace(/({) *;|; *(})/g, '$1$2') - .replace(/\(\(__t *= *\( *([^)]+) *\)\) *== *null *\? *'' *: *__t\)/g, '($1)'); + .replace(/(?:; *)([{}])|([{}])(?: *;)/g, '$1$2') + .replace(/\(\(__t *= *\( *([^)]+?) *\)\) *== *null *\? *'' *: *__t\)/g, '($1)'); // remove the with-statement snippet = snippet.replace(/ *with *\(.+?\) *{/, '\n').replace(/}([^}]*}[^}]*$)/, '$1'); // minor cleanup snippet = snippet - .replace(/obj *\|\|\s*\(obj *= *{}\);/, '') + .replace(/obj\s*\|\|\s*\(obj *= *{}\);/, '') .replace(/var __p = '';\s*__p \+=/, 'var __p ='); // remove comments, including sourceURLs snippet = snippet.replace(/\s*\/\/.*(?:\n|$)/g, ''); - return ' var iteratorTemplate = ' + snippet + ';\n'; + return indent + 'var iteratorTemplate = ' + snippet + ';\n'; }); } } @@ -2310,19 +2727,19 @@ source = source.replace(/(?: *\/\/.*\n)*( *)if *\(typeof +define[\s\S]+?else /, '$1'); } if (!isNode) { - source = source.replace(/(?: *\/\/.*\n)*( *)if *\(freeModule[\s\S]+?else *{([\s\S]+?\n)\1}\n/, '$1$2'); + source = source.replace(/(?: *\/\/.*\n)*( *)if *\(freeModule[\s\S]+?else *{([\s\S]+?\n)\1}\n+/, '$1$2'); } if (!isCommonJS) { - source = source.replace(/(?: *\/\/.*\n)*(?:( *)else *{)?\s*freeExports\.\w+ *=[\s\S]+?(?:\n\1})?\n/, ''); + source = source.replace(/(?: *\/\/.*\n)*(?:( *)else *{)?\s*freeExports\.\w+ *=[\s\S]+?(?:\n\1})?\n+/, ''); } if (!isGlobal) { - source = source.replace(/(?:( *)(})? *else(?: *if *\(_\))? *{)?(?:\s*\/\/.*)*\s*(?:window\._|_\.templates) *=[\s\S]+?(?:\n\1})?\n/g, '$1$2\n'); + source = source.replace(/(?:( *)(})? *else(?: *if *\(_\))? *{)?(?:\s*\/\/.*)*\s*(?:window\._|_\.templates) *=[\s\S]+?(?:\n\1})?\n+/g, '$1$2\n'); } // remove `if (freeExports) {...}` if it's empty if (isAMD && isGlobal) { - source = source.replace(/(?: *\/\/.*\n)* *(?:else )?if *\(freeExports\) *{\s*}\n/, ''); + source = source.replace(/(?: *\/\/.*\n)* *(?:else )?if *\(freeExports.*?\) *{\s*}\n+/, ''); } else { - source = source.replace(/(?: *\/\/.*\n)* *(?:else )?if *\(freeExports\) *{\s*}(?:\s*else *{([\s\S]+?) *})?\n/, '$1\n'); + source = source.replace(/(?: *\/\/.*\n)* *(?:else )?if *\(freeExports.*?\) *{\s*}(?:\s*else *{([\s\S]+?) *})?\n+/, '$1\n'); } }()); @@ -2334,82 +2751,132 @@ source = replaceVar(source, 'htmlUnescapes', "{'&':'&','<':'<','>':'>','"':'\"',''':\"'\"}"); } if (isRemoved(source, 'isArguments')) { - source = replaceVar(source, 'noArgsClass', 'false'); + source = replaceSupportProp(source, 'argsClass', 'true'); } if (isRemoved(source, 'isFunction')) { source = removeIsFunctionFallback(source); } if (isRemoved(source, 'mixin')) { - source = removeHasObjectSpliceBug(source); + // inline `_.mixin` call to ensure proper chaining behavior + source = source.replace(/^( *)mixin\(lodash\).+/m, function(match, indent) { + return indent + [ + 'forOwn(lodash, function(func, methodName) {', + ' lodash[methodName] = func;', + '', + ' lodash.prototype[methodName] = function() {', + ' var value = this.__wrapped__,', + ' args = [value];', + '', + ' push.apply(args, arguments);', + ' var result = func.apply(lodash, args);', + " return (value && typeof value == 'object' && value == result)", + ' ? this', + ' : new lodashWrapper(result);', + ' };', + '});' + ].join('\n' + indent); + }); + } + if (isRemoved(source, 'value')) { + source = removeFunction(source, 'wrapperToString'); + source = removeFunction(source, 'wrapperValueOf'); + source = removeSupportSpliceObjects(source); + source = removeLodashWrapper(source); // simplify the `lodash` function source = replaceFunction(source, 'lodash', [ - ' function lodash() {', - ' // no operation performed', - ' }' + 'function lodash() {', + ' // no operation performed', + '}' + ].join('\n')); + + // remove `lodash.prototype` method assignments from `_.mixin` + source = replaceFunction(source, 'mixin', [ + 'function mixin(object) {', + ' forEach(functions(object), function(methodName) {', + ' lodash[methodName] = object[methodName];', + ' });', + '}' ].join('\n')); // remove all `lodash.prototype` additions source = source .replace(/(?:\s*\/\/.*)*\n( *)forOwn\(lodash, *function\(func, *methodName\)[\s\S]+?\n\1}.+/g, '') - .replace(/(?:\s*\/\/.*)*\n( *)each\(\['[\s\S]+?\n\1}.+/g, '') - .replace(/(?:\s*\/\/.*)*\s*lodash\.prototype.+\n/g, '') - .replace(/(?:\s*\/\/.*)*\s*mixin\(lodash\).+\n/, ''); + .replace(/(?:\s*\/\/.*)*\n( *)(?:each|forEach)\(\['[\s\S]+?\n\1}.+/g, '') + .replace(/(?:\s*\/\/.*)*\n *lodash\.prototype.+/g, ''); } // remove functions, variables, and snippets that the minifier may miss if (isRemoved(source, 'clone')) { source = removeVar(source, 'cloneableClasses'); source = removeVar(source, 'ctorByClass'); } + if (isRemoved(source, 'defer')) { + source = removeSetImmediate(source); + } if (isRemoved(source, 'isArray')) { source = removeVar(source, 'nativeIsArray'); } if (isRemoved(source, 'isPlainObject')) { + source = removeFunction(source, 'shimIsPlainObject'); source = removeVar(source, 'getPrototypeOf'); - source = removeIteratesOwnLast(source); + source = removeSupportOwnLast(source); } if (isRemoved(source, 'keys')) { source = removeFunction(source, 'shimKeys'); } + if (isRemoved(source, 'parseInt')) { + source = removeVar(source, 'nativeParseInt'); + source = removeVar(source, 'reLeadingZeros'); + } if (isRemoved(source, 'template')) { // remove `templateSettings` assignment source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *lodash\.templateSettings[\s\S]+?};\n/, ''); } if (isRemoved(source, 'isArguments', 'isEmpty')) { - source = removeNoArgsClass(source); + source = removeSupportArgsClass(source); } if (isRemoved(source, 'clone', 'isEqual', 'isPlainObject')) { - source = removeNoNodeClass(source); + source = removeSupportNodeClass(source); + } + if (!/\beach\(/.test(source)) { + source = source.replace(matchFunction(source, 'each'), ''); } if ((source.match(/\bcreateIterator\b/g) || []).length < 2) { source = removeFunction(source, 'createIterator'); source = removeVar(source, 'defaultsIteratorOptions'); source = removeVar(source, 'eachIteratorOptions'); source = removeVar(source, 'forOwnIteratorOptions'); + source = removeVar(source, 'iteratorTemplate'); source = removeVar(source, 'templateIterator'); - source = removeHasDontEnumBug(source); - source = removeHasEnumPrototype(source); + source = removeSupportNonEnumShadows(source); + source = removeSupportEnumPrototypes(source); } if (isRemoved(source, 'createIterator', 'bind', 'keys')) { - source = removeSetImmediate(source); - source = removeVar(source, 'isBindFast'); + source = removeSupportProp(source, 'fastBind'); source = removeVar(source, 'isV8'); source = removeVar(source, 'nativeBind'); } if (isRemoved(source, 'createIterator', 'keys')) { source = removeVar(source, 'nativeKeys'); source = removeKeysOptimization(source); - source = removeNonEnumArgs(source); + source = removeSupportNonEnumArgs(source); } - if (!source.match(/var (?:hasDontEnumBug|hasEnumPrototype|iteratesOwnLast|nonEnumArgs)\b/g)) { - // remove IIFE used to assign `hasDontEnumBug`, `hasEnumPrototype`, `iteratesOwnLast`, and `nonEnumArgs` - source = source.replace(/^ *\(function\(\) *{[\s\S]+?}\(1\)\);\n/m, ''); + if (!/support\.(?:enumPrototypes|nonEnumShadows|ownLast)\b/.test(source)) { + // remove code used to resolve unneeded `support` properties + source = source.replace(/^ *\(function[\s\S]+?\n(( *)var ctor *= *function[\s\S]+?(?:\n *for.+)+\n)([\s\S]+?)}\(1\)\);\n/m, function(match, setup, indent, body) { + if (/support\.spliceObjects\b/.test(match)) { + return match.replace(setup, indent + "var object = { '0': 1, 'length': 1 };\n"); + } else if (/support\.nonEnumArgs\b/.test(match)) { + return match.replace(setup, indent + 'for (var prop in arguments) { }\n'); + } + return body.replace(RegExp('^' + indent, 'gm'), indent.slice(0, -2)); + }); } } - if ((source.match(/\bfreeModule\b/g) || []).length < 2) { + if (_.size(source.match(/\bfreeModule\b/g)) < 2) { source = removeVar(source, 'freeModule'); } - if ((source.match(/\bfreeExports\b/g) || []).length < 2) { + if (_.size(source.match(/\bfreeExports\b/g)) < 2) { source = removeVar(source, 'freeExports'); } diff --git a/build/minify.js b/build/minify.js index 2250669eeb..88acedb21d 100755 --- a/build/minify.js +++ b/build/minify.js @@ -2,24 +2,31 @@ ;(function() { 'use strict'; - /** Load Node modules */ + /** Load Node.js modules */ var fs = require('fs'), https = require('https'), path = require('path'), spawn = require('child_process').spawn, - zlib = require('zlib'), - tar = require('../vendor/tar/tar.js'), - _ = require('../lodash.js'); + zlib = require('zlib'); /** Load other modules */ - var preprocess = require('./pre-compile.js'), - postprocess = require('./post-compile.js'); + var _ = require('../lodash.js'), + mkdirpSync = require('./mkdirp-sync.js'), + preprocess = require('./pre-compile.js'), + postprocess = require('./post-compile.js'), + tar = require('../vendor/tar/tar.js'); + + /** Add `fs.existsSync` for older versions of Node.js */ + fs.existsSync || (fs.existsSync = path.existsSync); + + /** Add `path.sep` for older versions of Node.js */ + path.sep || (path.sep = process.platform == 'win32' ? '\\' : '/'); /** The Git object ID of `closure-compiler.tar.gz` */ - var closureId = '23cf67d0f0b979d97631fc108a2a43bb82225994'; + var closureId = 'a95a8007892aa824ce90c6aa3d3abb0489bf0fff'; /** The Git object ID of `uglifyjs.tar.gz` */ - var uglifyId = 'a934fb18f8fa2768c6a68de44b6e035fe96a268b'; + var uglifyId = '41308bd569db41a32d4f08af115875d0342e8bfb'; /** The path of the directory that is the base of the repository */ var basePath = fs.realpathSync(path.join(__dirname, '..')); @@ -39,6 +46,9 @@ /** The media type for raw blob data */ var mediaType = 'application/vnd.github.v3.raw'; + /** Used to detect the Node.js executable in command-line arguments */ + var reNode = RegExp('(?:^|' + path.sep + ')node(?:\\.exe)?$'); + /** Used to reference parts of the blob href */ var location = (function() { var host = 'api.github.com', @@ -59,9 +69,6 @@ 'advanced': 'ADVANCED_OPTIMIZATIONS' }; - /** Reassign `existsSync` for older versions of Node */ - fs.existsSync || (fs.existsSync = path.existsSync); - /*--------------------------------------------------------------------------*/ /** @@ -96,7 +103,7 @@ options = source; // used to report invalid command-line arguments - var invalidArgs = _.reject(options.slice(options[0] == 'node' ? 2 : 0), function(value, index, options) { + var invalidArgs = _.reject(options.slice(reNode.test(options[0]) ? 2 : 0), function(value, index, options) { if (/^(?:-o|--output)$/.test(options[index - 1]) || /^modes=.*$/.test(value)) { return true; @@ -125,9 +132,9 @@ return; } var filePath = options[options.length - 1], - isMapped = options.indexOf('-p') > -1 || options.indexOf('--source-map') > -1, - isSilent = options.indexOf('-s') > -1 || options.indexOf('--silent') > -1, - isTemplate = options.indexOf('-t') > -1 || options.indexOf('--template') > -1, + isMapped = _.contains(options, '-p') || _.contains(options, '--source-map'), + isSilent = _.contains(options, '-s') || _.contains(options, '--silent'), + isTemplate = _.contains(options, '-t') || _.contains(options, '--template'), outputPath = path.join(path.dirname(filePath), path.basename(filePath, '.js') + '.min.js'); modes = options.reduce(function(result, value) { @@ -138,7 +145,9 @@ outputPath = options.reduce(function(result, value, index) { if (/-o|--output/.test(value)) { result = options[index + 1]; - result = path.join(fs.realpathSync(path.dirname(result)), path.basename(result)); + var dirname = path.dirname(result); + mkdirpSync(dirname); + result = path.join(fs.realpathSync(dirname), path.basename(result)); } return result; }, outputPath); @@ -238,9 +247,9 @@ }; // begin the minification process - if (modes.indexOf('simple') > -1) { + if (_.contains(modes, 'simple')) { closureCompile.call(this, source, 'simple', onClosureSimpleCompile.bind(this)); - } else if (modes.indexOf('advanced') > -1) { + } else if (_.contains(modes, 'advanced')) { onClosureSimpleGzip.call(this); } else { onClosureAdvancedGzip.call(this); @@ -353,7 +362,7 @@ source = source.replace(license, ''); } - var hasIIFE = /^;?\(function[^{]+{\s*/.test(source), + var hasIIFE = /^;?\(function[^{]+{/.test(source), isStrict = hasIIFE && /^;?\(function[^{]+{\s*["']use strict["']/.test(source); // to avoid stripping the IIFE, convert it to a function call @@ -446,6 +455,7 @@ toplevel.figure_out_scope(); toplevel = toplevel.transform(uglifyJS.Compressor({ 'comparisons': false, + 'unsafe': true, 'unsafe_comps': true, 'warnings': false })); @@ -514,7 +524,7 @@ this.compiled.simple.gzip = result; } // compile the source using advanced optimizations - if (this.modes.indexOf('advanced') > -1) { + if (_.contains(this.modes, 'advanced')) { closureCompile.call(this, this.source, 'advanced', onClosureAdvancedCompile.bind(this)); } else { onClosureAdvancedGzip.call(this); @@ -601,10 +611,10 @@ } // minify the already Closure Compiler simple optimized source using UglifyJS var modes = this.modes; - if (modes.indexOf('hybrid') > -1) { - if (modes.indexOf('simple') > -1) { + if (_.contains(modes, 'hybrid')) { + if (_.contains(modes, 'simple')) { uglify.call(this, this.compiled.simple.source, 'hybrid (simple)', onSimpleHybrid.bind(this)); - } else if (modes.indexOf('advanced') > -1) { + } else if (_.contains(modes, 'advanced')) { onSimpleHybridGzip.call(this); } } else { @@ -646,7 +656,7 @@ this.hybrid.simple.gzip = result; } // minify the already Closure Compiler advance optimized source using UglifyJS - if (this.modes.indexOf('advanced') > -1) { + if (_.contains(this.modes, 'advanced')) { uglify.call(this, this.compiled.advanced.source, 'hybrid (advanced)', onAdvancedHybrid.bind(this)); } else { onComplete.call(this); diff --git a/build/mkdirp-sync.js b/build/mkdirp-sync.js new file mode 100755 index 0000000000..19e0f915de --- /dev/null +++ b/build/mkdirp-sync.js @@ -0,0 +1,43 @@ +#!/usr/bin/env node +;(function() { + 'use strict'; + + /** Load Node.js modules */ + var fs = require('fs'), + path = require('path'); + + /** Add `path.sep` for older versions of Node.js */ + path.sep || (path.sep = process.platform == 'win32' ? '\\' : '/'); + + /*--------------------------------------------------------------------------*/ + + /** + * Makes the given `dirname` directory, without throwing errors for existing + * directories and making parent directories as needed. + * + * @param {String} dirname The path of the directory. + * @param {Number|String} [mode='0777'] The permission mode. + */ + function mkdirpSync(dirname, mode) { + var sep = path.sep; + + // ensure relative paths are prefixed with `./` + if (!RegExp('^\\.?' + sep).test(dirname)) { + dirname = '.' + sep + dirname; + } + dirname.split(sep).reduce(function(currPath, segment) { + currPath += sep + segment; + try { + currPath = fs.realpathSync(currPath); + } catch(e) { + fs.mkdirSync(currPath, mode); + } + return currPath; + }); + } + + /*--------------------------------------------------------------------------*/ + + // expose + module.exports = mkdirpSync; +}()); diff --git a/build/post-compile.js b/build/post-compile.js index db56866f83..1cd032b6bd 100644 --- a/build/post-compile.js +++ b/build/post-compile.js @@ -2,7 +2,7 @@ ;(function() { 'use strict'; - /** The Node filesystem module */ + /** The Node.js filesystem module */ var fs = require('fs'); /** The minimal license/copyright template */ @@ -30,7 +30,11 @@ // correct overly aggressive Closure Compiler advanced optimizations source = source .replace(/prototype\s*=\s*{\s*valueOf\s*:\s*1\s*}/, 'prototype={valueOf:1,y:1}') - .replace(/(document[^&]+&&)\s*(?:\w+|!\d)/, '$1!({toString:0}+"")'); + .replace(/(document[^&]+&&)\s*(?:\w+|!\d)/, '$1!({toString:0}+"")') + + source = source.replace(/(\w+\.prototype\s*=\s*)\w+(?=\.prototype;)/, function(match, left) { + return left + /\w+(?=\.VERSION)/.exec(source); + }); // flip `typeof` expressions to help optimize Safari and // correct the AMD module definition for AMD build optimizers diff --git a/build/pre-compile.js b/build/pre-compile.js index 94e1011b34..69ef77732b 100644 --- a/build/pre-compile.js +++ b/build/pre-compile.js @@ -2,7 +2,7 @@ ;(function() { 'use strict'; - /** The Node filesystem module */ + /** The Node.js filesystem module */ var fs = require('fs'); /** Used to minify variables embedded in compiled strings */ @@ -12,7 +12,6 @@ 'argsLength', 'callback', 'collection', - 'createCallback', 'ctor', 'guard', 'hasOwnProperty', @@ -22,7 +21,8 @@ 'isString', 'iterable', 'length', - 'nativeKeys', + 'keys', + 'lodash', 'object', 'objectTypes', 'ownIndex', @@ -33,21 +33,18 @@ 'thisArg' ]; - /** Used to minify `compileIterator` option properties */ + /** Used to minify `iteratorTemplate` data properties */ var iteratorOptions = [ 'args', 'arrays', 'bottom', 'firstArg', - 'hasDontEnumBug', - 'hasEnumPrototype', - 'isKeysFast', + 'init', 'loop', - 'nonEnumArgs', - 'noCharByIndex', - 'shadowed', + 'shadowedProps', 'top', - 'useHas' + 'useHas', + 'useKeys' ]; /** Used to minify variables and string values to a single character */ @@ -58,12 +55,25 @@ /** Used to protect the specified properties from getting minified */ var propWhitelist = [ + 'Array', + 'Boolean', + 'Date', + 'Function', + 'Math', + 'Number', + 'Object', + 'RegExp', + 'String', + 'TypeError', + 'VERSION', '_', '__wrapped__', 'after', 'all', 'amd', 'any', + 'argsClass', + 'argsObject', 'assign', 'at', 'attachEvent', @@ -78,6 +88,7 @@ 'compose', 'contains', 'countBy', + 'createCallback', 'criteria', 'debounce', 'defaults', @@ -87,14 +98,19 @@ 'difference', 'drop', 'each', + 'enumPrototypes', 'environment', 'escape', 'evaluate', 'every', 'exports', 'extend', + 'fastBind', + 'fastKeys', 'filter', 'find', + 'findIndex', + 'findKey', 'first', 'flatten', 'foldl', @@ -107,8 +123,8 @@ 'groupBy', 'has', 'head', - 'imports', 'identity', + 'imports', 'include', 'index', 'indexOf', @@ -148,11 +164,15 @@ 'min', 'mixin', 'noConflict', + 'nodeClass', + 'nonEnumArgs', + 'nonEnumShadows', 'object', 'omit', 'once', - 'opera', + 'ownLast', 'pairs', + 'parseInt', 'partial', 'partialRight', 'pick', @@ -164,6 +184,7 @@ 'reject', 'rest', 'result', + 'runInContext', 'select', 'setImmediate', 'setTimeout', @@ -173,6 +194,8 @@ 'sortBy', 'sortedIndex', 'source', + 'spliceObjects', + 'support', 'tail', 'take', 'tap', @@ -182,6 +205,7 @@ 'times', 'toArray', 'unescape', + 'unindexedChars', 'union', 'uniq', 'unique', @@ -189,11 +213,11 @@ 'value', 'values', 'variable', - 'VERSION', 'where', 'without', 'wrap', 'zip', + 'zipObject', // properties used by the `backbone` and `underscore` builds '__chain__', @@ -226,7 +250,13 @@ return "['" + prop.replace(/['\n\r\t]/g, '\\$&') + "']"; }); - // remove brackets from `_.escape()` in `_.template` + // remove brackets from `lodash.createCallback` in `eachIteratorOptions` + source = source.replace('lodash[\'createCallback\'](callback, thisArg)"', 'lodash.createCallback(callback, thisArg)"'); + + // remove brackets from `lodash.createCallback` in `_.assign` + source = source.replace("' var callback = lodash['createCallback']", "'var callback=lodash.createCallback"); + + // remove brackets from `_.escape` in `_.template` source = source.replace(/__e *= *_\['escape']/g, '__e=_.escape'); // remove brackets from `collection.indexOf` in `_.contains` @@ -236,17 +266,17 @@ source = source.replace("result[length]['value']", 'result[length].value'); // remove whitespace from string literals - source = source.replace(/^([ "'\w]+:)? *"[^"\\\n]*(?:\\.[^"\\\n]*)*"|'[^'\\\n]*(?:\\.[^'\\\n]*)*'/gm, function(string, captured) { - // remove object literal property name - if (/:$/.test(captured)) { - string = string.slice(captured.length); + source = source.replace(/^((?:[ "'\w]+:)? *)"[^"\\\n]*(?:\\.[^"\\\n]*)*"|'[^'\\\n]*(?:\\.[^'\\\n]*)*'/gm, function(string, left) { + // clip after an object literal property name or leading spaces + if (left) { + string = string.slice(left.length); } // avoids removing the '\n' of the `stringEscapes` object string = string.replace(/\[object |delete |else (?!{)|function | in |return\s+[\w"']|throw |typeof |use strict|var |@ |(["'])\\n\1|\\\\n|\\n|\s+/g, function(match) { return match == false || match == '\\n' ? '' : match; }); - // prepend object literal property name - return (captured || '') + string; + // unclip + return (left || '') + string; }); // remove whitespace from `_.template` related regexes @@ -287,7 +317,9 @@ }); // replace with modified snippet - source = source.replace(snippet, modified); + source = source.replace(snippet, function() { + return modified; + }); }); }()); @@ -297,7 +329,7 @@ // match the `iteratorTemplate` '( +)var iteratorTemplate\\b[\\s\\S]+?\\n\\1}', // match methods created by `createIterator` calls - 'createIterator\\((?:{|[a-zA-Z]+)[\\s\\S]+?\\);\\n', + 'createIterator\\((?:{|[a-zA-Z]+)[\\s\\S]*?\\);\\n', // match variables storing `createIterator` options '( +)var [a-zA-Z]+IteratorOptions\\b[\\s\\S]+?\\n\\2}', // match the the `createIterator` function @@ -320,11 +352,6 @@ return "['" + prop.replace(/['\n\r\t]/g, '\\$&') + "']"; }); - if (isCreateIterator) { - // clip before the `factory` call to avoid minifying its arguments - source = source.replace(snippet, modified); - snippet = modified = modified.replace(/return factory\([\s\S]+$/, ''); - } // minify `createIterator` option property names iteratorOptions.forEach(function(property, index) { var minName = minNames[index]; @@ -336,20 +363,27 @@ }); // minify snippet variables / arguments - compiledVars.forEach(function(variable, index) { + compiledVars.forEach(function(varName, index) { var minName = minNames[index]; + // minify variable names present in strings + if (isCreateIterator) { + modified = modified.replace(RegExp('(([\'"])[^\\n\\2]*?)\\b' + varName + '\\b(?=[^\\n\\2]*\\2[ ,+]+$)', 'gm'), '$1' + minName); + } // ensure properties in compiled strings aren't minified - modified = modified.replace(RegExp('([^.]\\b)' + variable + '\\b(?!\' *[\\]:])', 'g'), '$1' + minName); - + else { + modified = modified.replace(RegExp('([^.])\\b' + varName + '\\b(?!\' *[\\]:])', 'g'), '$1' + minName); + } // correct `typeof` values - if (/^(?:boolean|function|object|number|string|undefined)$/.test(variable)) { - modified = modified.replace(RegExp("(typeof [^']+')" + minName + "'", 'g'), '$1' + variable + "'"); + if (/^(?:boolean|function|object|number|string|undefined)$/.test(varName)) { + modified = modified.replace(RegExp("(typeof [^']+')" + minName + "'", 'g'), '$1' + varName + "'"); } }); // replace with modified snippet - source = source.replace(snippet, modified); + source = source.replace(snippet, function() { + return modified; + }); }); return source; diff --git a/dist/lodash.compat.js b/dist/lodash.compat.js index 37ebc920be..eb0c9d966a 100644 --- a/dist/lodash.compat.js +++ b/dist/lodash.compat.js @@ -1,13 +1,16 @@ /** * @license - * Lo-Dash 1.0.1 (Custom Build) + * Lo-Dash 1.1.0 (Custom Build) * Build: `lodash -o ./dist/lodash.compat.js` * Copyright 2012-2013 The Dojo Foundation * Based on Underscore.js 1.4.4 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud Inc. * Available under MIT license */ -;(function(window, undefined) { +;(function(window) { + + /** Used as a safe reference for `undefined` in pre ES5 environments */ + var undefined; /** Detect free variable `exports` */ var freeExports = typeof exports == 'object' && exports; @@ -21,39 +24,19 @@ window = freeGlobal; } - /** Used for array and object method references */ - var arrayRef = [], - objectRef = {}; - /** Used to generate unique IDs */ var idCounter = 0; /** Used internally to indicate various things */ - var indicatorObject = objectRef; - - /** Used by `cachedContains` as the default size when optimizations are enabled for large arrays */ - var largeArraySize = 30; - - /** Used to restore the original `_` reference in `noConflict` */ - var oldDash = window._; - - /** Used to match HTML entities */ - var reEscapedHtml = /&(?:amp|lt|gt|quot|#39);/g; + var indicatorObject = {}; /** Used to match empty string literals in compiled template source */ var reEmptyStringLeading = /\b__p \+= '';/g, reEmptyStringMiddle = /\b(__p \+=) '' \+/g, reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g; - /** Used to match regexp flags from their coerced string values */ - var reFlags = /\w*$/; - - /** Used to detect if a method is native */ - var reNative = RegExp('^' + - (objectRef.valueOf + '') - .replace(/[.*+?^${}()|[\]\\]/g, '\\$&') - .replace(/valueOf|for [^\]]+/g, '.+?') + '$' - ); + /** Used to match HTML entities */ + var reEscapedHtml = /&(?:amp|lt|gt|quot|#39);/g; /** * Used to match ES6 template delimiters @@ -61,9 +44,15 @@ */ var reEsTemplate = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g; + /** Used to match regexp flags from their coerced string values */ + var reFlags = /\w*$/; + /** Used to match "interpolate" template delimiters */ var reInterpolate = /<%=([\s\S]+?)%>/g; + /** Used to match leading zeros to be removed */ + var reLeadingZeros = /^0+(?=.$)/; + /** Used to ensure capturing order of template delimiters */ var reNoMatch = /($^)/; @@ -73,8 +62,15 @@ /** Used to match unescaped characters in compiled string literals */ var reUnescapedString = /['\n\r\t\u2028\u2029\\]/g; + /** Used to assign default `context` object properties */ + var contextProps = [ + 'Array', 'Boolean', 'Date', 'Function', 'Math', 'Number', 'Object', 'RegExp', + 'String', '_', 'attachEvent', 'clearTimeout', 'isFinite', 'isNaN', 'parseInt', + 'setImmediate', 'setTimeout' + ]; + /** Used to fix the JScript [[DontEnum]] bug */ - var shadowed = [ + var shadowedProps = [ 'constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', 'toLocaleString', 'toString', 'valueOf' ]; @@ -82,25 +78,6 @@ /** Used to make template sourceURLs easier to identify */ var templateCounter = 0; - /** Native method shortcuts */ - var ceil = Math.ceil, - concat = arrayRef.concat, - floor = Math.floor, - getPrototypeOf = reNative.test(getPrototypeOf = Object.getPrototypeOf) && getPrototypeOf, - hasOwnProperty = objectRef.hasOwnProperty, - push = arrayRef.push, - toString = objectRef.toString; - - /* Native method shortcuts for methods with the same name as other `lodash` methods */ - var nativeBind = reNative.test(nativeBind = slice.bind) && nativeBind, - nativeIsArray = reNative.test(nativeIsArray = Array.isArray) && nativeIsArray, - nativeIsFinite = window.isFinite, - nativeIsNaN = window.isNaN, - nativeKeys = reNative.test(nativeKeys = Object.keys) && nativeKeys, - nativeMax = Math.max, - nativeMin = Math.min, - nativeRandom = Math.random; - /** `Object#toString` result shortcuts */ var argsClass = '[object Arguments]', arrayClass = '[object Array]', @@ -112,88 +89,6 @@ regexpClass = '[object RegExp]', stringClass = '[object String]'; - /** Detect various environments */ - var isIeOpera = !!window.attachEvent, - isV8 = nativeBind && !/\n|true/.test(nativeBind + isIeOpera); - - /* Detect if `Function#bind` exists and is inferred to be fast (all but V8) */ - var isBindFast = nativeBind && !isV8; - - /* Detect if `Object.keys` exists and is inferred to be fast (IE, Opera, V8) */ - var isKeysFast = nativeKeys && (isIeOpera || isV8); - - /** - * Detect the JScript [[DontEnum]] bug: - * - * In IE < 9 an objects own properties, shadowing non-enumerable ones, are - * made non-enumerable as well. - */ - var hasDontEnumBug; - - /** - * Detect if a `prototype` properties are enumerable by default: - * - * Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1 - * (if the prototype or a property on the prototype has been set) - * incorrectly sets a function's `prototype` property [[Enumerable]] - * value to `true`. - */ - var hasEnumPrototype; - - /** Detect if own properties are iterated after inherited properties (IE < 9) */ - var iteratesOwnLast; - - /** - * Detect if `Array#shift` and `Array#splice` augment array-like objects - * incorrectly: - * - * Firefox < 10, IE compatibility mode, and IE < 9 have buggy Array `shift()` - * and `splice()` functions that fail to remove the last element, `value[0]`, - * of array-like objects even though the `length` property is set to `0`. - * The `shift()` method is buggy in IE 8 compatibility mode, while `splice()` - * is buggy regardless of mode in IE < 9 and buggy in compatibility mode in IE 9. - */ - var hasObjectSpliceBug = (hasObjectSpliceBug = { '0': 1, 'length': 1 }, - arrayRef.splice.call(hasObjectSpliceBug, 0, 1), hasObjectSpliceBug[0]); - - /** Detect if `arguments` object indexes are non-enumerable (Firefox < 4, IE < 9, PhantomJS, Safari < 5.1) */ - var nonEnumArgs = true; - - (function() { - var props = []; - function ctor() { this.x = 1; } - ctor.prototype = { 'valueOf': 1, 'y': 1 }; - for (var prop in new ctor) { props.push(prop); } - for (prop in arguments) { nonEnumArgs = !prop; } - - hasDontEnumBug = !/valueOf/.test(props); - hasEnumPrototype = ctor.propertyIsEnumerable('prototype'); - iteratesOwnLast = props[0] != 'x'; - }(1)); - - /** Detect if `arguments` objects are `Object` objects (all but Opera < 10.5) */ - var argsAreObjects = arguments.constructor == Object; - - /** Detect if `arguments` objects [[Class]] is unresolvable (Firefox < 4, IE < 9) */ - var noArgsClass = !isArguments(arguments); - - /** - * Detect lack of support for accessing string characters by index: - * - * IE < 8 can't access characters by index and IE 8 can only access - * characters by index on string literals. - */ - var noCharByIndex = ('x'[0] + Object('x')[0]) != 'xx'; - - /** - * Detect if a DOM node's [[Class]] is unresolvable (IE < 9) - * and that the JS engine won't error when attempting to coerce an object to - * a string without a `toString` function. - */ - try { - var noNodeClass = toString.call(document) == objectClass && !({ 'toString': 0 } + ''); - } catch(e) { } - /** Used to identify object classifications that `_.clone` supports */ var cloneableClasses = {}; cloneableClasses[funcClass] = false; @@ -202,16 +97,6 @@ cloneableClasses[numberClass] = cloneableClasses[objectClass] = cloneableClasses[regexpClass] = cloneableClasses[stringClass] = true; - /** Used to lookup a built-in constructor by [[Class]] */ - var ctorByClass = {}; - ctorByClass[arrayClass] = Array; - ctorByClass[boolClass] = Boolean; - ctorByClass[dateClass] = Date; - ctorByClass[objectClass] = Object; - ctorByClass[numberClass] = Number; - ctorByClass[regexpClass] = RegExp; - ctorByClass[stringClass] = String; - /** Used to determine if values are of the language type Object */ var objectTypes = { 'boolean': false, @@ -236,936 +121,1040 @@ /*--------------------------------------------------------------------------*/ /** - * Creates a `lodash` object, that wraps the given `value`, to enable method - * chaining. - * - * In addition to Lo-Dash methods, wrappers also have the following `Array` methods: - * `concat`, `join`, `pop`, `push`, `reverse`, `shift`, `slice`, `sort`, `splice`, - * and `unshift` - * - * The chainable wrapper functions are: - * `after`, `assign`, `bind`, `bindAll`, `bindKey`, `chain`, `compact`, `compose`, - * `concat`, `countBy`, `debounce`, `defaults`, `defer`, `delay`, `difference`, - * `filter`, `flatten`, `forEach`, `forIn`, `forOwn`, `functions`, `groupBy`, - * `initial`, `intersection`, `invert`, `invoke`, `keys`, `map`, `max`, `memoize`, - * `merge`, `min`, `object`, `omit`, `once`, `pairs`, `partial`, `partialRight`, - * `pick`, `pluck`, `push`, `range`, `reject`, `rest`, `reverse`, `shuffle`, - * `slice`, `sort`, `sortBy`, `splice`, `tap`, `throttle`, `times`, `toArray`, - * `union`, `uniq`, `unshift`, `values`, `where`, `without`, `wrap`, and `zip` - * - * The non-chainable wrapper functions are: - * `clone`, `cloneDeep`, `contains`, `escape`, `every`, `find`, `has`, `identity`, - * `indexOf`, `isArguments`, `isArray`, `isBoolean`, `isDate`, `isElement`, `isEmpty`, - * `isEqual`, `isFinite`, `isFunction`, `isNaN`, `isNull`, `isNumber`, `isObject`, - * `isPlainObject`, `isRegExp`, `isString`, `isUndefined`, `join`, `lastIndexOf`, - * `mixin`, `noConflict`, `pop`, `random`, `reduce`, `reduceRight`, `result`, - * `shift`, `size`, `some`, `sortedIndex`, `template`, `unescape`, and `uniqueId` - * - * The wrapper functions `first` and `last` return wrapped values when `n` is - * passed, otherwise they return unwrapped values. - * - * @name _ - * @constructor - * @category Chaining - * @param {Mixed} value The value to wrap in a `lodash` instance. - * @returns {Object} Returns a `lodash` instance. - */ - function lodash(value) { - // exit early if already wrapped, even if wrapped by a different `lodash` constructor - if (value && typeof value == 'object' && value.__wrapped__) { - return value; - } - // allow invoking `lodash` without the `new` operator - if (!(this instanceof lodash)) { - return new lodash(value); - } - this.__wrapped__ = value; - } - - /** - * By default, the template delimiters used by Lo-Dash are similar to those in - * embedded Ruby (ERB). Change the following template settings to use alternative - * delimiters. + * Create a new `lodash` function using the given `context` object. * * @static * @memberOf _ - * @type Object + * @category Utilities + * @param {Object} [context=window] The context object. + * @returns {Function} Returns the `lodash` function. */ - lodash.templateSettings = { + function runInContext(context) { + // Avoid issues with some ES3 environments that attempt to use values, named + // after built-in constructors like `Object`, for the creation of literals. + // ES5 clears this up by stating that literals must use built-in constructors. + // See http://es5.github.com/#x11.1.5. + context = context ? _.defaults(window.Object(), context, _.pick(window, contextProps)) : window; + + /** Native constructor references */ + var Array = context.Array, + Boolean = context.Boolean, + Date = context.Date, + Function = context.Function, + Math = context.Math, + Number = context.Number, + Object = context.Object, + RegExp = context.RegExp, + String = context.String, + TypeError = context.TypeError; + + /** Used for `Array` and `Object` method references */ + var arrayRef = Array(), + objectRef = Object(); + + /** Used to restore the original `_` reference in `noConflict` */ + var oldDash = context._; + + /** Used to detect if a method is native */ + var reNative = RegExp('^' + + String(objectRef.valueOf) + .replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + .replace(/valueOf|for [^\]]+/g, '.+?') + '$' + ); - /** - * Used to detect `data` property values to be HTML-escaped. - * - * @memberOf _.templateSettings - * @type RegExp - */ - 'escape': /<%-([\s\S]+?)%>/g, + /** Native method shortcuts */ + var ceil = Math.ceil, + clearTimeout = context.clearTimeout, + concat = arrayRef.concat, + floor = Math.floor, + getPrototypeOf = reNative.test(getPrototypeOf = Object.getPrototypeOf) && getPrototypeOf, + hasOwnProperty = objectRef.hasOwnProperty, + push = arrayRef.push, + setImmediate = context.setImmediate, + setTimeout = context.setTimeout, + toString = objectRef.toString; + + /* Native method shortcuts for methods with the same name as other `lodash` methods */ + var nativeBind = reNative.test(nativeBind = slice.bind) && nativeBind, + nativeIsArray = reNative.test(nativeIsArray = Array.isArray) && nativeIsArray, + nativeIsFinite = context.isFinite, + nativeIsNaN = context.isNaN, + nativeKeys = reNative.test(nativeKeys = Object.keys) && nativeKeys, + nativeMax = Math.max, + nativeMin = Math.min, + nativeParseInt = context.parseInt, + nativeRandom = Math.random; + + /** Detect various environments */ + var isIeOpera = reNative.test(context.attachEvent), + isV8 = nativeBind && !/\n|true/.test(nativeBind + isIeOpera); + + /** Used to lookup a built-in constructor by [[Class]] */ + var ctorByClass = {}; + ctorByClass[arrayClass] = Array; + ctorByClass[boolClass] = Boolean; + ctorByClass[dateClass] = Date; + ctorByClass[objectClass] = Object; + ctorByClass[numberClass] = Number; + ctorByClass[regexpClass] = RegExp; + ctorByClass[stringClass] = String; + + /*--------------------------------------------------------------------------*/ /** - * Used to detect code to be evaluated. + * Creates a `lodash` object, that wraps the given `value`, to enable method + * chaining. * - * @memberOf _.templateSettings - * @type RegExp - */ - 'evaluate': /<%([\s\S]+?)%>/g, - - /** - * Used to detect `data` property values to inject. + * In addition to Lo-Dash methods, wrappers also have the following `Array` methods: + * `concat`, `join`, `pop`, `push`, `reverse`, `shift`, `slice`, `sort`, `splice`, + * and `unshift` + * + * Chaining is supported in custom builds as long as the `value` method is + * implicitly or explicitly included in the build. + * + * The chainable wrapper functions are: + * `after`, `assign`, `bind`, `bindAll`, `bindKey`, `chain`, `compact`, + * `compose`, `concat`, `countBy`, `createCallback`, `debounce`, `defaults`, + * `defer`, `delay`, `difference`, `filter`, `flatten`, `forEach`, `forIn`, + * `forOwn`, `functions`, `groupBy`, `initial`, `intersection`, `invert`, + * `invoke`, `keys`, `map`, `max`, `memoize`, `merge`, `min`, `object`, `omit`, + * `once`, `pairs`, `partial`, `partialRight`, `pick`, `pluck`, `push`, `range`, + * `reject`, `rest`, `reverse`, `shuffle`, `slice`, `sort`, `sortBy`, `splice`, + * `tap`, `throttle`, `times`, `toArray`, `union`, `uniq`, `unshift`, `values`, + * `where`, `without`, `wrap`, and `zip` + * + * The non-chainable wrapper functions are: + * `clone`, `cloneDeep`, `contains`, `escape`, `every`, `find`, `has`, + * `identity`, `indexOf`, `isArguments`, `isArray`, `isBoolean`, `isDate`, + * `isElement`, `isEmpty`, `isEqual`, `isFinite`, `isFunction`, `isNaN`, + * `isNull`, `isNumber`, `isObject`, `isPlainObject`, `isRegExp`, `isString`, + * `isUndefined`, `join`, `lastIndexOf`, `mixin`, `noConflict`, `parseInt`, + * `pop`, `random`, `reduce`, `reduceRight`, `result`, `shift`, `size`, `some`, + * `sortedIndex`, `runInContext`, `template`, `unescape`, `uniqueId`, and `value` * - * @memberOf _.templateSettings - * @type RegExp + * The wrapper functions `first` and `last` return wrapped values when `n` is + * passed, otherwise they return unwrapped values. + * + * @name _ + * @constructor + * @category Chaining + * @param {Mixed} value The value to wrap in a `lodash` instance. + * @returns {Object} Returns a `lodash` instance. */ - 'interpolate': reInterpolate, + function lodash(value) { + // don't wrap if already wrapped, even if wrapped by a different `lodash` constructor + return (value && typeof value == 'object' && !isArray(value) && hasOwnProperty.call(value, '__wrapped__')) + ? value + : new lodashWrapper(value); + } /** - * Used to reference the data object in the template text. + * An object used to flag environments features. * - * @memberOf _.templateSettings - * @type String + * @static + * @memberOf _ + * @type Object */ - 'variable': '', + var support = lodash.support = {}; + + (function() { + var ctor = function() { this.x = 1; }, + object = { '0': 1, 'length': 1 }, + props = []; + + ctor.prototype = { 'valueOf': 1, 'y': 1 }; + for (var prop in new ctor) { props.push(prop); } + for (prop in arguments) { } + + /** + * Detect if `arguments` objects are `Object` objects (all but Opera < 10.5). + * + * @memberOf _.support + * @type Boolean + */ + support.argsObject = arguments.constructor == Object; + + /** + * Detect if an `arguments` object's [[Class]] is resolvable (all but Firefox < 4, IE < 9). + * + * @memberOf _.support + * @type Boolean + */ + support.argsClass = isArguments(arguments); + + /** + * Detect if `prototype` properties are enumerable by default. + * + * Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1 + * (if the prototype or a property on the prototype has been set) + * incorrectly sets a function's `prototype` property [[Enumerable]] + * value to `true`. + * + * @memberOf _.support + * @type Boolean + */ + support.enumPrototypes = ctor.propertyIsEnumerable('prototype'); + + /** + * Detect if `Function#bind` exists and is inferred to be fast (all but V8). + * + * @memberOf _.support + * @type Boolean + */ + support.fastBind = nativeBind && !isV8; + + /** + * Detect if own properties are iterated after inherited properties (all but IE < 9). + * + * @memberOf _.support + * @type Boolean + */ + support.ownLast = props[0] != 'x'; + + /** + * Detect if `arguments` object indexes are non-enumerable + * (Firefox < 4, IE < 9, PhantomJS, Safari < 5.1). + * + * @memberOf _.support + * @type Boolean + */ + support.nonEnumArgs = prop != 0; + + /** + * Detect if properties shadowing those on `Object.prototype` are non-enumerable. + * + * In IE < 9 an objects own properties, shadowing non-enumerable ones, are + * made non-enumerable as well (a.k.a the JScript [[DontEnum]] bug). + * + * @memberOf _.support + * @type Boolean + */ + support.nonEnumShadows = !/valueOf/.test(props); + + /** + * Detect if `Array#shift` and `Array#splice` augment array-like objects correctly. + * + * Firefox < 10, IE compatibility mode, and IE < 9 have buggy Array `shift()` + * and `splice()` functions that fail to remove the last element, `value[0]`, + * of array-like objects even though the `length` property is set to `0`. + * The `shift()` method is buggy in IE 8 compatibility mode, while `splice()` + * is buggy regardless of mode in IE < 9 and buggy in compatibility mode in IE 9. + * + * @memberOf _.support + * @type Boolean + */ + support.spliceObjects = (arrayRef.splice.call(object, 0, 1), !object[0]); + + /** + * Detect lack of support for accessing string characters by index. + * + * IE < 8 can't access characters by index and IE 8 can only access + * characters by index on string literals. + * + * @memberOf _.support + * @type Boolean + */ + support.unindexedChars = ('x'[0] + Object('x')[0]) != 'xx'; + + /** + * Detect if a DOM node's [[Class]] is resolvable (all but IE < 9) + * and that the JS engine errors when attempting to coerce an object to + * a string without a `toString` function. + * + * @memberOf _.support + * @type Boolean + */ + try { + support.nodeClass = !(toString.call(document) == objectClass && !({ 'toString': 0 } + '')); + } catch(e) { + support.nodeClass = true; + } + }(1)); /** - * Used to import variables into the compiled template. + * By default, the template delimiters used by Lo-Dash are similar to those in + * embedded Ruby (ERB). Change the following template settings to use alternative + * delimiters. * - * @memberOf _.templateSettings + * @static + * @memberOf _ * @type Object */ - 'imports': { + lodash.templateSettings = { /** - * A reference to the `lodash` function. + * Used to detect `data` property values to be HTML-escaped. * - * @memberOf _.templateSettings.imports - * @type Function + * @memberOf _.templateSettings + * @type RegExp */ - '_': lodash - } - }; + 'escape': /<%-([\s\S]+?)%>/g, - /*--------------------------------------------------------------------------*/ + /** + * Used to detect code to be evaluated. + * + * @memberOf _.templateSettings + * @type RegExp + */ + 'evaluate': /<%([\s\S]+?)%>/g, - /** - * The template used to create iterator functions. - * - * @private - * @param {Obect} data The data object used to populate the text. - * @returns {String} Returns the interpolated text. - */ - var iteratorTemplate = function(obj) { - - var __p = 'var index, iterable = ' + - (obj.firstArg ) + - ', result = iterable;\nif (!iterable) return result;\n' + - (obj.top ) + - ';\n'; - if (obj.arrays) { - __p += 'var length = iterable.length; index = -1;\nif (' + - (obj.arrays ) + - ') { '; - if (obj.noCharByIndex) { - __p += '\n if (isString(iterable)) {\n iterable = iterable.split(\'\')\n } '; - } ; - __p += '\n while (++index < length) {\n ' + - (obj.loop ) + - '\n }\n}\nelse { '; - } else if (obj.nonEnumArgs) { - __p += '\n var length = iterable.length; index = -1;\n if (length && isArguments(iterable)) {\n while (++index < length) {\n index += \'\';\n ' + - (obj.loop ) + - '\n }\n } else { '; - } ; - - if (obj.hasEnumPrototype) { - __p += '\n var skipProto = typeof iterable == \'function\';\n '; - } ; - - if (obj.isKeysFast && obj.useHas) { - __p += '\n var ownIndex = -1,\n ownProps = objectTypes[typeof iterable] ? nativeKeys(iterable) : [],\n length = ownProps.length;\n\n while (++ownIndex < length) {\n index = ownProps[ownIndex];\n '; - if (obj.hasEnumPrototype) { - __p += 'if (!(skipProto && index == \'prototype\')) {\n '; - } ; - __p += - (obj.loop ) + - ''; - if (obj.hasEnumPrototype) { - __p += '}\n'; - } ; - __p += ' } '; - } else { - __p += '\n for (index in iterable) {'; - if (obj.hasEnumPrototype || obj.useHas) { - __p += '\n if ('; - if (obj.hasEnumPrototype) { - __p += '!(skipProto && index == \'prototype\')'; - } if (obj.hasEnumPrototype && obj.useHas) { - __p += ' && '; - } if (obj.useHas) { - __p += 'hasOwnProperty.call(iterable, index)'; - } ; - __p += ') { '; - } ; - __p += - (obj.loop ) + - '; '; - if (obj.hasEnumPrototype || obj.useHas) { - __p += '\n }'; - } ; - __p += '\n } '; - } ; - - if (obj.hasDontEnumBug) { - __p += '\n\n var ctor = iterable.constructor;\n '; - for (var k = 0; k < 7; k++) { - __p += '\n index = \'' + - (obj.shadowed[k] ) + - '\';\n if ('; - if (obj.shadowed[k] == 'constructor') { - __p += '!(ctor && ctor.prototype === iterable) && '; - } ; - __p += 'hasOwnProperty.call(iterable, index)) {\n ' + - (obj.loop ) + - '\n } '; - } ; - - } ; - - if (obj.arrays || obj.nonEnumArgs) { - __p += '\n}'; - } ; - __p += - (obj.bottom ) + - ';\nreturn result'; - - - return __p - }; + /** + * Used to detect `data` property values to inject. + * + * @memberOf _.templateSettings + * @type RegExp + */ + 'interpolate': reInterpolate, - /** Reusable iterator options for `assign` and `defaults` */ - var defaultsIteratorOptions = { - 'args': 'object, source, guard', - 'top': - 'var args = arguments,\n' + - ' argsIndex = 0,\n' + - " argsLength = typeof guard == 'number' ? 2 : args.length;\n" + - 'while (++argsIndex < argsLength) {\n' + - ' iterable = args[argsIndex];\n' + - ' if (iterable && objectTypes[typeof iterable]) {', - 'loop': "if (typeof result[index] == 'undefined') result[index] = iterable[index]", - 'bottom': ' }\n}' - }; + /** + * Used to reference the data object in the template text. + * + * @memberOf _.templateSettings + * @type String + */ + 'variable': '', - /** Reusable iterator options shared by `each`, `forIn`, and `forOwn` */ - var eachIteratorOptions = { - 'args': 'collection, callback, thisArg', - 'top': "callback = callback && typeof thisArg == 'undefined' ? callback : createCallback(callback, thisArg)", - 'arrays': "typeof length == 'number'", - 'loop': 'if (callback(iterable[index], index, collection) === false) return result' - }; + /** + * Used to import variables into the compiled template. + * + * @memberOf _.templateSettings + * @type Object + */ + 'imports': { + + /** + * A reference to the `lodash` function. + * + * @memberOf _.templateSettings.imports + * @type Function + */ + '_': lodash + } + }; - /** Reusable iterator options for `forIn` and `forOwn` */ - var forOwnIteratorOptions = { - 'top': 'if (!objectTypes[typeof iterable]) return result;\n' + eachIteratorOptions.top, - 'arrays': false - }; + /*--------------------------------------------------------------------------*/ - /*--------------------------------------------------------------------------*/ + /** + * The template used to create iterator functions. + * + * @private + * @param {Obect} data The data object used to populate the text. + * @returns {String} Returns the interpolated text. + */ + var iteratorTemplate = function(obj) { + + var __p = 'var index, iterable = ' + + (obj.firstArg) + + ', result = ' + + (obj.init) + + ';\nif (!iterable) return result;\n' + + (obj.top) + + ';\n'; + if (obj.arrays) { + __p += 'var length = iterable.length; index = -1;\nif (' + + (obj.arrays) + + ') { '; + if (support.unindexedChars) { + __p += '\n if (isString(iterable)) {\n iterable = iterable.split(\'\')\n } '; + } + __p += '\n while (++index < length) {\n ' + + (obj.loop) + + '\n }\n}\nelse { '; + } else if (support.nonEnumArgs) { + __p += '\n var length = iterable.length; index = -1;\n if (length && isArguments(iterable)) {\n while (++index < length) {\n index += \'\';\n ' + + (obj.loop) + + '\n }\n } else { '; + } + + if (support.enumPrototypes) { + __p += '\n var skipProto = typeof iterable == \'function\';\n '; + } + + if (obj.useHas && obj.useKeys) { + __p += '\n var ownIndex = -1,\n ownProps = objectTypes[typeof iterable] ? keys(iterable) : [],\n length = ownProps.length;\n\n while (++ownIndex < length) {\n index = ownProps[ownIndex];\n '; + if (support.enumPrototypes) { + __p += 'if (!(skipProto && index == \'prototype\')) {\n '; + } + __p += + (obj.loop); + if (support.enumPrototypes) { + __p += '}\n'; + } + __p += ' } '; + } else { + __p += '\n for (index in iterable) {'; + if (support.enumPrototypes || obj.useHas) { + __p += '\n if ('; + if (support.enumPrototypes) { + __p += '!(skipProto && index == \'prototype\')'; + } if (support.enumPrototypes && obj.useHas) { + __p += ' && '; + } if (obj.useHas) { + __p += 'hasOwnProperty.call(iterable, index)'; + } + __p += ') { '; + } + __p += + (obj.loop) + + '; '; + if (support.enumPrototypes || obj.useHas) { + __p += '\n }'; + } + __p += '\n } '; + if (support.nonEnumShadows) { + __p += '\n\n var ctor = iterable.constructor;\n '; + for (var k = 0; k < 7; k++) { + __p += '\n index = \'' + + (obj.shadowedProps[k]) + + '\';\n if ('; + if (obj.shadowedProps[k] == 'constructor') { + __p += '!(ctor && ctor.prototype === iterable) && '; + } + __p += 'hasOwnProperty.call(iterable, index)) {\n ' + + (obj.loop) + + '\n } '; + } - /** - * Creates a function optimized to search large arrays for a given `value`, - * starting at `fromIndex`, using strict equality for comparisons, i.e. `===`. - * - * @private - * @param {Array} array The array to search. - * @param {Mixed} value The value to search for. - * @param {Number} [fromIndex=0] The index to search from. - * @param {Number} [largeSize=30] The length at which an array is considered large. - * @returns {Boolean} Returns `true`, if `value` is found, else `false`. - */ - function cachedContains(array, fromIndex, largeSize) { - fromIndex || (fromIndex = 0); + } - var length = array.length, - isLarge = (length - fromIndex) >= (largeSize || largeArraySize); + } - if (isLarge) { - var cache = {}, - index = fromIndex - 1; + if (obj.arrays || support.nonEnumArgs) { + __p += '\n}'; + } + __p += + (obj.bottom) + + ';\nreturn result'; - while (++index < length) { - // manually coerce `value` to a string because `hasOwnProperty`, in some - // older versions of Firefox, coerces objects incorrectly - var key = array[index] + ''; - (hasOwnProperty.call(cache, key) ? cache[key] : (cache[key] = [])).push(array[index]); - } - } - return function(value) { - if (isLarge) { - var key = value + ''; - return hasOwnProperty.call(cache, key) && indexOf(cache[key], value) > -1; - } - return indexOf(array, value, fromIndex) > -1; - } - } + return __p + }; - /** - * Used by `_.max` and `_.min` as the default `callback` when a given - * `collection` is a string value. - * - * @private - * @param {String} value The character to inspect. - * @returns {Number} Returns the code unit of given character. - */ - function charAtCallback(value) { - return value.charCodeAt(0); - } + /** Reusable iterator options for `assign` and `defaults` */ + var defaultsIteratorOptions = { + 'args': 'object, source, guard', + 'top': + 'var args = arguments,\n' + + ' argsIndex = 0,\n' + + " argsLength = typeof guard == 'number' ? 2 : args.length;\n" + + 'while (++argsIndex < argsLength) {\n' + + ' iterable = args[argsIndex];\n' + + ' if (iterable && objectTypes[typeof iterable]) {', + 'loop': "if (typeof result[index] == 'undefined') result[index] = iterable[index]", + 'bottom': ' }\n}' + }; - /** - * Used by `sortBy` to compare transformed `collection` values, stable sorting - * them in ascending order. - * - * @private - * @param {Object} a The object to compare to `b`. - * @param {Object} b The object to compare to `a`. - * @returns {Number} Returns the sort order indicator of `1` or `-1`. - */ - function compareAscending(a, b) { - var ai = a.index, - bi = b.index; - - a = a.criteria; - b = b.criteria; - - // ensure a stable sort in V8 and other engines - // http://code.google.com/p/v8/issues/detail?id=90 - if (a !== b) { - if (a > b || typeof a == 'undefined') { - return 1; - } - if (a < b || typeof b == 'undefined') { - return -1; - } - } - return ai < bi ? -1 : 1; - } + /** Reusable iterator options shared by `each`, `forIn`, and `forOwn` */ + var eachIteratorOptions = { + 'args': 'collection, callback, thisArg', + 'top': "callback = callback && typeof thisArg == 'undefined' ? callback : lodash.createCallback(callback, thisArg)", + 'arrays': "typeof length == 'number'", + 'loop': 'if (callback(iterable[index], index, collection) === false) return result' + }; - /** - * Creates a function that, when called, invokes `func` with the `this` binding - * of `thisArg` and prepends any `partialArgs` to the arguments passed to the - * bound function. - * - * @private - * @param {Function|String} func The function to bind or the method name. - * @param {Mixed} [thisArg] The `this` binding of `func`. - * @param {Array} partialArgs An array of arguments to be partially applied. - * @param {Object} [rightIndicator] Used to indicate partially applying arguments from the right. - * @returns {Function} Returns the new bound function. - */ - function createBound(func, thisArg, partialArgs, rightIndicator) { - var isFunc = isFunction(func), - isPartial = !partialArgs, - key = thisArg; - - // juggle arguments - if (isPartial) { - partialArgs = thisArg; - } - if (!isFunc) { - thisArg = func; - } + /** Reusable iterator options for `forIn` and `forOwn` */ + var forOwnIteratorOptions = { + 'top': 'if (!objectTypes[typeof iterable]) return result;\n' + eachIteratorOptions.top, + 'arrays': false + }; - function bound() { - // `Function#bind` spec - // http://es5.github.com/#x15.3.4.5 - var args = arguments, - thisBinding = isPartial ? this : thisArg; + /*--------------------------------------------------------------------------*/ - if (!isFunc) { - func = thisArg[key]; - } - if (partialArgs.length) { - args = args.length - ? (args = slice(args), rightIndicator ? args.concat(partialArgs) : partialArgs.concat(args)) - : partialArgs; + /** + * Creates a function optimized to search large arrays for a given `value`, + * starting at `fromIndex`, using strict equality for comparisons, i.e. `===`. + * + * @private + * @param {Array} array The array to search. + * @param {Mixed} value The value to search for. + * @param {Number} fromIndex The index to search from. + * @param {Number} largeSize The length at which an array is considered large. + * @returns {Boolean} Returns `true`, if `value` is found, else `false`. + */ + function cachedContains(array, fromIndex, largeSize) { + var length = array.length, + isLarge = (length - fromIndex) >= largeSize; + + if (isLarge) { + var cache = {}, + index = fromIndex - 1; + + while (++index < length) { + // manually coerce `value` to a string because `hasOwnProperty`, in some + // older versions of Firefox, coerces objects incorrectly + var key = String(array[index]); + (hasOwnProperty.call(cache, key) ? cache[key] : (cache[key] = [])).push(array[index]); + } } - if (this instanceof bound) { - // ensure `new bound` is an instance of `bound` and `func` - noop.prototype = func.prototype; - thisBinding = new noop; - noop.prototype = null; - - // mimic the constructor's `return` behavior - // http://es5.github.com/#x13.2.2 - var result = func.apply(thisBinding, args); - return isObject(result) ? result : thisBinding; + return function(value) { + if (isLarge) { + var key = String(value); + return hasOwnProperty.call(cache, key) && indexOf(cache[key], value) > -1; + } + return indexOf(array, value, fromIndex) > -1; } - return func.apply(thisBinding, args); } - return bound; - } - /** - * Produces a callback bound to an optional `thisArg`. If `func` is a property - * name, the created callback will return the property value for a given element. - * If `func` is an object, the created callback will return `true` for elements - * that contain the equivalent object properties, otherwise it will return `false`. - * - * @private - * @param {Mixed} [func=identity] The value to convert to a callback. - * @param {Mixed} [thisArg] The `this` binding of the created callback. - * @param {Number} [argCount=3] The number of arguments the callback accepts. - * @returns {Function} Returns a callback function. - */ - function createCallback(func, thisArg, argCount) { - if (func == null) { - return identity; + /** + * Used by `_.max` and `_.min` as the default `callback` when a given + * `collection` is a string value. + * + * @private + * @param {String} value The character to inspect. + * @returns {Number} Returns the code unit of given character. + */ + function charAtCallback(value) { + return value.charCodeAt(0); } - var type = typeof func; - if (type != 'function') { - if (type != 'object') { - return function(object) { - return object[func]; - }; - } - var props = keys(func); - return function(object) { - var length = props.length, - result = false; - while (length--) { - if (!(result = isEqual(object[props[length]], func[props[length]], indicatorObject))) { - break; - } + + /** + * Used by `sortBy` to compare transformed `collection` values, stable sorting + * them in ascending order. + * + * @private + * @param {Object} a The object to compare to `b`. + * @param {Object} b The object to compare to `a`. + * @returns {Number} Returns the sort order indicator of `1` or `-1`. + */ + function compareAscending(a, b) { + var ai = a.index, + bi = b.index; + + a = a.criteria; + b = b.criteria; + + // ensure a stable sort in V8 and other engines + // http://code.google.com/p/v8/issues/detail?id=90 + if (a !== b) { + if (a > b || typeof a == 'undefined') { + return 1; } - return result; - }; + if (a < b || typeof b == 'undefined') { + return -1; + } + } + return ai < bi ? -1 : 1; } - if (typeof thisArg != 'undefined') { - if (argCount === 1) { - return function(value) { - return func.call(thisArg, value); - }; + + /** + * Creates a function that, when called, invokes `func` with the `this` binding + * of `thisArg` and prepends any `partialArgs` to the arguments passed to the + * bound function. + * + * @private + * @param {Function|String} func The function to bind or the method name. + * @param {Mixed} [thisArg] The `this` binding of `func`. + * @param {Array} partialArgs An array of arguments to be partially applied. + * @param {Object} [idicator] Used to indicate binding by key or partially + * applying arguments from the right. + * @returns {Function} Returns the new bound function. + */ + function createBound(func, thisArg, partialArgs, indicator) { + var isFunc = isFunction(func), + isPartial = !partialArgs, + key = thisArg; + + // juggle arguments + if (isPartial) { + var rightIndicator = indicator; + partialArgs = thisArg; } - if (argCount === 2) { - return function(a, b) { - return func.call(thisArg, a, b); - }; + else if (!isFunc) { + if (!indicator) { + throw new TypeError; + } + thisArg = func; } - if (argCount === 4) { - return function(accumulator, value, index, object) { - return func.call(thisArg, accumulator, value, index, object); - }; + + function bound() { + // `Function#bind` spec + // http://es5.github.com/#x15.3.4.5 + var args = arguments, + thisBinding = isPartial ? this : thisArg; + + if (!isFunc) { + func = thisArg[key]; + } + if (partialArgs.length) { + args = args.length + ? (args = slice(args), rightIndicator ? args.concat(partialArgs) : partialArgs.concat(args)) + : partialArgs; + } + if (this instanceof bound) { + // ensure `new bound` is an instance of `func` + noop.prototype = func.prototype; + thisBinding = new noop; + noop.prototype = null; + + // mimic the constructor's `return` behavior + // http://es5.github.com/#x13.2.2 + var result = func.apply(thisBinding, args); + return isObject(result) ? result : thisBinding; + } + return func.apply(thisBinding, args); } - return function(value, index, object) { - return func.call(thisArg, value, index, object); - }; + return bound; } - return func; - } - /** - * Creates compiled iteration functions. - * - * @private - * @param {Object} [options1, options2, ...] The compile options object(s). - * arrays - A string of code to determine if the iterable is an array or array-like. - * useHas - A boolean to specify using `hasOwnProperty` checks in the object loop. - * args - A string of comma separated arguments the iteration function will accept. - * top - A string of code to execute before the iteration branches. - * loop - A string of code to execute in the object loop. - * bottom - A string of code to execute after the iteration branches. - * - * @returns {Function} Returns the compiled function. - */ - function createIterator() { - var data = { - // support properties - 'hasDontEnumBug': hasDontEnumBug, - 'hasEnumPrototype': hasEnumPrototype, - 'isKeysFast': isKeysFast, - 'nonEnumArgs': nonEnumArgs, - 'noCharByIndex': noCharByIndex, - 'shadowed': shadowed, - - // iterator options - 'arrays': 'isArray(iterable)', - 'bottom': '', - 'loop': '', - 'top': '', - 'useHas': true - }; + /** + * Creates compiled iteration functions. + * + * @private + * @param {Object} [options1, options2, ...] The compile options object(s). + * arrays - A string of code to determine if the iterable is an array or array-like. + * useHas - A boolean to specify using `hasOwnProperty` checks in the object loop. + * args - A string of comma separated arguments the iteration function will accept. + * top - A string of code to execute before the iteration branches. + * loop - A string of code to execute in the object loop. + * bottom - A string of code to execute after the iteration branches. + * @returns {Function} Returns the compiled function. + */ + function createIterator() { + var data = { + // data properties + 'shadowedProps': shadowedProps, + // iterator options + 'arrays': 'isArray(iterable)', + 'bottom': '', + 'init': 'iterable', + 'loop': '', + 'top': '', + 'useHas': true, + 'useKeys': !!keys + }; - // merge options into a template data object - for (var object, index = 0; object = arguments[index]; index++) { - for (var key in object) { - data[key] = object[key]; + // merge options into a template data object + for (var object, index = 0; object = arguments[index]; index++) { + for (var key in object) { + data[key] = object[key]; + } } + var args = data.args; + data.firstArg = /^[^,]+/.exec(args)[0]; + + // create the function factory + var factory = Function( + 'hasOwnProperty, isArguments, isArray, isString, keys, ' + + 'lodash, objectTypes', + 'return function(' + args + ') {\n' + iteratorTemplate(data) + '\n}' + ); + // return the compiled function + return factory( + hasOwnProperty, isArguments, isArray, isString, keys, + lodash, objectTypes + ); } - var args = data.args; - data.firstArg = /^[^,]+/.exec(args)[0]; - - // create the function factory - var factory = Function( - 'createCallback, hasOwnProperty, isArguments, isArray, isString, ' + - 'objectTypes, nativeKeys', - 'return function(' + args + ') {\n' + iteratorTemplate(data) + '\n}' - ); - // return the compiled function - return factory( - createCallback, hasOwnProperty, isArguments, isArray, isString, - objectTypes, nativeKeys - ); - } - /** - * A function compiled to iterate `arguments` objects, arrays, objects, and - * strings consistenly across environments, executing the `callback` for each - * element in the `collection`. The `callback` is bound to `thisArg` and invoked - * with three arguments; (value, index|key, collection). Callbacks may exit - * iteration early by explicitly returning `false`. - * - * @private - * @type Function - * @param {Array|Object|String} collection The collection to iterate over. - * @param {Function} [callback=identity] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @returns {Array|Object|String} Returns `collection`. - */ - var each = createIterator(eachIteratorOptions); + /** + * Used by `template` to escape characters for inclusion in compiled + * string literals. + * + * @private + * @param {String} match The matched character to escape. + * @returns {String} Returns the escaped character. + */ + function escapeStringChar(match) { + return '\\' + stringEscapes[match]; + } - /** - * Used by `template` to escape characters for inclusion in compiled - * string literals. - * - * @private - * @param {String} match The matched character to escape. - * @returns {String} Returns the escaped character. - */ - function escapeStringChar(match) { - return '\\' + stringEscapes[match]; - } + /** + * Used by `escape` to convert characters to HTML entities. + * + * @private + * @param {String} match The matched character to escape. + * @returns {String} Returns the escaped character. + */ + function escapeHtmlChar(match) { + return htmlEscapes[match]; + } - /** - * Used by `escape` to convert characters to HTML entities. - * - * @private - * @param {String} match The matched character to escape. - * @returns {String} Returns the escaped character. - */ - function escapeHtmlChar(match) { - return htmlEscapes[match]; - } - - /** - * Checks if `value` is a DOM node in IE < 9. - * - * @private - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is a DOM node, else `false`. - */ - function isNode(value) { - // IE < 9 presents DOM nodes as `Object` objects except they have `toString` - // methods that are `typeof` "string" and still can coerce nodes to strings - return typeof value.toString != 'function' && typeof (value + '') == 'string'; - } - - /** - * A no-operation function. - * - * @private - */ - function noop() { - // no operation performed - } - - /** - * Slices the `collection` from the `start` index up to, but not including, - * the `end` index. - * - * Note: This function is used, instead of `Array#slice`, to support node lists - * in IE < 9 and to ensure dense arrays are returned. - * - * @private - * @param {Array|Object|String} collection The collection to slice. - * @param {Number} start The start index. - * @param {Number} end The end index. - * @returns {Array} Returns the new array. - */ - function slice(array, start, end) { - start || (start = 0); - if (typeof end == 'undefined') { - end = array ? array.length : 0; + /** + * Checks if `value` is a DOM node in IE < 9. + * + * @private + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is a DOM node, else `false`. + */ + function isNode(value) { + // IE < 9 presents DOM nodes as `Object` objects except they have `toString` + // methods that are `typeof` "string" and still can coerce nodes to strings + return typeof value.toString != 'function' && typeof (value + '') == 'string'; } - var index = -1, - length = end - start || 0, - result = Array(length < 0 ? 0 : length); - while (++index < length) { - result[index] = array[start + index]; + /** + * A fast path for creating `lodash` wrapper objects. + * + * @private + * @param {Mixed} value The value to wrap in a `lodash` instance. + * @returns {Object} Returns a `lodash` instance. + */ + function lodashWrapper(value) { + this.__wrapped__ = value; } - return result; - } + // ensure `new lodashWrapper` is an instance of `lodash` + lodashWrapper.prototype = lodash.prototype; - /** - * Used by `unescape` to convert HTML entities to characters. - * - * @private - * @param {String} match The matched character to unescape. - * @returns {String} Returns the unescaped character. - */ - function unescapeHtmlChar(match) { - return htmlUnescapes[match]; - } + /** + * A no-operation function. + * + * @private + */ + function noop() { + // no operation performed + } - /*--------------------------------------------------------------------------*/ + /** + * A fallback implementation of `isPlainObject` that checks if a given `value` + * is an object created by the `Object` constructor, assuming objects created + * by the `Object` constructor have no inherited enumerable properties and that + * there are no `Object.prototype` extensions. + * + * @private + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if `value` is a plain object, else `false`. + */ + function shimIsPlainObject(value) { + // avoid non-objects and false positives for `arguments` objects + var result = false; + if (!(value && toString.call(value) == objectClass) || (!support.argsClass && isArguments(value))) { + return result; + } + // check that the constructor is `Object` (i.e. `Object instanceof Object`) + var ctor = value.constructor; + + if (isFunction(ctor) ? ctor instanceof ctor : (support.nodeClass || !isNode(value))) { + // IE < 9 iterates inherited properties before own properties. If the first + // iterated property is an object's own property then there are no inherited + // enumerable properties. + if (support.ownLast) { + forIn(value, function(value, key, object) { + result = hasOwnProperty.call(object, key); + return false; + }); + return result === true; + } + // In most environments an object's own properties are iterated before + // its inherited properties. If the last iterated property is an object's + // own property then there are no inherited enumerable properties. + forIn(value, function(value, key) { + result = key; + }); + return result === false || hasOwnProperty.call(value, result); + } + return result; + } - /** - * Checks if `value` is an `arguments` object. - * - * @static - * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true`, if the `value` is an `arguments` object, else `false`. - * @example - * - * (function() { return _.isArguments(arguments); })(1, 2, 3); - * // => true - * - * _.isArguments([1, 2, 3]); - * // => false - */ - function isArguments(value) { - return toString.call(value) == argsClass; - } - // fallback for browsers that can't detect `arguments` objects by [[Class]] - if (noArgsClass) { - isArguments = function(value) { - return value ? hasOwnProperty.call(value, 'callee') : false; - }; - } + /** + * Slices the `collection` from the `start` index up to, but not including, + * the `end` index. + * + * Note: This function is used, instead of `Array#slice`, to support node lists + * in IE < 9 and to ensure dense arrays are returned. + * + * @private + * @param {Array|Object|String} collection The collection to slice. + * @param {Number} start The start index. + * @param {Number} end The end index. + * @returns {Array} Returns the new array. + */ + function slice(array, start, end) { + start || (start = 0); + if (typeof end == 'undefined') { + end = array ? array.length : 0; + } + var index = -1, + length = end - start || 0, + result = Array(length < 0 ? 0 : length); - /** - * Iterates over `object`'s own and inherited enumerable properties, executing - * the `callback` for each property. The `callback` is bound to `thisArg` and - * invoked with three arguments; (value, key, object). Callbacks may exit iteration - * early by explicitly returning `false`. - * - * @static - * @memberOf _ - * @type Function - * @category Objects - * @param {Object} object The object to iterate over. - * @param {Function} [callback=identity] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @returns {Object} Returns `object`. - * @example - * - * function Dog(name) { - * this.name = name; - * } - * - * Dog.prototype.bark = function() { - * alert('Woof, woof!'); - * }; - * - * _.forIn(new Dog('Dagny'), function(value, key) { - * alert(key); - * }); - * // => alerts 'name' and 'bark' (order is not guaranteed) - */ - var forIn = createIterator(eachIteratorOptions, forOwnIteratorOptions, { - 'useHas': false - }); + while (++index < length) { + result[index] = array[start + index]; + } + return result; + } - /** - * Iterates over an object's own enumerable properties, executing the `callback` - * for each property. The `callback` is bound to `thisArg` and invoked with three - * arguments; (value, key, object). Callbacks may exit iteration early by explicitly - * returning `false`. - * - * @static - * @memberOf _ - * @type Function - * @category Objects - * @param {Object} object The object to iterate over. - * @param {Function} [callback=identity] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @returns {Object} Returns `object`. - * @example - * - * _.forOwn({ '0': 'zero', '1': 'one', 'length': 2 }, function(num, key) { - * alert(key); - * }); - * // => alerts '0', '1', and 'length' (order is not guaranteed) - */ - var forOwn = createIterator(eachIteratorOptions, forOwnIteratorOptions); + /** + * Used by `unescape` to convert HTML entities to characters. + * + * @private + * @param {String} match The matched character to unescape. + * @returns {String} Returns the unescaped character. + */ + function unescapeHtmlChar(match) { + return htmlUnescapes[match]; + } - /** - * Checks if `value` is an array. - * - * @static - * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true`, if the `value` is an array, else `false`. - * @example - * - * (function() { return _.isArray(arguments); })(); - * // => false - * - * _.isArray([1, 2, 3]); - * // => true - */ - var isArray = nativeIsArray || function(value) { - // `instanceof` may cause a memory leak in IE 7 if `value` is a host object - // http://ajaxian.com/archives/working-aroung-the-instanceof-memory-leak - return (argsAreObjects && value instanceof Array) || toString.call(value) == arrayClass; - }; + /*--------------------------------------------------------------------------*/ - /** - * Creates an array composed of the own enumerable property names of `object`. - * - * @static - * @memberOf _ - * @category Objects - * @param {Object} object The object to inspect. - * @returns {Array} Returns a new array of property names. - * @example - * - * _.keys({ 'one': 1, 'two': 2, 'three': 3 }); - * // => ['one', 'two', 'three'] (order is not guaranteed) - */ - var keys = !nativeKeys ? shimKeys : function(object) { - if (!isObject(object)) { - return []; + /** + * Checks if `value` is an `arguments` object. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if the `value` is an `arguments` object, else `false`. + * @example + * + * (function() { return _.isArguments(arguments); })(1, 2, 3); + * // => true + * + * _.isArguments([1, 2, 3]); + * // => false + */ + function isArguments(value) { + return toString.call(value) == argsClass; } - if ((hasEnumPrototype && typeof object == 'function') || - (nonEnumArgs && object.length && isArguments(object))) { - return shimKeys(object); + // fallback for browsers that can't detect `arguments` objects by [[Class]] + if (!support.argsClass) { + isArguments = function(value) { + return value ? hasOwnProperty.call(value, 'callee') : false; + }; } - return nativeKeys(object); - }; - /** - * A fallback implementation of `isPlainObject` that checks if a given `value` - * is an object created by the `Object` constructor, assuming objects created - * by the `Object` constructor have no inherited enumerable properties and that - * there are no `Object.prototype` extensions. - * - * @private - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true`, if `value` is a plain object, else `false`. - */ - function shimIsPlainObject(value) { - // avoid non-objects and false positives for `arguments` objects - var result = false; - if (!(value && typeof value == 'object') || isArguments(value)) { - return result; - } - // check that the constructor is `Object` (i.e. `Object instanceof Object`) - var ctor = value.constructor; - if ((!isFunction(ctor) && (!noNodeClass || !isNode(value))) || ctor instanceof ctor) { - // IE < 9 iterates inherited properties before own properties. If the first - // iterated property is an object's own property then there are no inherited - // enumerable properties. - if (iteratesOwnLast) { - forIn(value, function(value, key, object) { - result = !hasOwnProperty.call(object, key); - return false; - }); - return result === false; - } - // In most environments an object's own properties are iterated before - // its inherited properties. If the last iterated property is an object's - // own property then there are no inherited enumerable properties. - forIn(value, function(value, key) { - result = key; - }); - return result === false || hasOwnProperty.call(value, result); - } - return result; - } + /** + * Checks if `value` is an array. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if the `value` is an array, else `false`. + * @example + * + * (function() { return _.isArray(arguments); })(); + * // => false + * + * _.isArray([1, 2, 3]); + * // => true + */ + var isArray = nativeIsArray || function(value) { + // `instanceof` may cause a memory leak in IE 7 if `value` is a host object + // http://ajaxian.com/archives/working-aroung-the-instanceof-memory-leak + return (support.argsObject && value instanceof Array) || toString.call(value) == arrayClass; + }; - /** - * A fallback implementation of `Object.keys` that produces an array of the - * given object's own enumerable property names. - * - * @private - * @param {Object} object The object to inspect. - * @returns {Array} Returns a new array of property names. - */ - function shimKeys(object) { - var result = []; - forOwn(object, function(value, key) { - result.push(key); + /** + * A fallback implementation of `Object.keys` that produces an array of the + * given object's own enumerable property names. + * + * @private + * @type Function + * @param {Object} object The object to inspect. + * @returns {Array} Returns a new array of property names. + */ + var shimKeys = createIterator({ + 'args': 'object', + 'init': '[]', + 'top': 'if (!(objectTypes[typeof object])) return result', + 'loop': 'result.push(index)', + 'arrays': false }); - return result; - } - /** - * Used to convert characters to HTML entities: - * - * Though the `>` character is escaped for symmetry, characters like `>` and `/` - * don't require escaping in HTML and have no special meaning unless they're part - * of a tag or an unquoted attribute value. - * http://mathiasbynens.be/notes/ambiguous-ampersands (under "semi-related fun fact") - */ - var htmlEscapes = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''' - }; + /** + * Creates an array composed of the own enumerable property names of `object`. + * + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The object to inspect. + * @returns {Array} Returns a new array of property names. + * @example + * + * _.keys({ 'one': 1, 'two': 2, 'three': 3 }); + * // => ['one', 'two', 'three'] (order is not guaranteed) + */ + var keys = !nativeKeys ? shimKeys : function(object) { + if (!isObject(object)) { + return []; + } + if ((support.enumPrototypes && typeof object == 'function') || + (support.nonEnumArgs && object.length && isArguments(object))) { + return shimKeys(object); + } + return nativeKeys(object); + }; - /** Used to convert HTML entities to characters */ - var htmlUnescapes = invert(htmlEscapes); + /** + * A function compiled to iterate `arguments` objects, arrays, objects, and + * strings consistenly across environments, executing the `callback` for each + * element in the `collection`. The `callback` is bound to `thisArg` and invoked + * with three arguments; (value, index|key, collection). Callbacks may exit + * iteration early by explicitly returning `false`. + * + * @private + * @type Function + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function} [callback=identity] The function called per iteration. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Array|Object|String} Returns `collection`. + */ + var each = createIterator(eachIteratorOptions); - /*--------------------------------------------------------------------------*/ + /** + * Used to convert characters to HTML entities: + * + * Though the `>` character is escaped for symmetry, characters like `>` and `/` + * don't require escaping in HTML and have no special meaning unless they're part + * of a tag or an unquoted attribute value. + * http://mathiasbynens.be/notes/ambiguous-ampersands (under "semi-related fun fact") + */ + var htmlEscapes = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' + }; - /** - * Assigns own enumerable properties of source object(s) to the destination - * object. Subsequent sources will overwrite propery assignments of previous - * sources. If a `callback` function is passed, it will be executed to produce - * the assigned values. The `callback` is bound to `thisArg` and invoked with - * two arguments; (objectValue, sourceValue). - * - * @static - * @memberOf _ - * @type Function - * @alias extend - * @category Objects - * @param {Object} object The destination object. - * @param {Object} [source1, source2, ...] The source objects. - * @param {Function} [callback] The function to customize assigning values. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @returns {Object} Returns the destination object. - * @example - * - * _.assign({ 'name': 'moe' }, { 'age': 40 }); - * // => { 'name': 'moe', 'age': 40 } - * - * var defaults = _.partialRight(_.assign, function(a, b) { - * return typeof a == 'undefined' ? b : a; - * }); - * - * var food = { 'name': 'apple' }; - * defaults(food, { 'name': 'banana', 'type': 'fruit' }); - * // => { 'name': 'apple', 'type': 'fruit' } - */ - var assign = createIterator(defaultsIteratorOptions, { - 'top': - defaultsIteratorOptions.top.replace(';', - ';\n' + - "if (argsLength > 3 && typeof args[argsLength - 2] == 'function') {\n" + - ' var callback = createCallback(args[--argsLength - 1], args[argsLength--], 2);\n' + - "} else if (argsLength > 2 && typeof args[argsLength - 1] == 'function') {\n" + - ' callback = args[--argsLength];\n' + - '}' - ), - 'loop': 'result[index] = callback ? callback(result[index], iterable[index]) : iterable[index]' - }); + /** Used to convert HTML entities to characters */ + var htmlUnescapes = invert(htmlEscapes); - /** - * Creates a clone of `value`. If `deep` is `true`, nested objects will also - * be cloned, otherwise they will be assigned by reference. If a `callback` - * function is passed, it will be executed to produce the cloned values. If - * `callback` returns `undefined`, cloning will be handled by the method instead. - * The `callback` is bound to `thisArg` and invoked with one argument; (value). - * - * @static - * @memberOf _ - * @category Objects - * @param {Mixed} value The value to clone. - * @param {Boolean} [deep=false] A flag to indicate a deep clone. - * @param {Function} [callback] The function to customize cloning values. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @param- {Array} [stackA=[]] Internally used to track traversed source objects. - * @param- {Array} [stackB=[]] Internally used to associate clones with source counterparts. - * @returns {Mixed} Returns the cloned `value`. - * @example - * - * var stooges = [ - * { 'name': 'moe', 'age': 40 }, - * { 'name': 'larry', 'age': 50 } - * ]; - * - * var shallow = _.clone(stooges); - * shallow[0] === stooges[0]; - * // => true - * - * var deep = _.clone(stooges, true); - * deep[0] === stooges[0]; - * // => false - * - * _.mixin({ - * 'clone': _.partialRight(_.clone, function(value) { - * return _.isElement(value) ? value.cloneNode(false) : undefined; - * }) - * }); - * - * var clone = _.clone(document.body); - * clone.childNodes.length; - * // => 0 - */ - function clone(value, deep, callback, thisArg, stackA, stackB) { - var result = value; - - // allows working with "Collections" methods without using their `callback` - // argument, `index|key`, for this method's `callback` - if (typeof deep == 'function') { - thisArg = callback; - callback = deep; - deep = false; - } - if (typeof callback == 'function') { - callback = typeof thisArg == 'undefined' ? callback : createCallback(callback, thisArg, 1); - result = callback(result); + /*--------------------------------------------------------------------------*/ + + /** + * Assigns own enumerable properties of source object(s) to the destination + * object. Subsequent sources will overwrite property assignments of previous + * sources. If a `callback` function is passed, it will be executed to produce + * the assigned values. The `callback` is bound to `thisArg` and invoked with + * two arguments; (objectValue, sourceValue). + * + * @static + * @memberOf _ + * @type Function + * @alias extend + * @category Objects + * @param {Object} object The destination object. + * @param {Object} [source1, source2, ...] The source objects. + * @param {Function} [callback] The function to customize assigning values. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Object} Returns the destination object. + * @example + * + * _.assign({ 'name': 'moe' }, { 'age': 40 }); + * // => { 'name': 'moe', 'age': 40 } + * + * var defaults = _.partialRight(_.assign, function(a, b) { + * return typeof a == 'undefined' ? b : a; + * }); + * + * var food = { 'name': 'apple' }; + * defaults(food, { 'name': 'banana', 'type': 'fruit' }); + * // => { 'name': 'apple', 'type': 'fruit' } + */ + var assign = createIterator(defaultsIteratorOptions, { + 'top': + defaultsIteratorOptions.top.replace(';', + ';\n' + + "if (argsLength > 3 && typeof args[argsLength - 2] == 'function') {\n" + + ' var callback = lodash.createCallback(args[--argsLength - 1], args[argsLength--], 2);\n' + + "} else if (argsLength > 2 && typeof args[argsLength - 1] == 'function') {\n" + + ' callback = args[--argsLength];\n' + + '}' + ), + 'loop': 'result[index] = callback ? callback(result[index], iterable[index]) : iterable[index]' + }); - var done = typeof result != 'undefined'; - if (!done) { + /** + * Creates a clone of `value`. If `deep` is `true`, nested objects will also + * be cloned, otherwise they will be assigned by reference. If a `callback` + * function is passed, it will be executed to produce the cloned values. If + * `callback` returns `undefined`, cloning will be handled by the method instead. + * The `callback` is bound to `thisArg` and invoked with one argument; (value). + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to clone. + * @param {Boolean} [deep=false] A flag to indicate a deep clone. + * @param {Function} [callback] The function to customize cloning values. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @param- {Array} [stackA=[]] Tracks traversed source objects. + * @param- {Array} [stackB=[]] Associates clones with source counterparts. + * @returns {Mixed} Returns the cloned `value`. + * @example + * + * var stooges = [ + * { 'name': 'moe', 'age': 40 }, + * { 'name': 'larry', 'age': 50 } + * ]; + * + * var shallow = _.clone(stooges); + * shallow[0] === stooges[0]; + * // => true + * + * var deep = _.clone(stooges, true); + * deep[0] === stooges[0]; + * // => false + * + * _.mixin({ + * 'clone': _.partialRight(_.clone, function(value) { + * return _.isElement(value) ? value.cloneNode(false) : undefined; + * }) + * }); + * + * var clone = _.clone(document.body); + * clone.childNodes.length; + * // => 0 + */ + function clone(value, deep, callback, thisArg, stackA, stackB) { + var result = value; + + // allows working with "Collections" methods without using their `callback` + // argument, `index|key`, for this method's `callback` + if (typeof deep == 'function') { + thisArg = callback; + callback = deep; + deep = false; + } + if (typeof callback == 'function') { + callback = (typeof thisArg == 'undefined') + ? callback + : lodash.createCallback(callback, thisArg, 1); + + result = callback(result); + if (typeof result != 'undefined') { + return result; + } result = value; } - } - // inspect [[Class]] - var isObj = isObject(result); - if (isObj) { - var className = toString.call(result); - if (!cloneableClasses[className] || (noNodeClass && isNode(result))) { - return result; + // inspect [[Class]] + var isObj = isObject(result); + if (isObj) { + var className = toString.call(result); + if (!cloneableClasses[className] || (!support.nodeClass && isNode(result))) { + return result; + } + var isArr = isArray(result); } - var isArr = isArray(result); - } - // shallow clone - if (!isObj || !deep) { - return isObj && !done - ? (isArr ? slice(result) : assign({}, result)) - : result; - } - var ctor = ctorByClass[className]; - switch (className) { - case boolClass: - case dateClass: - return done ? result : new ctor(+result); - - case numberClass: - case stringClass: - return done ? result : new ctor(result); - - case regexpClass: - return done ? result : ctor(result.source, reFlags.exec(result)); - } - // check for circular references and return corresponding clone - stackA || (stackA = []); - stackB || (stackB = []); - - var length = stackA.length; - while (length--) { - if (stackA[length] == value) { - return stackB[length]; + // shallow clone + if (!isObj || !deep) { + return isObj + ? (isArr ? slice(result) : assign({}, result)) + : result; } - } - // init cloned object - if (!done) { + var ctor = ctorByClass[className]; + switch (className) { + case boolClass: + case dateClass: + return new ctor(+result); + + case numberClass: + case stringClass: + return new ctor(result); + + case regexpClass: + return ctor(result.source, reFlags.exec(result)); + } + // check for circular references and return corresponding clone + stackA || (stackA = []); + stackB || (stackB = []); + + var length = stackA.length; + while (length--) { + if (stackA[length] == value) { + return stackB[length]; + } + } + // init cloned object result = isArr ? ctor(result.length) : {}; // add array properties assigned by `RegExp#exec` @@ -1177,3976 +1166,4273 @@ result.input = value.input; } } + // add the source value to the stack of traversed objects + // and associate it with its clone + stackA.push(value); + stackB.push(result); + + // recursively populate clone (susceptible to call stack limits) + (isArr ? forEach : forOwn)(value, function(objValue, key) { + result[key] = clone(objValue, deep, callback, undefined, stackA, stackB); + }); + + return result; } - // add the source value to the stack of traversed objects - // and associate it with its clone - stackA.push(value); - stackB.push(result); - - // recursively populate clone (susceptible to call stack limits) - (isArr ? forEach : forOwn)(done ? result : value, function(objValue, key) { - result[key] = clone(objValue, deep, callback, undefined, stackA, stackB); - }); - return result; - } + /** + * Creates a deep clone of `value`. If a `callback` function is passed, + * it will be executed to produce the cloned values. If `callback` returns + * `undefined`, cloning will be handled by the method instead. The `callback` + * is bound to `thisArg` and invoked with one argument; (value). + * + * Note: This function is loosely based on the structured clone algorithm. Functions + * and DOM nodes are **not** cloned. The enumerable properties of `arguments` objects and + * objects created by constructors other than `Object` are cloned to plain `Object` objects. + * See http://www.w3.org/TR/html5/infrastructure.html#internal-structured-cloning-algorithm. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to deep clone. + * @param {Function} [callback] The function to customize cloning values. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Mixed} Returns the deep cloned `value`. + * @example + * + * var stooges = [ + * { 'name': 'moe', 'age': 40 }, + * { 'name': 'larry', 'age': 50 } + * ]; + * + * var deep = _.cloneDeep(stooges); + * deep[0] === stooges[0]; + * // => false + * + * var view = { + * 'label': 'docs', + * 'node': element + * }; + * + * var clone = _.cloneDeep(view, function(value) { + * return _.isElement(value) ? value.cloneNode(true) : undefined; + * }); + * + * clone.node == view.node; + * // => false + */ + function cloneDeep(value, callback, thisArg) { + return clone(value, true, callback, thisArg); + } - /** - * Creates a deep clone of `value`. If a `callback` function is passed, it will - * be executed to produce the cloned values. If `callback` returns the value it - * was passed, cloning will be handled by the method instead. The `callback` is - * bound to `thisArg` and invoked with one argument; (value). - * - * Note: This function is loosely based on the structured clone algorithm. Functions - * and DOM nodes are **not** cloned. The enumerable properties of `arguments` objects and - * objects created by constructors other than `Object` are cloned to plain `Object` objects. - * See http://www.w3.org/TR/html5/infrastructure.html#internal-structured-cloning-algorithm. - * - * @static - * @memberOf _ - * @category Objects - * @param {Mixed} value The value to deep clone. - * @param {Function} [callback] The function to customize cloning values. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @returns {Mixed} Returns the deep cloned `value`. - * @example - * - * var stooges = [ - * { 'name': 'moe', 'age': 40 }, - * { 'name': 'larry', 'age': 50 } - * ]; - * - * var deep = _.cloneDeep(stooges); - * deep[0] === stooges[0]; - * // => false - * - * var view = { - * 'label': 'docs', - * 'node': element - * }; - * - * var clone = _.cloneDeep(view, function(value) { - * return _.isElement(value) ? value.cloneNode(true) : value; - * }); - * - * clone.node == view.node; - * // => false - */ - function cloneDeep(value, callback, thisArg) { - return clone(value, true, callback, thisArg); - } + /** + * Assigns own enumerable properties of source object(s) to the destination + * object for all destination properties that resolve to `undefined`. Once a + * property is set, additional defaults of the same property will be ignored. + * + * @static + * @memberOf _ + * @type Function + * @category Objects + * @param {Object} object The destination object. + * @param {Object} [source1, source2, ...] The source objects. + * @param- {Object} [guard] Allows working with `_.reduce` without using its + * callback's `key` and `object` arguments as sources. + * @returns {Object} Returns the destination object. + * @example + * + * var food = { 'name': 'apple' }; + * _.defaults(food, { 'name': 'banana', 'type': 'fruit' }); + * // => { 'name': 'apple', 'type': 'fruit' } + */ + var defaults = createIterator(defaultsIteratorOptions); - /** - * Assigns own enumerable properties of source object(s) to the destination - * object for all destination properties that resolve to `undefined`. Once a - * property is set, additional defaults of the same property will be ignored. - * - * @static - * @memberOf _ - * @type Function - * @category Objects - * @param {Object} object The destination object. - * @param {Object} [source1, source2, ...] The source objects. - * @param- {Object} [guard] Internally used to allow working with `_.reduce` - * without using its callback's `key` and `object` arguments as sources. - * @returns {Object} Returns the destination object. - * @example - * - * var food = { 'name': 'apple' }; - * _.defaults(food, { 'name': 'banana', 'type': 'fruit' }); - * // => { 'name': 'apple', 'type': 'fruit' } - */ - var defaults = createIterator(defaultsIteratorOptions); + /** + * This method is similar to `_.find`, except that it returns the key of the + * element that passes the callback check, instead of the element itself. + * + * @static + * @memberOf _ + * @category Objects + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Mixed} Returns the key of the found element, else `undefined`. + * @example + * + * _.findKey({ 'a': 1, 'b': 2, 'c': 3, 'd': 4 }, function(num) { return num % 2 == 0; }); + * // => 'b' + */ + function findKey(collection, callback, thisArg) { + var result; + callback = lodash.createCallback(callback, thisArg); + forOwn(collection, function(value, key, collection) { + if (callback(value, key, collection)) { + result = key; + return false; + } + }); + return result; + } - /** - * Creates a sorted array of all enumerable properties, own and inherited, - * of `object` that have function values. - * - * @static - * @memberOf _ - * @alias methods - * @category Objects - * @param {Object} object The object to inspect. - * @returns {Array} Returns a new array of property names that have function values. - * @example - * - * _.functions(_); - * // => ['all', 'any', 'bind', 'bindAll', 'clone', 'compact', 'compose', ...] - */ - function functions(object) { - var result = []; - forIn(object, function(value, key) { - if (isFunction(value)) { - result.push(key); - } + /** + * Iterates over `object`'s own and inherited enumerable properties, executing + * the `callback` for each property. The `callback` is bound to `thisArg` and + * invoked with three arguments; (value, key, object). Callbacks may exit iteration + * early by explicitly returning `false`. + * + * @static + * @memberOf _ + * @type Function + * @category Objects + * @param {Object} object The object to iterate over. + * @param {Function} [callback=identity] The function called per iteration. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Object} Returns `object`. + * @example + * + * function Dog(name) { + * this.name = name; + * } + * + * Dog.prototype.bark = function() { + * alert('Woof, woof!'); + * }; + * + * _.forIn(new Dog('Dagny'), function(value, key) { + * alert(key); + * }); + * // => alerts 'name' and 'bark' (order is not guaranteed) + */ + var forIn = createIterator(eachIteratorOptions, forOwnIteratorOptions, { + 'useHas': false }); - return result.sort(); - } - /** - * Checks if the specified object `property` exists and is a direct property, - * instead of an inherited property. - * - * @static - * @memberOf _ - * @category Objects - * @param {Object} object The object to check. - * @param {String} property The property to check for. - * @returns {Boolean} Returns `true` if key is a direct property, else `false`. - * @example - * - * _.has({ 'a': 1, 'b': 2, 'c': 3 }, 'b'); - * // => true - */ - function has(object, property) { - return object ? hasOwnProperty.call(object, property) : false; - } + /** + * Iterates over an object's own enumerable properties, executing the `callback` + * for each property. The `callback` is bound to `thisArg` and invoked with three + * arguments; (value, key, object). Callbacks may exit iteration early by explicitly + * returning `false`. + * + * @static + * @memberOf _ + * @type Function + * @category Objects + * @param {Object} object The object to iterate over. + * @param {Function} [callback=identity] The function called per iteration. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Object} Returns `object`. + * @example + * + * _.forOwn({ '0': 'zero', '1': 'one', 'length': 2 }, function(num, key) { + * alert(key); + * }); + * // => alerts '0', '1', and 'length' (order is not guaranteed) + */ + var forOwn = createIterator(eachIteratorOptions, forOwnIteratorOptions); - /** - * Creates an object composed of the inverted keys and values of the given `object`. - * - * @static - * @memberOf _ - * @category Objects - * @param {Object} object The object to invert. - * @returns {Object} Returns the created inverted object. - * @example - * - * _.invert({ 'first': 'moe', 'second': 'larry' }); - * // => { 'moe': 'first', 'larry': 'second' } (order is not guaranteed) - */ - function invert(object) { - var index = -1, - props = keys(object), - length = props.length, - result = {}; - - while (++index < length) { - var key = props[index]; - result[object[key]] = key; + /** + * Creates a sorted array of all enumerable properties, own and inherited, + * of `object` that have function values. + * + * @static + * @memberOf _ + * @alias methods + * @category Objects + * @param {Object} object The object to inspect. + * @returns {Array} Returns a new array of property names that have function values. + * @example + * + * _.functions(_); + * // => ['all', 'any', 'bind', 'bindAll', 'clone', 'compact', 'compose', ...] + */ + function functions(object) { + var result = []; + forIn(object, function(value, key) { + if (isFunction(value)) { + result.push(key); + } + }); + return result.sort(); } - return result; - } - - /** - * Checks if `value` is a boolean value. - * - * @static - * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true`, if the `value` is a boolean value, else `false`. - * @example - * - * _.isBoolean(null); - * // => false - */ - function isBoolean(value) { - return value === true || value === false || toString.call(value) == boolClass; - } - /** - * Checks if `value` is a date. - * - * @static - * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true`, if the `value` is a date, else `false`. - * @example - * - * _.isDate(new Date); - * // => true - */ - function isDate(value) { - return value instanceof Date || toString.call(value) == dateClass; - } + /** + * Checks if the specified object `property` exists and is a direct property, + * instead of an inherited property. + * + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The object to check. + * @param {String} property The property to check for. + * @returns {Boolean} Returns `true` if key is a direct property, else `false`. + * @example + * + * _.has({ 'a': 1, 'b': 2, 'c': 3 }, 'b'); + * // => true + */ + function has(object, property) { + return object ? hasOwnProperty.call(object, property) : false; + } - /** - * Checks if `value` is a DOM element. - * - * @static - * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true`, if the `value` is a DOM element, else `false`. - * @example - * - * _.isElement(document.body); - * // => true - */ - function isElement(value) { - return value ? value.nodeType === 1 : false; - } + /** + * Creates an object composed of the inverted keys and values of the given `object`. + * + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The object to invert. + * @returns {Object} Returns the created inverted object. + * @example + * + * _.invert({ 'first': 'moe', 'second': 'larry' }); + * // => { 'moe': 'first', 'larry': 'second' } + */ + function invert(object) { + var index = -1, + props = keys(object), + length = props.length, + result = {}; - /** - * Checks if `value` is empty. Arrays, strings, or `arguments` objects with a - * length of `0` and objects with no own enumerable properties are considered - * "empty". - * - * @static - * @memberOf _ - * @category Objects - * @param {Array|Object|String} value The value to inspect. - * @returns {Boolean} Returns `true`, if the `value` is empty, else `false`. - * @example - * - * _.isEmpty([1, 2, 3]); - * // => false - * - * _.isEmpty({}); - * // => true - * - * _.isEmpty(''); - * // => true - */ - function isEmpty(value) { - var result = true; - if (!value) { + while (++index < length) { + var key = props[index]; + result[object[key]] = key; + } return result; } - var className = toString.call(value), - length = value.length; - if ((className == arrayClass || className == stringClass || - className == argsClass || (noArgsClass && isArguments(value))) || - (className == objectClass && typeof length == 'number' && isFunction(value.splice))) { - return !length; + /** + * Checks if `value` is a boolean value. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if the `value` is a boolean value, else `false`. + * @example + * + * _.isBoolean(null); + * // => false + */ + function isBoolean(value) { + return value === true || value === false || toString.call(value) == boolClass; } - forOwn(value, function() { - return (result = false); - }); - return result; - } - /** - * Performs a deep comparison between two values to determine if they are - * equivalent to each other. If `callback` is passed, it will be executed to - * compare values. If `callback` returns `undefined`, comparisons will be handled - * by the method instead. The `callback` is bound to `thisArg` and invoked with - * two arguments; (a, b). - * - * @static - * @memberOf _ - * @category Objects - * @param {Mixed} a The value to compare. - * @param {Mixed} b The other value to compare. - * @param {Function} [callback] The function to customize comparing values. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @param- {Object} [stackA=[]] Internally used track traversed `a` objects. - * @param- {Object} [stackB=[]] Internally used track traversed `b` objects. - * @returns {Boolean} Returns `true`, if the values are equvalent, else `false`. - * @example - * - * var moe = { 'name': 'moe', 'age': 40 }; - * var copy = { 'name': 'moe', 'age': 40 }; - * - * moe == copy; - * // => false - * - * _.isEqual(moe, copy); - * // => true - * - * var words = ['hello', 'goodbye']; - * var otherWords = ['hi', 'goodbye']; - * - * _.isEqual(words, otherWords, function(a, b) { - * var reGreet = /^(?:hello|hi)$/i, - * aGreet = _.isString(a) && reGreet.test(a), - * bGreet = _.isString(b) && reGreet.test(b); - * - * return (aGreet || bGreet) ? (aGreet == bGreet) : undefined; - * }); - * // => true - */ - function isEqual(a, b, callback, thisArg, stackA, stackB) { - // used to indicate that when comparing objects, `a` has at least the properties of `b` - var whereIndicator = callback === indicatorObject; - if (callback && !whereIndicator) { - callback = typeof thisArg == 'undefined' ? callback : createCallback(callback, thisArg, 2); - var result = callback(a, b); - if (typeof result != 'undefined') { - return !!result; - } - } - // exit early for identical values - if (a === b) { - // treat `+0` vs. `-0` as not equal - return a !== 0 || (1 / a == 1 / b); - } - var type = typeof a, - otherType = typeof b; - - // exit early for unlike primitive values - if (a === a && - (!a || (type != 'function' && type != 'object')) && - (!b || (otherType != 'function' && otherType != 'object'))) { - return false; - } - // exit early for `null` and `undefined`, avoiding ES3's Function#call behavior - // http://es5.github.com/#x15.3.4.4 - if (a == null || b == null) { - return a === b; + /** + * Checks if `value` is a date. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if the `value` is a date, else `false`. + * @example + * + * _.isDate(new Date); + * // => true + */ + function isDate(value) { + return value instanceof Date || toString.call(value) == dateClass; } - // compare [[Class]] names - var className = toString.call(a), - otherClass = toString.call(b); - if (className == argsClass) { - className = objectClass; - } - if (otherClass == argsClass) { - otherClass = objectClass; - } - if (className != otherClass) { - return false; + /** + * Checks if `value` is a DOM element. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if the `value` is a DOM element, else `false`. + * @example + * + * _.isElement(document.body); + * // => true + */ + function isElement(value) { + return value ? value.nodeType === 1 : false; } - switch (className) { - case boolClass: - case dateClass: - // coerce dates and booleans to numbers, dates to milliseconds and booleans - // to `1` or `0`, treating invalid dates coerced to `NaN` as not equal - return +a == +b; - - case numberClass: - // treat `NaN` vs. `NaN` as equal - return a != +a - ? b != +b - // but treat `+0` vs. `-0` as not equal - : (a == 0 ? (1 / a == 1 / b) : a == +b); - - case regexpClass: - case stringClass: - // coerce regexes to strings (http://es5.github.com/#x15.10.6.4) - // treat string primitives and their corresponding object instances as equal - return a == b + ''; + + /** + * Checks if `value` is empty. Arrays, strings, or `arguments` objects with a + * length of `0` and objects with no own enumerable properties are considered + * "empty". + * + * @static + * @memberOf _ + * @category Objects + * @param {Array|Object|String} value The value to inspect. + * @returns {Boolean} Returns `true`, if the `value` is empty, else `false`. + * @example + * + * _.isEmpty([1, 2, 3]); + * // => false + * + * _.isEmpty({}); + * // => true + * + * _.isEmpty(''); + * // => true + */ + function isEmpty(value) { + var result = true; + if (!value) { + return result; + } + var className = toString.call(value), + length = value.length; + + if ((className == arrayClass || className == stringClass || + (support.argsClass ? className == argsClass : isArguments(value))) || + (className == objectClass && typeof length == 'number' && isFunction(value.splice))) { + return !length; + } + forOwn(value, function() { + return (result = false); + }); + return result; } - var isArr = className == arrayClass; - if (!isArr) { - // unwrap any `lodash` wrapped values - if (a.__wrapped__ || b.__wrapped__) { - return isEqual(a.__wrapped__ || a, b.__wrapped__ || b, callback, thisArg, stackA, stackB); + + /** + * Performs a deep comparison between two values to determine if they are + * equivalent to each other. If `callback` is passed, it will be executed to + * compare values. If `callback` returns `undefined`, comparisons will be handled + * by the method instead. The `callback` is bound to `thisArg` and invoked with + * two arguments; (a, b). + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} a The value to compare. + * @param {Mixed} b The other value to compare. + * @param {Function} [callback] The function to customize comparing values. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @param- {Array} [stackA=[]] Tracks traversed `a` objects. + * @param- {Array} [stackB=[]] Tracks traversed `b` objects. + * @returns {Boolean} Returns `true`, if the values are equivalent, else `false`. + * @example + * + * var moe = { 'name': 'moe', 'age': 40 }; + * var copy = { 'name': 'moe', 'age': 40 }; + * + * moe == copy; + * // => false + * + * _.isEqual(moe, copy); + * // => true + * + * var words = ['hello', 'goodbye']; + * var otherWords = ['hi', 'goodbye']; + * + * _.isEqual(words, otherWords, function(a, b) { + * var reGreet = /^(?:hello|hi)$/i, + * aGreet = _.isString(a) && reGreet.test(a), + * bGreet = _.isString(b) && reGreet.test(b); + * + * return (aGreet || bGreet) ? (aGreet == bGreet) : undefined; + * }); + * // => true + */ + function isEqual(a, b, callback, thisArg, stackA, stackB) { + // used to indicate that when comparing objects, `a` has at least the properties of `b` + var whereIndicator = callback === indicatorObject; + if (callback && !whereIndicator) { + callback = (typeof thisArg == 'undefined') + ? callback + : lodash.createCallback(callback, thisArg, 2); + + var result = callback(a, b); + if (typeof result != 'undefined') { + return !!result; + } } - // exit for functions and DOM nodes - if (className != objectClass || (noNodeClass && (isNode(a) || isNode(b)))) { + // exit early for identical values + if (a === b) { + // treat `+0` vs. `-0` as not equal + return a !== 0 || (1 / a == 1 / b); + } + var type = typeof a, + otherType = typeof b; + + // exit early for unlike primitive values + if (a === a && + (!a || (type != 'function' && type != 'object')) && + (!b || (otherType != 'function' && otherType != 'object'))) { return false; } - // in older versions of Opera, `arguments` objects have `Array` constructors - var ctorA = !argsAreObjects && isArguments(a) ? Object : a.constructor, - ctorB = !argsAreObjects && isArguments(b) ? Object : b.constructor; - - // non `Object` object instances with different constructors are not equal - if (ctorA != ctorB && !( - isFunction(ctorA) && ctorA instanceof ctorA && - isFunction(ctorB) && ctorB instanceof ctorB - )) { + // exit early for `null` and `undefined`, avoiding ES3's Function#call behavior + // http://es5.github.com/#x15.3.4.4 + if (a == null || b == null) { + return a === b; + } + // compare [[Class]] names + var className = toString.call(a), + otherClass = toString.call(b); + + if (className == argsClass) { + className = objectClass; + } + if (otherClass == argsClass) { + otherClass = objectClass; + } + if (className != otherClass) { return false; } - } - // assume cyclic structures are equal - // the algorithm for detecting cyclic structures is adapted from ES 5.1 - // section 15.12.3, abstract operation `JO` (http://es5.github.com/#x15.12.3) - stackA || (stackA = []); - stackB || (stackB = []); - - var length = stackA.length; - while (length--) { - if (stackA[length] == a) { - return stackB[length] == b; + switch (className) { + case boolClass: + case dateClass: + // coerce dates and booleans to numbers, dates to milliseconds and booleans + // to `1` or `0`, treating invalid dates coerced to `NaN` as not equal + return +a == +b; + + case numberClass: + // treat `NaN` vs. `NaN` as equal + return (a != +a) + ? b != +b + // but treat `+0` vs. `-0` as not equal + : (a == 0 ? (1 / a == 1 / b) : a == +b); + + case regexpClass: + case stringClass: + // coerce regexes to strings (http://es5.github.com/#x15.10.6.4) + // treat string primitives and their corresponding object instances as equal + return a == String(b); } - } - var size = 0; - result = true; + var isArr = className == arrayClass; + if (!isArr) { + // unwrap any `lodash` wrapped values + if (hasOwnProperty.call(a, '__wrapped__ ') || hasOwnProperty.call(b, '__wrapped__')) { + return isEqual(a.__wrapped__ || a, b.__wrapped__ || b, callback, thisArg, stackA, stackB); + } + // exit for functions and DOM nodes + if (className != objectClass || (!support.nodeClass && (isNode(a) || isNode(b)))) { + return false; + } + // in older versions of Opera, `arguments` objects have `Array` constructors + var ctorA = !support.argsObject && isArguments(a) ? Object : a.constructor, + ctorB = !support.argsObject && isArguments(b) ? Object : b.constructor; + + // non `Object` object instances with different constructors are not equal + if (ctorA != ctorB && !( + isFunction(ctorA) && ctorA instanceof ctorA && + isFunction(ctorB) && ctorB instanceof ctorB + )) { + return false; + } + } + // assume cyclic structures are equal + // the algorithm for detecting cyclic structures is adapted from ES 5.1 + // section 15.12.3, abstract operation `JO` (http://es5.github.com/#x15.12.3) + stackA || (stackA = []); + stackB || (stackB = []); - // add `a` and `b` to the stack of traversed objects - stackA.push(a); - stackB.push(b); + var length = stackA.length; + while (length--) { + if (stackA[length] == a) { + return stackB[length] == b; + } + } + var size = 0; + result = true; - // recursively compare objects and arrays (susceptible to call stack limits) - if (isArr) { - length = a.length; - size = b.length; + // add `a` and `b` to the stack of traversed objects + stackA.push(a); + stackB.push(b); - // compare lengths to determine if a deep comparison is necessary - result = size == a.length; - if (!result && !whereIndicator) { - return result; - } - // deep compare the contents, ignoring non-numeric properties - while (size--) { - var index = length, - value = b[size]; - - if (whereIndicator) { - while (index--) { - if ((result = isEqual(a[index], value, callback, thisArg, stackA, stackB))) { - break; + // recursively compare objects and arrays (susceptible to call stack limits) + if (isArr) { + length = a.length; + size = b.length; + + // compare lengths to determine if a deep comparison is necessary + result = size == a.length; + if (!result && !whereIndicator) { + return result; + } + // deep compare the contents, ignoring non-numeric properties + while (size--) { + var index = length, + value = b[size]; + + if (whereIndicator) { + while (index--) { + if ((result = isEqual(a[index], value, callback, thisArg, stackA, stackB))) { + break; + } } + } else if (!(result = isEqual(a[size], value, callback, thisArg, stackA, stackB))) { + break; } - } else if (!(result = isEqual(a[size], value, callback, thisArg, stackA, stackB))) { - break; } + return result; + } + // deep compare objects using `forIn`, instead of `forOwn`, to avoid `Object.keys` + // which, in this case, is more costly + forIn(b, function(value, key, b) { + if (hasOwnProperty.call(b, key)) { + // count the number of properties. + size++; + // deep compare each property value. + return (result = hasOwnProperty.call(a, key) && isEqual(a[key], value, callback, thisArg, stackA, stackB)); + } + }); + + if (result && !whereIndicator) { + // ensure both objects have the same number of properties + forIn(a, function(value, key, a) { + if (hasOwnProperty.call(a, key)) { + // `size` will be `-1` if `a` has more properties than `b` + return (result = --size > -1); + } + }); } return result; } - // deep compare objects using `forIn`, instead of `forOwn`, to avoid `Object.keys` - // which, in this case, is more costly - forIn(b, function(value, key, b) { - if (hasOwnProperty.call(b, key)) { - // count the number of properties. - size++; - // deep compare each property value. - return (result = hasOwnProperty.call(a, key) && isEqual(a[key], value, callback, thisArg, stackA, stackB)); - } - }); - if (result && !whereIndicator) { - // ensure both objects have the same number of properties - forIn(a, function(value, key, a) { - if (hasOwnProperty.call(a, key)) { - // `size` will be `-1` if `a` has more properties than `b` - return (result = --size > -1); - } - }); + /** + * Checks if `value` is, or can be coerced to, a finite number. + * + * Note: This is not the same as native `isFinite`, which will return true for + * booleans and empty strings. See http://es5.github.com/#x15.1.2.5. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if the `value` is finite, else `false`. + * @example + * + * _.isFinite(-101); + * // => true + * + * _.isFinite('10'); + * // => true + * + * _.isFinite(true); + * // => false + * + * _.isFinite(''); + * // => false + * + * _.isFinite(Infinity); + * // => false + */ + function isFinite(value) { + return nativeIsFinite(value) && !nativeIsNaN(parseFloat(value)); } - return result; - } - /** - * Checks if `value` is, or can be coerced to, a finite number. - * - * Note: This is not the same as native `isFinite`, which will return true for - * booleans and empty strings. See http://es5.github.com/#x15.1.2.5. - * - * @static - * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true`, if the `value` is finite, else `false`. - * @example - * - * _.isFinite(-101); - * // => true - * - * _.isFinite('10'); - * // => true - * - * _.isFinite(true); - * // => false - * - * _.isFinite(''); - * // => false - * - * _.isFinite(Infinity); - * // => false - */ - function isFinite(value) { - return nativeIsFinite(value) && !nativeIsNaN(parseFloat(value)); - } + /** + * Checks if `value` is a function. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if the `value` is a function, else `false`. + * @example + * + * _.isFunction(_); + * // => true + */ + function isFunction(value) { + return typeof value == 'function'; + } + // fallback for older versions of Chrome and Safari + if (isFunction(/x/)) { + isFunction = function(value) { + return value instanceof Function || toString.call(value) == funcClass; + }; + } - /** - * Checks if `value` is a function. - * - * @static - * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true`, if the `value` is a function, else `false`. - * @example - * - * _.isFunction(_); - * // => true - */ - function isFunction(value) { - return typeof value == 'function'; - } - // fallback for older versions of Chrome and Safari - if (isFunction(/x/)) { - isFunction = function(value) { - return value instanceof Function || toString.call(value) == funcClass; - }; - } + /** + * Checks if `value` is the language type of Object. + * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if the `value` is an object, else `false`. + * @example + * + * _.isObject({}); + * // => true + * + * _.isObject([1, 2, 3]); + * // => true + * + * _.isObject(1); + * // => false + */ + function isObject(value) { + // check if the value is the ECMAScript language type of Object + // http://es5.github.com/#x8 + // and avoid a V8 bug + // http://code.google.com/p/v8/issues/detail?id=2291 + return value ? objectTypes[typeof value] : false; + } - /** - * Checks if `value` is the language type of Object. - * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) - * - * @static - * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true`, if the `value` is an object, else `false`. - * @example - * - * _.isObject({}); - * // => true - * - * _.isObject([1, 2, 3]); - * // => true - * - * _.isObject(1); - * // => false - */ - function isObject(value) { - // check if the value is the ECMAScript language type of Object - // http://es5.github.com/#x8 - // and avoid a V8 bug - // http://code.google.com/p/v8/issues/detail?id=2291 - return value ? objectTypes[typeof value] : false; - } + /** + * Checks if `value` is `NaN`. + * + * Note: This is not the same as native `isNaN`, which will return `true` for + * `undefined` and other values. See http://es5.github.com/#x15.1.2.4. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if the `value` is `NaN`, else `false`. + * @example + * + * _.isNaN(NaN); + * // => true + * + * _.isNaN(new Number(NaN)); + * // => true + * + * isNaN(undefined); + * // => true + * + * _.isNaN(undefined); + * // => false + */ + function isNaN(value) { + // `NaN` as a primitive is the only value that is not equal to itself + // (perform the [[Class]] check first to avoid errors with some host objects in IE) + return isNumber(value) && value != +value + } - /** - * Checks if `value` is `NaN`. - * - * Note: This is not the same as native `isNaN`, which will return `true` for - * `undefined` and other values. See http://es5.github.com/#x15.1.2.4. - * - * @static - * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true`, if the `value` is `NaN`, else `false`. - * @example - * - * _.isNaN(NaN); - * // => true - * - * _.isNaN(new Number(NaN)); - * // => true - * - * isNaN(undefined); - * // => true - * - * _.isNaN(undefined); - * // => false - */ - function isNaN(value) { - // `NaN` as a primitive is the only value that is not equal to itself - // (perform the [[Class]] check first to avoid errors with some host objects in IE) - return isNumber(value) && value != +value - } + /** + * Checks if `value` is `null`. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if the `value` is `null`, else `false`. + * @example + * + * _.isNull(null); + * // => true + * + * _.isNull(undefined); + * // => false + */ + function isNull(value) { + return value === null; + } - /** - * Checks if `value` is `null`. - * - * @static - * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true`, if the `value` is `null`, else `false`. - * @example - * - * _.isNull(null); - * // => true - * - * _.isNull(undefined); - * // => false - */ - function isNull(value) { - return value === null; - } + /** + * Checks if `value` is a number. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if the `value` is a number, else `false`. + * @example + * + * _.isNumber(8.4 * 5); + * // => true + */ + function isNumber(value) { + return typeof value == 'number' || toString.call(value) == numberClass; + } - /** - * Checks if `value` is a number. - * - * @static - * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true`, if the `value` is a number, else `false`. - * @example - * - * _.isNumber(8.4 * 5); - * // => true - */ - function isNumber(value) { - return typeof value == 'number' || toString.call(value) == numberClass; - } - - /** - * Checks if a given `value` is an object created by the `Object` constructor. - * - * @static - * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true`, if `value` is a plain object, else `false`. - * @example - * - * function Stooge(name, age) { - * this.name = name; - * this.age = age; - * } - * - * _.isPlainObject(new Stooge('moe', 40)); - * // => false - * - * _.isPlainObject([1, 2, 3]); - * // => false - * - * _.isPlainObject({ 'name': 'moe', 'age': 40 }); - * // => true - */ - var isPlainObject = !getPrototypeOf ? shimIsPlainObject : function(value) { - if (!(value && typeof value == 'object')) { - return false; - } - var valueOf = value.valueOf, - objProto = typeof valueOf == 'function' && (objProto = getPrototypeOf(valueOf)) && getPrototypeOf(objProto); + /** + * Checks if a given `value` is an object created by the `Object` constructor. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if `value` is a plain object, else `false`. + * @example + * + * function Stooge(name, age) { + * this.name = name; + * this.age = age; + * } + * + * _.isPlainObject(new Stooge('moe', 40)); + * // => false + * + * _.isPlainObject([1, 2, 3]); + * // => false + * + * _.isPlainObject({ 'name': 'moe', 'age': 40 }); + * // => true + */ + var isPlainObject = !getPrototypeOf ? shimIsPlainObject : function(value) { + if (!(value && toString.call(value) == objectClass) || (!support.argsClass && isArguments(value))) { + return false; + } + var valueOf = value.valueOf, + objProto = typeof valueOf == 'function' && (objProto = getPrototypeOf(valueOf)) && getPrototypeOf(objProto); - return objProto - ? value == objProto || (getPrototypeOf(value) == objProto && !isArguments(value)) - : shimIsPlainObject(value); - }; + return objProto + ? (value == objProto || getPrototypeOf(value) == objProto) + : shimIsPlainObject(value); + }; - /** - * Checks if `value` is a regular expression. - * - * @static - * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true`, if the `value` is a regular expression, else `false`. - * @example - * - * _.isRegExp(/moe/); - * // => true - */ - function isRegExp(value) { - return value instanceof RegExp || toString.call(value) == regexpClass; - } + /** + * Checks if `value` is a regular expression. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if the `value` is a regular expression, else `false`. + * @example + * + * _.isRegExp(/moe/); + * // => true + */ + function isRegExp(value) { + return value instanceof RegExp || toString.call(value) == regexpClass; + } - /** - * Checks if `value` is a string. - * - * @static - * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true`, if the `value` is a string, else `false`. - * @example - * - * _.isString('moe'); - * // => true - */ - function isString(value) { - return typeof value == 'string' || toString.call(value) == stringClass; - } + /** + * Checks if `value` is a string. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if the `value` is a string, else `false`. + * @example + * + * _.isString('moe'); + * // => true + */ + function isString(value) { + return typeof value == 'string' || toString.call(value) == stringClass; + } - /** - * Checks if `value` is `undefined`. - * - * @static - * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true`, if the `value` is `undefined`, else `false`. - * @example - * - * _.isUndefined(void 0); - * // => true - */ - function isUndefined(value) { - return typeof value == 'undefined'; - } + /** + * Checks if `value` is `undefined`. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if the `value` is `undefined`, else `false`. + * @example + * + * _.isUndefined(void 0); + * // => true + */ + function isUndefined(value) { + return typeof value == 'undefined'; + } - /** - * Recursively merges own enumerable properties of the source object(s), that - * don't resolve to `undefined`, into the destination object. Subsequent sources - * will overwrite propery assignments of previous sources. If a `callback` function - * is passed, it will be executed to produce the merged values of the destination - * and source properties. If `callback` returns `undefined`, merging will be - * handled by the method instead. The `callback` is bound to `thisArg` and - * invoked with two arguments; (objectValue, sourceValue). - * - * @static - * @memberOf _ - * @category Objects - * @param {Object} object The destination object. - * @param {Object} [source1, source2, ...] The source objects. - * @param {Function} [callback] The function to customize merging properties. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @param- {Object} [deepIndicator] Internally used to indicate that `stackA` - * and `stackB` are arrays of traversed objects instead of source objects. - * @param- {Array} [stackA=[]] Internally used to track traversed source objects. - * @param- {Array} [stackB=[]] Internally used to associate values with their - * source counterparts. - * @returns {Object} Returns the destination object. - * @example - * - * var names = { - * 'stooges': [ - * { 'name': 'moe' }, - * { 'name': 'larry' } - * ] - * }; - * - * var ages = { - * 'stooges': [ - * { 'age': 40 }, - * { 'age': 50 } - * ] - * }; - * - * _.merge(names, ages); - * // => { 'stooges': [{ 'name': 'moe', 'age': 40 }, { 'name': 'larry', 'age': 50 }] } - * - * var food = { - * 'fruits': ['apple'], - * 'vegetables': ['beet'] - * }; - * - * var otherFood = { - * 'fruits': ['banana'], - * 'vegetables': ['carrot'] - * }; - * - * _.merge(food, otherFood, function(a, b) { - * return _.isArray(a) ? a.concat(b) : undefined; - * }); - * // => { 'fruits': ['apple', 'banana'], 'vegetables': ['beet', 'carrot] } - */ - function merge(object, source, deepIndicator) { - var args = arguments, - index = 0, - length = 2; + /** + * Recursively merges own enumerable properties of the source object(s), that + * don't resolve to `undefined`, into the destination object. Subsequent sources + * will overwrite property assignments of previous sources. If a `callback` function + * is passed, it will be executed to produce the merged values of the destination + * and source properties. If `callback` returns `undefined`, merging will be + * handled by the method instead. The `callback` is bound to `thisArg` and + * invoked with two arguments; (objectValue, sourceValue). + * + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The destination object. + * @param {Object} [source1, source2, ...] The source objects. + * @param {Function} [callback] The function to customize merging properties. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @param- {Object} [deepIndicator] Indicates that `stackA` and `stackB` are + * arrays of traversed objects, instead of source objects. + * @param- {Array} [stackA=[]] Tracks traversed source objects. + * @param- {Array} [stackB=[]] Associates values with source counterparts. + * @returns {Object} Returns the destination object. + * @example + * + * var names = { + * 'stooges': [ + * { 'name': 'moe' }, + * { 'name': 'larry' } + * ] + * }; + * + * var ages = { + * 'stooges': [ + * { 'age': 40 }, + * { 'age': 50 } + * ] + * }; + * + * _.merge(names, ages); + * // => { 'stooges': [{ 'name': 'moe', 'age': 40 }, { 'name': 'larry', 'age': 50 }] } + * + * var food = { + * 'fruits': ['apple'], + * 'vegetables': ['beet'] + * }; + * + * var otherFood = { + * 'fruits': ['banana'], + * 'vegetables': ['carrot'] + * }; + * + * _.merge(food, otherFood, function(a, b) { + * return _.isArray(a) ? a.concat(b) : undefined; + * }); + * // => { 'fruits': ['apple', 'banana'], 'vegetables': ['beet', 'carrot] } + */ + function merge(object, source, deepIndicator) { + var args = arguments, + index = 0, + length = 2; - if (!isObject(object)) { - return object; - } - if (deepIndicator === indicatorObject) { - var callback = args[3], - stackA = args[4], - stackB = args[5]; - } else { - stackA = []; - stackB = []; - - // allows working with `_.reduce` and `_.reduceRight` without - // using their `callback` arguments, `index|key` and `collection` - if (typeof deepIndicator != 'number') { - length = args.length; + if (!isObject(object)) { + return object; } - if (length > 3 && typeof args[length - 2] == 'function') { - callback = createCallback(args[--length - 1], args[length--], 2); - } else if (length > 2 && typeof args[length - 1] == 'function') { - callback = args[--length]; + if (deepIndicator === indicatorObject) { + var callback = args[3], + stackA = args[4], + stackB = args[5]; + } else { + stackA = []; + stackB = []; + + // allows working with `_.reduce` and `_.reduceRight` without + // using their `callback` arguments, `index|key` and `collection` + if (typeof deepIndicator != 'number') { + length = args.length; + } + if (length > 3 && typeof args[length - 2] == 'function') { + callback = lodash.createCallback(args[--length - 1], args[length--], 2); + } else if (length > 2 && typeof args[length - 1] == 'function') { + callback = args[--length]; + } } - } - while (++index < length) { - (isArray(args[index]) ? forEach : forOwn)(args[index], function(source, key) { - var found, - isArr, - result = source, - value = object[key]; - - if (source && ((isArr = isArray(source)) || isPlainObject(source))) { - // avoid merging previously merged cyclic sources - var stackLength = stackA.length; - while (stackLength--) { - if ((found = stackA[stackLength] == source)) { - value = stackB[stackLength]; - break; + while (++index < length) { + (isArray(args[index]) ? forEach : forOwn)(args[index], function(source, key) { + var found, + isArr, + result = source, + value = object[key]; + + if (source && ((isArr = isArray(source)) || isPlainObject(source))) { + // avoid merging previously merged cyclic sources + var stackLength = stackA.length; + while (stackLength--) { + if ((found = stackA[stackLength] == source)) { + value = stackB[stackLength]; + break; + } } - } - if (!found) { - value = isArr - ? (isArray(value) ? value : []) - : (isPlainObject(value) ? value : {}); + if (!found) { + value = isArr + ? (isArray(value) ? value : []) + : (isPlainObject(value) ? value : {}); + + if (callback) { + result = callback(value, source); + if (typeof result != 'undefined') { + value = result; + } + } + // add `source` and associated `value` to the stack of traversed objects + stackA.push(source); + stackB.push(value); + // recursively merge objects and arrays (susceptible to call stack limits) + if (!callback) { + value = merge(value, source, indicatorObject, callback, stackA, stackB); + } + } + } + else { if (callback) { result = callback(value, source); - if (typeof result != 'undefined') { - value = result; + if (typeof result == 'undefined') { + result = source; } } - // add `source` and associated `value` to the stack of traversed objects - stackA.push(source); - stackB.push(value); - - // recursively merge objects and arrays (susceptible to call stack limits) - if (!callback) { - value = merge(value, source, indicatorObject, callback, stackA, stackB); - } - } - } - else { - if (callback) { - result = callback(value, source); - if (typeof result == 'undefined') { - result = source; + if (typeof result != 'undefined') { + value = result; } } - if (typeof result != 'undefined') { - value = result; - } - } - object[key] = value; - }); - } - return object; - } - - /** - * Creates a shallow clone of `object` excluding the specified properties. - * Property names may be specified as individual arguments or as arrays of - * property names. If a `callback` function is passed, it will be executed - * for each property in the `object`, omitting the properties `callback` - * returns truthy for. The `callback` is bound to `thisArg` and invoked - * with three arguments; (value, key, object). - * - * @static - * @memberOf _ - * @category Objects - * @param {Object} object The source object. - * @param {Function|String} callback|[prop1, prop2, ...] The properties to omit - * or the function called per iteration. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @returns {Object} Returns an object without the omitted properties. - * @example - * - * _.omit({ 'name': 'moe', 'age': 40 }, 'age'); - * // => { 'name': 'moe' } - * - * _.omit({ 'name': 'moe', 'age': 40 }, function(value) { - * return typeof value == 'number'; - * }); - * // => { 'name': 'moe' } - */ - function omit(object, callback, thisArg) { - var isFunc = typeof callback == 'function', - result = {}; - - if (isFunc) { - callback = createCallback(callback, thisArg); - } else { - var props = concat.apply(arrayRef, arguments); - } - forIn(object, function(value, key, object) { - if (isFunc - ? !callback(value, key, object) - : indexOf(props, key, 1) < 0 - ) { - result[key] = value; + object[key] = value; + }); } - }); - return result; - } - - /** - * Creates a two dimensional array of the given object's key-value pairs, - * i.e. `[[key1, value1], [key2, value2]]`. - * - * @static - * @memberOf _ - * @category Objects - * @param {Object} object The object to inspect. - * @returns {Array} Returns new array of key-value pairs. - * @example - * - * _.pairs({ 'moe': 30, 'larry': 40 }); - * // => [['moe', 30], ['larry', 40]] (order is not guaranteed) - */ - function pairs(object) { - var index = -1, - props = keys(object), - length = props.length, - result = Array(length); - - while (++index < length) { - var key = props[index]; - result[index] = [key, object[key]]; + return object; } - return result; - } - /** - * Creates a shallow clone of `object` composed of the specified properties. - * Property names may be specified as individual arguments or as arrays of property - * names. If `callback` is passed, it will be executed for each property in the - * `object`, picking the properties `callback` returns truthy for. The `callback` - * is bound to `thisArg` and invoked with three arguments; (value, key, object). - * - * @static - * @memberOf _ - * @category Objects - * @param {Object} object The source object. - * @param {Array|Function|String} callback|[prop1, prop2, ...] The function called - * per iteration or properties to pick, either as individual arguments or arrays. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @returns {Object} Returns an object composed of the picked properties. - * @example - * - * _.pick({ 'name': 'moe', '_userid': 'moe1' }, 'name'); - * // => { 'name': 'moe' } - * - * _.pick({ 'name': 'moe', '_userid': 'moe1' }, function(value, key) { - * return key.charAt(0) != '_'; - * }); - * // => { 'name': 'moe' } - */ - function pick(object, callback, thisArg) { - var result = {}; - if (typeof callback != 'function') { - var index = 0, - props = concat.apply(arrayRef, arguments), - length = isObject(object) ? props.length : 0; + /** + * Creates a shallow clone of `object` excluding the specified properties. + * Property names may be specified as individual arguments or as arrays of + * property names. If a `callback` function is passed, it will be executed + * for each property in the `object`, omitting the properties `callback` + * returns truthy for. The `callback` is bound to `thisArg` and invoked + * with three arguments; (value, key, object). + * + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The source object. + * @param {Function|String} callback|[prop1, prop2, ...] The properties to omit + * or the function called per iteration. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Object} Returns an object without the omitted properties. + * @example + * + * _.omit({ 'name': 'moe', 'age': 40 }, 'age'); + * // => { 'name': 'moe' } + * + * _.omit({ 'name': 'moe', 'age': 40 }, function(value) { + * return typeof value == 'number'; + * }); + * // => { 'name': 'moe' } + */ + function omit(object, callback, thisArg) { + var isFunc = typeof callback == 'function', + result = {}; - while (++index < length) { - var key = props[index]; - if (key in object) { - result[key] = object[key]; - } + if (isFunc) { + callback = lodash.createCallback(callback, thisArg); + } else { + var props = concat.apply(arrayRef, arguments); } - } else { - callback = createCallback(callback, thisArg); forIn(object, function(value, key, object) { - if (callback(value, key, object)) { + if (isFunc + ? !callback(value, key, object) + : indexOf(props, key, 1) < 0 + ) { result[key] = value; } }); + return result; } - return result; - } - - /** - * Creates an array composed of the own enumerable property values of `object`. - * - * @static - * @memberOf _ - * @category Objects - * @param {Object} object The object to inspect. - * @returns {Array} Returns a new array of property values. - * @example - * - * _.values({ 'one': 1, 'two': 2, 'three': 3 }); - * // => [1, 2, 3] - */ - function values(object) { - var index = -1, - props = keys(object), - length = props.length, - result = Array(length); - - while (++index < length) { - result[index] = object[props[index]]; - } - return result; - } - /*--------------------------------------------------------------------------*/ + /** + * Creates a two dimensional array of the given object's key-value pairs, + * i.e. `[[key1, value1], [key2, value2]]`. + * + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The object to inspect. + * @returns {Array} Returns new array of key-value pairs. + * @example + * + * _.pairs({ 'moe': 30, 'larry': 40 }); + * // => [['moe', 30], ['larry', 40]] (order is not guaranteed) + */ + function pairs(object) { + var index = -1, + props = keys(object), + length = props.length, + result = Array(length); - /** - * Creates an array of elements from the specified indexes, or keys, of the - * `collection`. Indexes may be specified as individual arguments or as arrays - * of indexes. - * - * @static - * @memberOf _ - * @category Collections - * @param {Array|Object|String} collection The collection to iterate over. - * @param {Array|Number|String} [index1, index2, ...] The indexes of - * `collection` to retrieve, either as individual arguments or arrays. - * @returns {Array} Returns a new array of elements corresponding to the - * provided indexes. - * @example - * - * _.at(['a', 'b', 'c', 'd', 'e'], [0, 2, 4]); - * // => ['a', 'c', 'e'] - * - * _.at(['moe', 'larry', 'curly'], 0, 2); - * // => ['moe', 'curly'] - */ - function at(collection) { - var index = -1, - props = concat.apply(arrayRef, slice(arguments, 1)), - length = props.length, - result = Array(length); - - if (noCharByIndex && isString(collection)) { - collection = collection.split(''); - } - while(++index < length) { - result[index] = collection[props[index]]; + while (++index < length) { + var key = props[index]; + result[index] = [key, object[key]]; + } + return result; } - return result; - } - /** - * Checks if a given `target` element is present in a `collection` using strict - * equality for comparisons, i.e. `===`. If `fromIndex` is negative, it is used - * as the offset from the end of the collection. - * - * @static - * @memberOf _ - * @alias include - * @category Collections - * @param {Array|Object|String} collection The collection to iterate over. - * @param {Mixed} target The value to check for. - * @param {Number} [fromIndex=0] The index to search from. - * @returns {Boolean} Returns `true` if the `target` element is found, else `false`. - * @example - * - * _.contains([1, 2, 3], 1); - * // => true - * - * _.contains([1, 2, 3], 1, 2); - * // => false - * - * _.contains({ 'name': 'moe', 'age': 40 }, 'moe'); - * // => true - * - * _.contains('curly', 'ur'); - * // => true - */ - function contains(collection, target, fromIndex) { - var index = -1, - length = collection ? collection.length : 0, - result = false; - - fromIndex = (fromIndex < 0 ? nativeMax(0, length + fromIndex) : fromIndex) || 0; - if (typeof length == 'number') { - result = (isString(collection) - ? collection.indexOf(target, fromIndex) - : indexOf(collection, target, fromIndex) - ) > -1; - } else { - each(collection, function(value) { - if (++index >= fromIndex) { - return !(result = value === target); + /** + * Creates a shallow clone of `object` composed of the specified properties. + * Property names may be specified as individual arguments or as arrays of property + * names. If `callback` is passed, it will be executed for each property in the + * `object`, picking the properties `callback` returns truthy for. The `callback` + * is bound to `thisArg` and invoked with three arguments; (value, key, object). + * + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The source object. + * @param {Array|Function|String} callback|[prop1, prop2, ...] The function called + * per iteration or properties to pick, either as individual arguments or arrays. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Object} Returns an object composed of the picked properties. + * @example + * + * _.pick({ 'name': 'moe', '_userid': 'moe1' }, 'name'); + * // => { 'name': 'moe' } + * + * _.pick({ 'name': 'moe', '_userid': 'moe1' }, function(value, key) { + * return key.charAt(0) != '_'; + * }); + * // => { 'name': 'moe' } + */ + function pick(object, callback, thisArg) { + var result = {}; + if (typeof callback != 'function') { + var index = 0, + props = concat.apply(arrayRef, arguments), + length = isObject(object) ? props.length : 0; + + while (++index < length) { + var key = props[index]; + if (key in object) { + result[key] = object[key]; + } } - }); + } else { + callback = lodash.createCallback(callback, thisArg); + forIn(object, function(value, key, object) { + if (callback(value, key, object)) { + result[key] = value; + } + }); + } + return result; } - return result; - } - - /** - * Creates an object composed of keys returned from running each element of the - * `collection` through the given `callback`. The corresponding value of each key - * is the number of times the key was returned by the `callback`. The `callback` - * is bound to `thisArg` and invoked with three arguments; (value, index|key, collection). - * - * If a property name is passed for `callback`, the created "_.pluck" style - * callback will return the property value of the given element. - * - * If an object is passed for `callback`, the created "_.where" style callback - * will return `true` for elements that have the propeties of the given object, - * else `false`. - * - * @static - * @memberOf _ - * @category Collections - * @param {Array|Object|String} collection The collection to iterate over. - * @param {Function|Object|String} [callback=identity] The function called per - * iteration. If a property name or object is passed, it will be used to create - * a "_.pluck" or "_.where" style callback, respectively. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @returns {Object} Returns the composed aggregate object. - * @example - * - * _.countBy([4.3, 6.1, 6.4], function(num) { return Math.floor(num); }); - * // => { '4': 1, '6': 2 } - * - * _.countBy([4.3, 6.1, 6.4], function(num) { return this.floor(num); }, Math); - * // => { '4': 1, '6': 2 } - * - * _.countBy(['one', 'two', 'three'], 'length'); - * // => { '3': 2, '5': 1 } - */ - function countBy(collection, callback, thisArg) { - var result = {}; - callback = createCallback(callback, thisArg); - - forEach(collection, function(value, key, collection) { - key = callback(value, key, collection) + ''; - (hasOwnProperty.call(result, key) ? result[key]++ : result[key] = 1); - }); - return result; - } - /** - * Checks if the `callback` returns a truthy value for **all** elements of a - * `collection`. The `callback` is bound to `thisArg` and invoked with three - * arguments; (value, index|key, collection). - * - * If a property name is passed for `callback`, the created "_.pluck" style - * callback will return the property value of the given element. - * - * If an object is passed for `callback`, the created "_.where" style callback - * will return `true` for elements that have the propeties of the given object, - * else `false`. - * - * @static - * @memberOf _ - * @alias all - * @category Collections - * @param {Array|Object|String} collection The collection to iterate over. - * @param {Function|Object|String} [callback=identity] The function called per - * iteration. If a property name or object is passed, it will be used to create - * a "_.pluck" or "_.where" style callback, respectively. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @returns {Boolean} Returns `true` if all elements pass the callback check, - * else `false`. - * @example - * - * _.every([true, 1, null, 'yes'], Boolean); - * // => false - * - * var stooges = [ - * { 'name': 'moe', 'age': 40 }, - * { 'name': 'larry', 'age': 50 } - * ]; - * - * // using "_.pluck" callback shorthand - * _.every(stooges, 'age'); - * // => true - * - * // using "_.where" callback shorthand - * _.every(stooges, { 'age': 50 }); - * // => false - */ - function every(collection, callback, thisArg) { - var result = true; - callback = createCallback(callback, thisArg); - - if (isArray(collection)) { + /** + * Creates an array composed of the own enumerable property values of `object`. + * + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The object to inspect. + * @returns {Array} Returns a new array of property values. + * @example + * + * _.values({ 'one': 1, 'two': 2, 'three': 3 }); + * // => [1, 2, 3] (order is not guaranteed) + */ + function values(object) { var index = -1, - length = collection.length; + props = keys(object), + length = props.length, + result = Array(length); while (++index < length) { - if (!(result = !!callback(collection[index], index, collection))) { - break; - } + result[index] = object[props[index]]; } - } else { - each(collection, function(value, index, collection) { - return (result = !!callback(value, index, collection)); - }); + return result; } - return result; - } - /** - * Examines each element in a `collection`, returning an array of all elements - * the `callback` returns truthy for. The `callback` is bound to `thisArg` and - * invoked with three arguments; (value, index|key, collection). - * - * If a property name is passed for `callback`, the created "_.pluck" style - * callback will return the property value of the given element. - * - * If an object is passed for `callback`, the created "_.where" style callback - * will return `true` for elements that have the propeties of the given object, - * else `false`. - * - * @static - * @memberOf _ - * @alias select - * @category Collections - * @param {Array|Object|String} collection The collection to iterate over. - * @param {Function|Object|String} [callback=identity] The function called per - * iteration. If a property name or object is passed, it will be used to create - * a "_.pluck" or "_.where" style callback, respectively. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @returns {Array} Returns a new array of elements that passed the callback check. - * @example - * - * var evens = _.filter([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; }); - * // => [2, 4, 6] - * - * var food = [ - * { 'name': 'apple', 'organic': false, 'type': 'fruit' }, - * { 'name': 'carrot', 'organic': true, 'type': 'vegetable' } - * ]; - * - * // using "_.pluck" callback shorthand - * _.filter(food, 'organic'); - * // => [{ 'name': 'carrot', 'organic': true, 'type': 'vegetable' }] - * - * // using "_.where" callback shorthand - * _.filter(food, { 'type': 'fruit' }); - * // => [{ 'name': 'apple', 'organic': false, 'type': 'fruit' }] - */ - function filter(collection, callback, thisArg) { - var result = []; - callback = createCallback(callback, thisArg); + /*--------------------------------------------------------------------------*/ - if (isArray(collection)) { + /** + * Creates an array of elements from the specified indexes, or keys, of the + * `collection`. Indexes may be specified as individual arguments or as arrays + * of indexes. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Array|Number|String} [index1, index2, ...] The indexes of + * `collection` to retrieve, either as individual arguments or arrays. + * @returns {Array} Returns a new array of elements corresponding to the + * provided indexes. + * @example + * + * _.at(['a', 'b', 'c', 'd', 'e'], [0, 2, 4]); + * // => ['a', 'c', 'e'] + * + * _.at(['moe', 'larry', 'curly'], 0, 2); + * // => ['moe', 'curly'] + */ + function at(collection) { var index = -1, - length = collection.length; + props = concat.apply(arrayRef, slice(arguments, 1)), + length = props.length, + result = Array(length); - while (++index < length) { - var value = collection[index]; - if (callback(value, index, collection)) { - result.push(value); - } + if (support.unindexedChars && isString(collection)) { + collection = collection.split(''); } - } else { - each(collection, function(value, index, collection) { - if (callback(value, index, collection)) { - result.push(value); - } - }); + while(++index < length) { + result[index] = collection[props[index]]; + } + return result; } - return result; - } - /** - * Examines each element in a `collection`, returning the first that the `callback` - * returns truthy for. The `callback` is bound to `thisArg` and invoked with three - * arguments; (value, index|key, collection). - * - * If a property name is passed for `callback`, the created "_.pluck" style - * callback will return the property value of the given element. - * - * If an object is passed for `callback`, the created "_.where" style callback - * will return `true` for elements that have the propeties of the given object, - * else `false`. - * - * @static - * @memberOf _ - * @alias detect - * @category Collections - * @param {Array|Object|String} collection The collection to iterate over. - * @param {Function|Object|String} [callback=identity] The function called per - * iteration. If a property name or object is passed, it will be used to create - * a "_.pluck" or "_.where" style callback, respectively. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @returns {Mixed} Returns the element that passed the callback check, - * else `undefined`. - * @example - * - * var even = _.find([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; }); - * // => 2 - * - * var food = [ - * { 'name': 'apple', 'organic': false, 'type': 'fruit' }, - * { 'name': 'banana', 'organic': true, 'type': 'fruit' }, - * { 'name': 'beet', 'organic': false, 'type': 'vegetable' }, - * { 'name': 'carrot', 'organic': true, 'type': 'vegetable' } - * ]; - * - * // using "_.where" callback shorthand - * var veggie = _.find(food, { 'type': 'vegetable' }); - * // => { 'name': 'beet', 'organic': false, 'type': 'vegetable' } - * - * // using "_.pluck" callback shorthand - * var healthy = _.find(food, 'organic'); - * // => { 'name': 'banana', 'organic': true, 'type': 'fruit' } - */ - function find(collection, callback, thisArg) { - var result; - callback = createCallback(callback, thisArg); - - forEach(collection, function(value, index, collection) { - if (callback(value, index, collection)) { - result = value; - return false; - } - }); - return result; - } - - /** - * Iterates over a `collection`, executing the `callback` for each element in - * the `collection`. The `callback` is bound to `thisArg` and invoked with three - * arguments; (value, index|key, collection). Callbacks may exit iteration early - * by explicitly returning `false`. - * - * @static - * @memberOf _ - * @alias each - * @category Collections - * @param {Array|Object|String} collection The collection to iterate over. - * @param {Function} [callback=identity] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @returns {Array|Object|String} Returns `collection`. - * @example - * - * _([1, 2, 3]).forEach(alert).join(','); - * // => alerts each number and returns '1,2,3' - * - * _.forEach({ 'one': 1, 'two': 2, 'three': 3 }, alert); - * // => alerts each number value (order is not guaranteed) - */ - function forEach(collection, callback, thisArg) { - if (callback && typeof thisArg == 'undefined' && isArray(collection)) { + /** + * Checks if a given `target` element is present in a `collection` using strict + * equality for comparisons, i.e. `===`. If `fromIndex` is negative, it is used + * as the offset from the end of the collection. + * + * @static + * @memberOf _ + * @alias include + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Mixed} target The value to check for. + * @param {Number} [fromIndex=0] The index to search from. + * @returns {Boolean} Returns `true` if the `target` element is found, else `false`. + * @example + * + * _.contains([1, 2, 3], 1); + * // => true + * + * _.contains([1, 2, 3], 1, 2); + * // => false + * + * _.contains({ 'name': 'moe', 'age': 40 }, 'moe'); + * // => true + * + * _.contains('curly', 'ur'); + * // => true + */ + function contains(collection, target, fromIndex) { var index = -1, - length = collection.length; - - while (++index < length) { - if (callback(collection[index], index, collection) === false) { - break; - } + length = collection ? collection.length : 0, + result = false; + + fromIndex = (fromIndex < 0 ? nativeMax(0, length + fromIndex) : fromIndex) || 0; + if (typeof length == 'number') { + result = (isString(collection) + ? collection.indexOf(target, fromIndex) + : indexOf(collection, target, fromIndex) + ) > -1; + } else { + each(collection, function(value) { + if (++index >= fromIndex) { + return !(result = value === target); + } + }); } - } else { - each(collection, callback, thisArg); + return result; } - return collection; - } - - /** - * Creates an object composed of keys returned from running each element of the - * `collection` through the `callback`. The corresponding value of each key is - * an array of elements passed to `callback` that returned the key. The `callback` - * is bound to `thisArg` and invoked with three arguments; (value, index|key, collection). - * - * If a property name is passed for `callback`, the created "_.pluck" style - * callback will return the property value of the given element. - * - * If an object is passed for `callback`, the created "_.where" style callback - * will return `true` for elements that have the propeties of the given object, - * else `false` - * - * @static - * @memberOf _ - * @category Collections - * @param {Array|Object|String} collection The collection to iterate over. - * @param {Function|Object|String} [callback=identity] The function called per - * iteration. If a property name or object is passed, it will be used to create - * a "_.pluck" or "_.where" style callback, respectively. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @returns {Object} Returns the composed aggregate object. - * @example - * - * _.groupBy([4.2, 6.1, 6.4], function(num) { return Math.floor(num); }); - * // => { '4': [4.2], '6': [6.1, 6.4] } - * - * _.groupBy([4.2, 6.1, 6.4], function(num) { return this.floor(num); }, Math); - * // => { '4': [4.2], '6': [6.1, 6.4] } - * - * // using "_.pluck" callback shorthand - * _.groupBy(['one', 'two', 'three'], 'length'); - * // => { '3': ['one', 'two'], '5': ['three'] } - */ - function groupBy(collection, callback, thisArg) { - var result = {}; - callback = createCallback(callback, thisArg); - forEach(collection, function(value, key, collection) { - key = callback(value, key, collection) + ''; - (hasOwnProperty.call(result, key) ? result[key] : result[key] = []).push(value); - }); - return result; - } - - /** - * Invokes the method named by `methodName` on each element in the `collection`, - * returning an array of the results of each invoked method. Additional arguments - * will be passed to each invoked method. If `methodName` is a function, it will - * be invoked for, and `this` bound to, each element in the `collection`. - * - * @static - * @memberOf _ - * @category Collections - * @param {Array|Object|String} collection The collection to iterate over. - * @param {Function|String} methodName The name of the method to invoke or - * the function invoked per iteration. - * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the method with. - * @returns {Array} Returns a new array of the results of each invoked method. - * @example - * - * _.invoke([[5, 1, 7], [3, 2, 1]], 'sort'); - * // => [[1, 5, 7], [1, 2, 3]] - * - * _.invoke([123, 456], String.prototype.split, ''); - * // => [['1', '2', '3'], ['4', '5', '6']] - */ - function invoke(collection, methodName) { - var args = slice(arguments, 2), - index = -1, - isFunc = typeof methodName == 'function', - length = collection ? collection.length : 0, - result = Array(typeof length == 'number' ? length : 0); - - forEach(collection, function(value) { - result[++index] = (isFunc ? methodName : value[methodName]).apply(value, args); - }); - return result; - } - - /** - * Creates an array of values by running each element in the `collection` - * through the `callback`. The `callback` is bound to `thisArg` and invoked with - * three arguments; (value, index|key, collection). - * - * If a property name is passed for `callback`, the created "_.pluck" style - * callback will return the property value of the given element. - * - * If an object is passed for `callback`, the created "_.where" style callback - * will return `true` for elements that have the propeties of the given object, - * else `false`. - * - * @static - * @memberOf _ - * @alias collect - * @category Collections - * @param {Array|Object|String} collection The collection to iterate over. - * @param {Function|Object|String} [callback=identity] The function called per - * iteration. If a property name or object is passed, it will be used to create - * a "_.pluck" or "_.where" style callback, respectively. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @returns {Array} Returns a new array of the results of each `callback` execution. - * @example - * - * _.map([1, 2, 3], function(num) { return num * 3; }); - * // => [3, 6, 9] - * - * _.map({ 'one': 1, 'two': 2, 'three': 3 }, function(num) { return num * 3; }); - * // => [3, 6, 9] (order is not guaranteed) - * - * var stooges = [ - * { 'name': 'moe', 'age': 40 }, - * { 'name': 'larry', 'age': 50 } - * ]; - * - * // using "_.pluck" callback shorthand - * _.map(stooges, 'name'); - * // => ['moe', 'larry'] - */ - function map(collection, callback, thisArg) { - var index = -1, - length = collection ? collection.length : 0, - result = Array(typeof length == 'number' ? length : 0); + /** + * Creates an object composed of keys returned from running each element of the + * `collection` through the given `callback`. The corresponding value of each key + * is the number of times the key was returned by the `callback`. The `callback` + * is bound to `thisArg` and invoked with three arguments; (value, index|key, collection). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Object} Returns the composed aggregate object. + * @example + * + * _.countBy([4.3, 6.1, 6.4], function(num) { return Math.floor(num); }); + * // => { '4': 1, '6': 2 } + * + * _.countBy([4.3, 6.1, 6.4], function(num) { return this.floor(num); }, Math); + * // => { '4': 1, '6': 2 } + * + * _.countBy(['one', 'two', 'three'], 'length'); + * // => { '3': 2, '5': 1 } + */ + function countBy(collection, callback, thisArg) { + var result = {}; + callback = lodash.createCallback(callback, thisArg); - callback = createCallback(callback, thisArg); - if (isArray(collection)) { - while (++index < length) { - result[index] = callback(collection[index], index, collection); - } - } else { - each(collection, function(value, key, collection) { - result[++index] = callback(value, key, collection); + forEach(collection, function(value, key, collection) { + key = String(callback(value, key, collection)); + (hasOwnProperty.call(result, key) ? result[key]++ : result[key] = 1); }); + return result; } - return result; - } - /** - * Retrieves the maximum value of an `array`. If `callback` is passed, - * it will be executed for each value in the `array` to generate the - * criterion by which the value is ranked. The `callback` is bound to - * `thisArg` and invoked with three arguments; (value, index, collection). - * - * If a property name is passed for `callback`, the created "_.pluck" style - * callback will return the property value of the given element. - * - * If an object is passed for `callback`, the created "_.where" style callback - * will return `true` for elements that have the propeties of the given object, - * else `false`. - * - * @static - * @memberOf _ - * @category Collections - * @param {Array|Object|String} collection The collection to iterate over. - * @param {Function|Object|String} [callback=identity] The function called per - * iteration. If a property name or object is passed, it will be used to create - * a "_.pluck" or "_.where" style callback, respectively. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @returns {Mixed} Returns the maximum value. - * @example - * - * _.max([4, 2, 8, 6]); - * // => 8 - * - * var stooges = [ - * { 'name': 'moe', 'age': 40 }, - * { 'name': 'larry', 'age': 50 } - * ]; - * - * _.max(stooges, function(stooge) { return stooge.age; }); - * // => { 'name': 'larry', 'age': 50 }; - * - * // using "_.pluck" callback shorthand - * _.max(stooges, 'age'); - * // => { 'name': 'larry', 'age': 50 }; - */ - function max(collection, callback, thisArg) { - var computed = -Infinity, - result = computed; + /** + * Checks if the `callback` returns a truthy value for **all** elements of a + * `collection`. The `callback` is bound to `thisArg` and invoked with three + * arguments; (value, index|key, collection). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @alias all + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Boolean} Returns `true` if all elements pass the callback check, + * else `false`. + * @example + * + * _.every([true, 1, null, 'yes'], Boolean); + * // => false + * + * var stooges = [ + * { 'name': 'moe', 'age': 40 }, + * { 'name': 'larry', 'age': 50 } + * ]; + * + * // using "_.pluck" callback shorthand + * _.every(stooges, 'age'); + * // => true + * + * // using "_.where" callback shorthand + * _.every(stooges, { 'age': 50 }); + * // => false + */ + function every(collection, callback, thisArg) { + var result = true; + callback = lodash.createCallback(callback, thisArg); - if (!callback && isArray(collection)) { - var index = -1, - length = collection.length; + if (isArray(collection)) { + var index = -1, + length = collection.length; - while (++index < length) { - var value = collection[index]; - if (value > result) { - result = value; + while (++index < length) { + if (!(result = !!callback(collection[index], index, collection))) { + break; + } } + } else { + each(collection, function(value, index, collection) { + return (result = !!callback(value, index, collection)); + }); } - } else { - callback = !callback && isString(collection) - ? charAtCallback - : createCallback(callback, thisArg); - - each(collection, function(value, index, collection) { - var current = callback(value, index, collection); - if (current > computed) { - computed = current; - result = value; + return result; + } + + /** + * Examines each element in a `collection`, returning an array of all elements + * the `callback` returns truthy for. The `callback` is bound to `thisArg` and + * invoked with three arguments; (value, index|key, collection). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @alias select + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Array} Returns a new array of elements that passed the callback check. + * @example + * + * var evens = _.filter([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; }); + * // => [2, 4, 6] + * + * var food = [ + * { 'name': 'apple', 'organic': false, 'type': 'fruit' }, + * { 'name': 'carrot', 'organic': true, 'type': 'vegetable' } + * ]; + * + * // using "_.pluck" callback shorthand + * _.filter(food, 'organic'); + * // => [{ 'name': 'carrot', 'organic': true, 'type': 'vegetable' }] + * + * // using "_.where" callback shorthand + * _.filter(food, { 'type': 'fruit' }); + * // => [{ 'name': 'apple', 'organic': false, 'type': 'fruit' }] + */ + function filter(collection, callback, thisArg) { + var result = []; + callback = lodash.createCallback(callback, thisArg); + + if (isArray(collection)) { + var index = -1, + length = collection.length; + + while (++index < length) { + var value = collection[index]; + if (callback(value, index, collection)) { + result.push(value); + } } - }); + } else { + each(collection, function(value, index, collection) { + if (callback(value, index, collection)) { + result.push(value); + } + }); + } + return result; } - return result; - } - /** - * Retrieves the minimum value of an `array`. If `callback` is passed, - * it will be executed for each value in the `array` to generate the - * criterion by which the value is ranked. The `callback` is bound to `thisArg` - * and invoked with three arguments; (value, index, collection). - * - * If a property name is passed for `callback`, the created "_.pluck" style - * callback will return the property value of the given element. - * - * If an object is passed for `callback`, the created "_.where" style callback - * will return `true` for elements that have the propeties of the given object, - * else `false`. - * - * @static - * @memberOf _ - * @category Collections - * @param {Array|Object|String} collection The collection to iterate over. - * @param {Function|Object|String} [callback=identity] The function called per - * iteration. If a property name or object is passed, it will be used to create - * a "_.pluck" or "_.where" style callback, respectively. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @returns {Mixed} Returns the minimum value. - * @example - * - * _.min([4, 2, 8, 6]); - * // => 2 - * - * var stooges = [ - * { 'name': 'moe', 'age': 40 }, - * { 'name': 'larry', 'age': 50 } - * ]; - * - * _.min(stooges, function(stooge) { return stooge.age; }); - * // => { 'name': 'moe', 'age': 40 }; - * - * // using "_.pluck" callback shorthand - * _.min(stooges, 'age'); - * // => { 'name': 'moe', 'age': 40 }; - */ - function min(collection, callback, thisArg) { - var computed = Infinity, - result = computed; + /** + * Examines each element in a `collection`, returning the first that the `callback` + * returns truthy for. The `callback` is bound to `thisArg` and invoked with three + * arguments; (value, index|key, collection). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @alias detect + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Mixed} Returns the found element, else `undefined`. + * @example + * + * _.find([1, 2, 3, 4], function(num) { return num % 2 == 0; }); + * // => 2 + * + * var food = [ + * { 'name': 'apple', 'organic': false, 'type': 'fruit' }, + * { 'name': 'banana', 'organic': true, 'type': 'fruit' }, + * { 'name': 'beet', 'organic': false, 'type': 'vegetable' } + * ]; + * + * // using "_.where" callback shorthand + * _.find(food, { 'type': 'vegetable' }); + * // => { 'name': 'beet', 'organic': false, 'type': 'vegetable' } + * + * // using "_.pluck" callback shorthand + * _.find(food, 'organic'); + * // => { 'name': 'banana', 'organic': true, 'type': 'fruit' } + */ + function find(collection, callback, thisArg) { + callback = lodash.createCallback(callback, thisArg); - if (!callback && isArray(collection)) { - var index = -1, - length = collection.length; + if (isArray(collection)) { + var index = -1, + length = collection.length; - while (++index < length) { - var value = collection[index]; - if (value < result) { - result = value; + while (++index < length) { + var value = collection[index]; + if (callback(value, index, collection)) { + return value; + } } + } else { + var result; + each(collection, function(value, index, collection) { + if (callback(value, index, collection)) { + result = value; + return false; + } + }); + return result; } - } else { - callback = !callback && isString(collection) - ? charAtCallback - : createCallback(callback, thisArg); - - each(collection, function(value, index, collection) { - var current = callback(value, index, collection); - if (current < computed) { - computed = current; - result = value; + } + + /** + * Iterates over a `collection`, executing the `callback` for each element in + * the `collection`. The `callback` is bound to `thisArg` and invoked with three + * arguments; (value, index|key, collection). Callbacks may exit iteration early + * by explicitly returning `false`. + * + * @static + * @memberOf _ + * @alias each + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function} [callback=identity] The function called per iteration. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Array|Object|String} Returns `collection`. + * @example + * + * _([1, 2, 3]).forEach(alert).join(','); + * // => alerts each number and returns '1,2,3' + * + * _.forEach({ 'one': 1, 'two': 2, 'three': 3 }, alert); + * // => alerts each number value (order is not guaranteed) + */ + function forEach(collection, callback, thisArg) { + if (callback && typeof thisArg == 'undefined' && isArray(collection)) { + var index = -1, + length = collection.length; + + while (++index < length) { + if (callback(collection[index], index, collection) === false) { + break; + } } + } else { + each(collection, callback, thisArg); + } + return collection; + } + + /** + * Creates an object composed of keys returned from running each element of the + * `collection` through the `callback`. The corresponding value of each key is + * an array of elements passed to `callback` that returned the key. The `callback` + * is bound to `thisArg` and invoked with three arguments; (value, index|key, collection). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false` + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Object} Returns the composed aggregate object. + * @example + * + * _.groupBy([4.2, 6.1, 6.4], function(num) { return Math.floor(num); }); + * // => { '4': [4.2], '6': [6.1, 6.4] } + * + * _.groupBy([4.2, 6.1, 6.4], function(num) { return this.floor(num); }, Math); + * // => { '4': [4.2], '6': [6.1, 6.4] } + * + * // using "_.pluck" callback shorthand + * _.groupBy(['one', 'two', 'three'], 'length'); + * // => { '3': ['one', 'two'], '5': ['three'] } + */ + function groupBy(collection, callback, thisArg) { + var result = {}; + callback = lodash.createCallback(callback, thisArg); + + forEach(collection, function(value, key, collection) { + key = String(callback(value, key, collection)); + (hasOwnProperty.call(result, key) ? result[key] : result[key] = []).push(value); }); + return result; } - return result; - } - /** - * Retrieves the value of a specified property from all elements in the `collection`. - * - * @static - * @memberOf _ - * @type Function - * @category Collections - * @param {Array|Object|String} collection The collection to iterate over. - * @param {String} property The property to pluck. - * @returns {Array} Returns a new array of property values. - * @example - * - * var stooges = [ - * { 'name': 'moe', 'age': 40 }, - * { 'name': 'larry', 'age': 50 } - * ]; - * - * _.pluck(stooges, 'name'); - * // => ['moe', 'larry'] - */ - var pluck = map; + /** + * Invokes the method named by `methodName` on each element in the `collection`, + * returning an array of the results of each invoked method. Additional arguments + * will be passed to each invoked method. If `methodName` is a function, it will + * be invoked for, and `this` bound to, each element in the `collection`. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|String} methodName The name of the method to invoke or + * the function invoked per iteration. + * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the method with. + * @returns {Array} Returns a new array of the results of each invoked method. + * @example + * + * _.invoke([[5, 1, 7], [3, 2, 1]], 'sort'); + * // => [[1, 5, 7], [1, 2, 3]] + * + * _.invoke([123, 456], String.prototype.split, ''); + * // => [['1', '2', '3'], ['4', '5', '6']] + */ + function invoke(collection, methodName) { + var args = slice(arguments, 2), + index = -1, + isFunc = typeof methodName == 'function', + length = collection ? collection.length : 0, + result = Array(typeof length == 'number' ? length : 0); - /** - * Reduces a `collection` to a value that is the accumulated result of running - * each element in the `collection` through the `callback`, where each successive - * `callback` execution consumes the return value of the previous execution. - * If `accumulator` is not passed, the first element of the `collection` will be - * used as the initial `accumulator` value. The `callback` is bound to `thisArg` - * and invoked with four arguments; (accumulator, value, index|key, collection). - * - * @static - * @memberOf _ - * @alias foldl, inject - * @category Collections - * @param {Array|Object|String} collection The collection to iterate over. - * @param {Function} [callback=identity] The function called per iteration. - * @param {Mixed} [accumulator] Initial value of the accumulator. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @returns {Mixed} Returns the accumulated value. - * @example - * - * var sum = _.reduce([1, 2, 3], function(sum, num) { - * return sum + num; - * }); - * // => 6 - * - * var mapped = _.reduce({ 'a': 1, 'b': 2, 'c': 3 }, function(result, num, key) { - * result[key] = num * 3; - * return result; - * }, {}); - * // => { 'a': 3, 'b': 6, 'c': 9 } - */ - function reduce(collection, callback, accumulator, thisArg) { - var noaccum = arguments.length < 3; - callback = createCallback(callback, thisArg, 4); + forEach(collection, function(value) { + result[++index] = (isFunc ? methodName : value[methodName]).apply(value, args); + }); + return result; + } - if (isArray(collection)) { + /** + * Creates an array of values by running each element in the `collection` + * through the `callback`. The `callback` is bound to `thisArg` and invoked with + * three arguments; (value, index|key, collection). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @alias collect + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Array} Returns a new array of the results of each `callback` execution. + * @example + * + * _.map([1, 2, 3], function(num) { return num * 3; }); + * // => [3, 6, 9] + * + * _.map({ 'one': 1, 'two': 2, 'three': 3 }, function(num) { return num * 3; }); + * // => [3, 6, 9] (order is not guaranteed) + * + * var stooges = [ + * { 'name': 'moe', 'age': 40 }, + * { 'name': 'larry', 'age': 50 } + * ]; + * + * // using "_.pluck" callback shorthand + * _.map(stooges, 'name'); + * // => ['moe', 'larry'] + */ + function map(collection, callback, thisArg) { var index = -1, - length = collection.length; + length = collection ? collection.length : 0, + result = Array(typeof length == 'number' ? length : 0); - if (noaccum) { - accumulator = collection[++index]; - } - while (++index < length) { - accumulator = callback(accumulator, collection[index], index, collection); + callback = lodash.createCallback(callback, thisArg); + if (isArray(collection)) { + while (++index < length) { + result[index] = callback(collection[index], index, collection); + } + } else { + each(collection, function(value, key, collection) { + result[++index] = callback(value, key, collection); + }); } - } else { - each(collection, function(value, index, collection) { - accumulator = noaccum - ? (noaccum = false, value) - : callback(accumulator, value, index, collection) - }); + return result; } - return accumulator; - } - /** - * This method is similar to `_.reduce`, except that it iterates over a - * `collection` from right to left. - * - * @static - * @memberOf _ - * @alias foldr - * @category Collections - * @param {Array|Object|String} collection The collection to iterate over. - * @param {Function} [callback=identity] The function called per iteration. - * @param {Mixed} [accumulator] Initial value of the accumulator. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @returns {Mixed} Returns the accumulated value. - * @example - * - * var list = [[0, 1], [2, 3], [4, 5]]; - * var flat = _.reduceRight(list, function(a, b) { return a.concat(b); }, []); - * // => [4, 5, 2, 3, 0, 1] - */ - function reduceRight(collection, callback, accumulator, thisArg) { - var iterable = collection, - length = collection ? collection.length : 0, - noaccum = arguments.length < 3; - - if (typeof length != 'number') { - var props = keys(collection); - length = props.length; - } else if (noCharByIndex && isString(collection)) { - iterable = collection.split(''); + /** + * Retrieves the maximum value of an `array`. If `callback` is passed, + * it will be executed for each value in the `array` to generate the + * criterion by which the value is ranked. The `callback` is bound to + * `thisArg` and invoked with three arguments; (value, index, collection). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Mixed} Returns the maximum value. + * @example + * + * _.max([4, 2, 8, 6]); + * // => 8 + * + * var stooges = [ + * { 'name': 'moe', 'age': 40 }, + * { 'name': 'larry', 'age': 50 } + * ]; + * + * _.max(stooges, function(stooge) { return stooge.age; }); + * // => { 'name': 'larry', 'age': 50 }; + * + * // using "_.pluck" callback shorthand + * _.max(stooges, 'age'); + * // => { 'name': 'larry', 'age': 50 }; + */ + function max(collection, callback, thisArg) { + var computed = -Infinity, + result = computed; + + if (!callback && isArray(collection)) { + var index = -1, + length = collection.length; + + while (++index < length) { + var value = collection[index]; + if (value > result) { + result = value; + } + } + } else { + callback = (!callback && isString(collection)) + ? charAtCallback + : lodash.createCallback(callback, thisArg); + + each(collection, function(value, index, collection) { + var current = callback(value, index, collection); + if (current > computed) { + computed = current; + result = value; + } + }); + } + return result; } - callback = createCallback(callback, thisArg, 4); - forEach(collection, function(value, index, collection) { - index = props ? props[--length] : --length; - accumulator = noaccum - ? (noaccum = false, iterable[index]) - : callback(accumulator, iterable[index], index, collection); - }); - return accumulator; - } - /** - * The opposite of `_.filter`, this method returns the elements of a - * `collection` that `callback` does **not** return truthy for. - * - * If a property name is passed for `callback`, the created "_.pluck" style - * callback will return the property value of the given element. - * - * If an object is passed for `callback`, the created "_.where" style callback - * will return `true` for elements that have the propeties of the given object, - * else `false`. - * - * @static - * @memberOf _ - * @category Collections - * @param {Array|Object|String} collection The collection to iterate over. - * @param {Function|Object|String} [callback=identity] The function called per - * iteration. If a property name or object is passed, it will be used to create - * a "_.pluck" or "_.where" style callback, respectively. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @returns {Array} Returns a new array of elements that did **not** pass the - * callback check. - * @example - * - * var odds = _.reject([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; }); - * // => [1, 3, 5] - * - * var food = [ - * { 'name': 'apple', 'organic': false, 'type': 'fruit' }, - * { 'name': 'carrot', 'organic': true, 'type': 'vegetable' } - * ]; - * - * // using "_.pluck" callback shorthand - * _.reject(food, 'organic'); - * // => [{ 'name': 'apple', 'organic': false, 'type': 'fruit' }] - * - * // using "_.where" callback shorthand - * _.reject(food, { 'type': 'fruit' }); - * // => [{ 'name': 'carrot', 'organic': true, 'type': 'vegetable' }] - */ - function reject(collection, callback, thisArg) { - callback = createCallback(callback, thisArg); - return filter(collection, function(value, index, collection) { - return !callback(value, index, collection); - }); - } - - /** - * Creates an array of shuffled `array` values, using a version of the - * Fisher-Yates shuffle. See http://en.wikipedia.org/wiki/Fisher-Yates_shuffle. - * - * @static - * @memberOf _ - * @category Collections - * @param {Array|Object|String} collection The collection to shuffle. - * @returns {Array} Returns a new shuffled collection. - * @example - * - * _.shuffle([1, 2, 3, 4, 5, 6]); - * // => [4, 1, 6, 3, 5, 2] - */ - function shuffle(collection) { - var index = -1, - length = collection ? collection.length : 0, - result = Array(typeof length == 'number' ? length : 0); - - forEach(collection, function(value) { - var rand = floor(nativeRandom() * (++index + 1)); - result[index] = result[rand]; - result[rand] = value; - }); - return result; - } + /** + * Retrieves the minimum value of an `array`. If `callback` is passed, + * it will be executed for each value in the `array` to generate the + * criterion by which the value is ranked. The `callback` is bound to `thisArg` + * and invoked with three arguments; (value, index, collection). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Mixed} Returns the minimum value. + * @example + * + * _.min([4, 2, 8, 6]); + * // => 2 + * + * var stooges = [ + * { 'name': 'moe', 'age': 40 }, + * { 'name': 'larry', 'age': 50 } + * ]; + * + * _.min(stooges, function(stooge) { return stooge.age; }); + * // => { 'name': 'moe', 'age': 40 }; + * + * // using "_.pluck" callback shorthand + * _.min(stooges, 'age'); + * // => { 'name': 'moe', 'age': 40 }; + */ + function min(collection, callback, thisArg) { + var computed = Infinity, + result = computed; + + if (!callback && isArray(collection)) { + var index = -1, + length = collection.length; + + while (++index < length) { + var value = collection[index]; + if (value < result) { + result = value; + } + } + } else { + callback = (!callback && isString(collection)) + ? charAtCallback + : lodash.createCallback(callback, thisArg); + + each(collection, function(value, index, collection) { + var current = callback(value, index, collection); + if (current < computed) { + computed = current; + result = value; + } + }); + } + return result; + } - /** - * Gets the size of the `collection` by returning `collection.length` for arrays - * and array-like objects or the number of own enumerable properties for objects. - * - * @static - * @memberOf _ - * @category Collections - * @param {Array|Object|String} collection The collection to inspect. - * @returns {Number} Returns `collection.length` or number of own enumerable properties. - * @example - * - * _.size([1, 2]); - * // => 2 - * - * _.size({ 'one': 1, 'two': 2, 'three': 3 }); - * // => 3 - * - * _.size('curly'); - * // => 5 - */ - function size(collection) { - var length = collection ? collection.length : 0; - return typeof length == 'number' ? length : keys(collection).length; - } + /** + * Retrieves the value of a specified property from all elements in the `collection`. + * + * @static + * @memberOf _ + * @type Function + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {String} property The property to pluck. + * @returns {Array} Returns a new array of property values. + * @example + * + * var stooges = [ + * { 'name': 'moe', 'age': 40 }, + * { 'name': 'larry', 'age': 50 } + * ]; + * + * _.pluck(stooges, 'name'); + * // => ['moe', 'larry'] + */ + var pluck = map; - /** - * Checks if the `callback` returns a truthy value for **any** element of a - * `collection`. The function returns as soon as it finds passing value, and - * does not iterate over the entire `collection`. The `callback` is bound to - * `thisArg` and invoked with three arguments; (value, index|key, collection). - * - * If a property name is passed for `callback`, the created "_.pluck" style - * callback will return the property value of the given element. - * - * If an object is passed for `callback`, the created "_.where" style callback - * will return `true` for elements that have the propeties of the given object, - * else `false`. - * - * @static - * @memberOf _ - * @alias any - * @category Collections - * @param {Array|Object|String} collection The collection to iterate over. - * @param {Function|Object|String} [callback=identity] The function called per - * iteration. If a property name or object is passed, it will be used to create - * a "_.pluck" or "_.where" style callback, respectively. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @returns {Boolean} Returns `true` if any element passes the callback check, - * else `false`. - * @example - * - * _.some([null, 0, 'yes', false], Boolean); - * // => true - * - * var food = [ - * { 'name': 'apple', 'organic': false, 'type': 'fruit' }, - * { 'name': 'carrot', 'organic': true, 'type': 'vegetable' } - * ]; - * - * // using "_.pluck" callback shorthand - * _.some(food, 'organic'); - * // => true - * - * // using "_.where" callback shorthand - * _.some(food, { 'type': 'meat' }); - * // => false - */ - function some(collection, callback, thisArg) { - var result; - callback = createCallback(callback, thisArg); + /** + * Reduces a `collection` to a value that is the accumulated result of running + * each element in the `collection` through the `callback`, where each successive + * `callback` execution consumes the return value of the previous execution. + * If `accumulator` is not passed, the first element of the `collection` will be + * used as the initial `accumulator` value. The `callback` is bound to `thisArg` + * and invoked with four arguments; (accumulator, value, index|key, collection). + * + * @static + * @memberOf _ + * @alias foldl, inject + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function} [callback=identity] The function called per iteration. + * @param {Mixed} [accumulator] Initial value of the accumulator. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Mixed} Returns the accumulated value. + * @example + * + * var sum = _.reduce([1, 2, 3], function(sum, num) { + * return sum + num; + * }); + * // => 6 + * + * var mapped = _.reduce({ 'a': 1, 'b': 2, 'c': 3 }, function(result, num, key) { + * result[key] = num * 3; + * return result; + * }, {}); + * // => { 'a': 3, 'b': 6, 'c': 9 } + */ + function reduce(collection, callback, accumulator, thisArg) { + var noaccum = arguments.length < 3; + callback = lodash.createCallback(callback, thisArg, 4); - if (isArray(collection)) { - var index = -1, - length = collection.length; + if (isArray(collection)) { + var index = -1, + length = collection.length; - while (++index < length) { - if ((result = callback(collection[index], index, collection))) { - break; + if (noaccum) { + accumulator = collection[++index]; } + while (++index < length) { + accumulator = callback(accumulator, collection[index], index, collection); + } + } else { + each(collection, function(value, index, collection) { + accumulator = noaccum + ? (noaccum = false, value) + : callback(accumulator, value, index, collection) + }); + } + return accumulator; + } + + /** + * This method is similar to `_.reduce`, except that it iterates over a + * `collection` from right to left. + * + * @static + * @memberOf _ + * @alias foldr + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function} [callback=identity] The function called per iteration. + * @param {Mixed} [accumulator] Initial value of the accumulator. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Mixed} Returns the accumulated value. + * @example + * + * var list = [[0, 1], [2, 3], [4, 5]]; + * var flat = _.reduceRight(list, function(a, b) { return a.concat(b); }, []); + * // => [4, 5, 2, 3, 0, 1] + */ + function reduceRight(collection, callback, accumulator, thisArg) { + var iterable = collection, + length = collection ? collection.length : 0, + noaccum = arguments.length < 3; + + if (typeof length != 'number') { + var props = keys(collection); + length = props.length; + } else if (support.unindexedChars && isString(collection)) { + iterable = collection.split(''); } - } else { - each(collection, function(value, index, collection) { - return !(result = callback(value, index, collection)); + callback = lodash.createCallback(callback, thisArg, 4); + forEach(collection, function(value, index, collection) { + index = props ? props[--length] : --length; + accumulator = noaccum + ? (noaccum = false, iterable[index]) + : callback(accumulator, iterable[index], index, collection); }); + return accumulator; } - return !!result; - } - /** - * Creates an array of elements, sorted in ascending order by the results of - * running each element in the `collection` through the `callback`. This method - * performs a stable sort, that is, it will preserve the original sort order of - * equal elements. The `callback` is bound to `thisArg` and invoked with three - * arguments; (value, index|key, collection). - * - * If a property name is passed for `callback`, the created "_.pluck" style - * callback will return the property value of the given element. - * - * If an object is passed for `callback`, the created "_.where" style callback - * will return `true` for elements that have the propeties of the given object, - * else `false`. - * - * @static - * @memberOf _ - * @category Collections - * @param {Array|Object|String} collection The collection to iterate over. - * @param {Function|Object|String} [callback=identity] The function called per - * iteration. If a property name or object is passed, it will be used to create - * a "_.pluck" or "_.where" style callback, respectively. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @returns {Array} Returns a new array of sorted elements. - * @example - * - * _.sortBy([1, 2, 3], function(num) { return Math.sin(num); }); - * // => [3, 1, 2] - * - * _.sortBy([1, 2, 3], function(num) { return this.sin(num); }, Math); - * // => [3, 1, 2] - * - * // using "_.pluck" callback shorthand - * _.sortBy(['banana', 'strawberry', 'apple'], 'length'); - * // => ['apple', 'banana', 'strawberry'] - */ - function sortBy(collection, callback, thisArg) { - var index = -1, - length = collection ? collection.length : 0, - result = Array(typeof length == 'number' ? length : 0); - - callback = createCallback(callback, thisArg); - forEach(collection, function(value, key, collection) { - result[++index] = { - 'criteria': callback(value, key, collection), - 'index': index, - 'value': value - }; - }); + /** + * The opposite of `_.filter`, this method returns the elements of a + * `collection` that `callback` does **not** return truthy for. + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Array} Returns a new array of elements that did **not** pass the + * callback check. + * @example + * + * var odds = _.reject([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; }); + * // => [1, 3, 5] + * + * var food = [ + * { 'name': 'apple', 'organic': false, 'type': 'fruit' }, + * { 'name': 'carrot', 'organic': true, 'type': 'vegetable' } + * ]; + * + * // using "_.pluck" callback shorthand + * _.reject(food, 'organic'); + * // => [{ 'name': 'apple', 'organic': false, 'type': 'fruit' }] + * + * // using "_.where" callback shorthand + * _.reject(food, { 'type': 'fruit' }); + * // => [{ 'name': 'carrot', 'organic': true, 'type': 'vegetable' }] + */ + function reject(collection, callback, thisArg) { + callback = lodash.createCallback(callback, thisArg); + return filter(collection, function(value, index, collection) { + return !callback(value, index, collection); + }); + } + + /** + * Creates an array of shuffled `array` values, using a version of the + * Fisher-Yates shuffle. See http://en.wikipedia.org/wiki/Fisher-Yates_shuffle. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|String} collection The collection to shuffle. + * @returns {Array} Returns a new shuffled collection. + * @example + * + * _.shuffle([1, 2, 3, 4, 5, 6]); + * // => [4, 1, 6, 3, 5, 2] + */ + function shuffle(collection) { + var index = -1, + length = collection ? collection.length : 0, + result = Array(typeof length == 'number' ? length : 0); - length = result.length; - result.sort(compareAscending); - while (length--) { - result[length] = result[length].value; + forEach(collection, function(value) { + var rand = floor(nativeRandom() * (++index + 1)); + result[index] = result[rand]; + result[rand] = value; + }); + return result; } - return result; - } - /** - * Converts the `collection` to an array. - * - * @static - * @memberOf _ - * @category Collections - * @param {Array|Object|String} collection The collection to convert. - * @returns {Array} Returns the new converted array. - * @example - * - * (function() { return _.toArray(arguments).slice(1); })(1, 2, 3, 4); - * // => [2, 3, 4] - */ - function toArray(collection) { - if (collection && typeof collection.length == 'number') { - return noCharByIndex && isString(collection) - ? collection.split('') - : slice(collection); + /** + * Gets the size of the `collection` by returning `collection.length` for arrays + * and array-like objects or the number of own enumerable properties for objects. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|String} collection The collection to inspect. + * @returns {Number} Returns `collection.length` or number of own enumerable properties. + * @example + * + * _.size([1, 2]); + * // => 2 + * + * _.size({ 'one': 1, 'two': 2, 'three': 3 }); + * // => 3 + * + * _.size('curly'); + * // => 5 + */ + function size(collection) { + var length = collection ? collection.length : 0; + return typeof length == 'number' ? length : keys(collection).length; } - return values(collection); - } - /** - * Examines each element in a `collection`, returning an array of all elements - * that have the given `properties`. When checking `properties`, this method - * performs a deep comparison between values to determine if they are equivalent - * to each other. - * - * @static - * @memberOf _ - * @type Function - * @category Collections - * @param {Array|Object|String} collection The collection to iterate over. - * @param {Object} properties The object of property values to filter by. - * @returns {Array} Returns a new array of elements that have the given `properties`. - * @example - * - * var stooges = [ - * { 'name': 'moe', 'age': 40 }, - * { 'name': 'larry', 'age': 50 } - * ]; - * - * _.where(stooges, { 'age': 40 }); - * // => [{ 'name': 'moe', 'age': 40 }] - */ - var where = filter; + /** + * Checks if the `callback` returns a truthy value for **any** element of a + * `collection`. The function returns as soon as it finds passing value, and + * does not iterate over the entire `collection`. The `callback` is bound to + * `thisArg` and invoked with three arguments; (value, index|key, collection). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @alias any + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Boolean} Returns `true` if any element passes the callback check, + * else `false`. + * @example + * + * _.some([null, 0, 'yes', false], Boolean); + * // => true + * + * var food = [ + * { 'name': 'apple', 'organic': false, 'type': 'fruit' }, + * { 'name': 'carrot', 'organic': true, 'type': 'vegetable' } + * ]; + * + * // using "_.pluck" callback shorthand + * _.some(food, 'organic'); + * // => true + * + * // using "_.where" callback shorthand + * _.some(food, { 'type': 'meat' }); + * // => false + */ + function some(collection, callback, thisArg) { + var result; + callback = lodash.createCallback(callback, thisArg); - /*--------------------------------------------------------------------------*/ + if (isArray(collection)) { + var index = -1, + length = collection.length; - /** - * Creates an array with all falsey values of `array` removed. The values - * `false`, `null`, `0`, `""`, `undefined` and `NaN` are all falsey. - * - * @static - * @memberOf _ - * @category Arrays - * @param {Array} array The array to compact. - * @returns {Array} Returns a new filtered array. - * @example - * - * _.compact([0, 1, false, 2, '', 3]); - * // => [1, 2, 3] - */ - function compact(array) { - var index = -1, - length = array ? array.length : 0, - result = []; - - while (++index < length) { - var value = array[index]; - if (value) { - result.push(value); + while (++index < length) { + if ((result = callback(collection[index], index, collection))) { + break; + } + } + } else { + each(collection, function(value, index, collection) { + return !(result = callback(value, index, collection)); + }); } + return !!result; } - return result; - } - /** - * Creates an array of `array` elements not present in the other arrays - * using strict equality for comparisons, i.e. `===`. - * - * @static - * @memberOf _ - * @category Arrays - * @param {Array} array The array to process. - * @param {Array} [array1, array2, ...] Arrays to check. - * @returns {Array} Returns a new array of `array` elements not present in the - * other arrays. - * @example - * - * _.difference([1, 2, 3, 4, 5], [5, 2, 10]); - * // => [1, 3, 4] - */ - function difference(array) { - var index = -1, - length = array ? array.length : 0, - flattened = concat.apply(arrayRef, arguments), - contains = cachedContains(flattened, length), - result = []; - - while (++index < length) { - var value = array[index]; - if (!contains(value)) { - result.push(value); + /** + * Creates an array of elements, sorted in ascending order by the results of + * running each element in the `collection` through the `callback`. This method + * performs a stable sort, that is, it will preserve the original sort order of + * equal elements. The `callback` is bound to `thisArg` and invoked with three + * arguments; (value, index|key, collection). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Array} Returns a new array of sorted elements. + * @example + * + * _.sortBy([1, 2, 3], function(num) { return Math.sin(num); }); + * // => [3, 1, 2] + * + * _.sortBy([1, 2, 3], function(num) { return this.sin(num); }, Math); + * // => [3, 1, 2] + * + * // using "_.pluck" callback shorthand + * _.sortBy(['banana', 'strawberry', 'apple'], 'length'); + * // => ['apple', 'banana', 'strawberry'] + */ + function sortBy(collection, callback, thisArg) { + var index = -1, + length = collection ? collection.length : 0, + result = Array(typeof length == 'number' ? length : 0); + + callback = lodash.createCallback(callback, thisArg); + forEach(collection, function(value, key, collection) { + result[++index] = { + 'criteria': callback(value, key, collection), + 'index': index, + 'value': value + }; + }); + + length = result.length; + result.sort(compareAscending); + while (length--) { + result[length] = result[length].value; } + return result; } - return result; - } - - /** - * Gets the first element of the `array`. If a number `n` is passed, the first - * `n` elements of the `array` are returned. If a `callback` function is passed, - * the first elements the `callback` returns truthy for are returned. The `callback` - * is bound to `thisArg` and invoked with three arguments; (value, index, array). - * - * If a property name is passed for `callback`, the created "_.pluck" style - * callback will return the property value of the given element. - * - * If an object is passed for `callback`, the created "_.where" style callback - * will return `true` for elements that have the propeties of the given object, - * else `false`. - * - * @static - * @memberOf _ - * @alias head, take - * @category Arrays - * @param {Array} array The array to query. - * @param {Function|Object|Number|String} [callback|n] The function called - * per element or the number of elements to return. If a property name or - * object is passed, it will be used to create a "_.pluck" or "_.where" - * style callback, respectively. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @returns {Mixed} Returns the first element(s) of `array`. - * @example - * - * _.first([1, 2, 3]); - * // => 1 - * - * _.first([1, 2, 3], 2); - * // => [1, 2] - * - * _.first([1, 2, 3], function(num) { - * return num < 3; - * }); - * // => [1, 2] - * - * var food = [ - * { 'name': 'banana', 'organic': true }, - * { 'name': 'beet', 'organic': false }, - * ]; - * - * // using "_.pluck" callback shorthand - * _.first(food, 'organic'); - * // => [{ 'name': 'banana', 'organic': true }] - * - * var food = [ - * { 'name': 'apple', 'type': 'fruit' }, - * { 'name': 'banana', 'type': 'fruit' }, - * { 'name': 'beet', 'type': 'vegetable' } - * ]; - * - * // using "_.where" callback shorthand - * _.first(food, { 'type': 'fruit' }); - * // => [{ 'name': 'apple', 'type': 'fruit' }, { 'name': 'banana', 'type': 'fruit' }] - */ - function first(array, callback, thisArg) { - if (array) { - var n = 0, - length = array.length; - if (typeof callback != 'number' && callback != null) { - var index = -1; - callback = createCallback(callback, thisArg); - while (++index < length && callback(array[index], index, array)) { - n++; - } - } else { - n = callback; - if (n == null || thisArg) { - return array[0]; - } + /** + * Converts the `collection` to an array. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|String} collection The collection to convert. + * @returns {Array} Returns the new converted array. + * @example + * + * (function() { return _.toArray(arguments).slice(1); })(1, 2, 3, 4); + * // => [2, 3, 4] + */ + function toArray(collection) { + if (collection && typeof collection.length == 'number') { + return (support.unindexedChars && isString(collection)) + ? collection.split('') + : slice(collection); } - return slice(array, 0, nativeMin(nativeMax(0, n), length)); + return values(collection); } - } - /** - * Flattens a nested array (the nesting can be to any depth). If `shallow` is - * truthy, `array` will only be flattened a single level. - * - * @static - * @memberOf _ - * @category Arrays - * @param {Array} array The array to compact. - * @param {Boolean} shallow A flag to indicate only flattening a single level. - * @returns {Array} Returns a new flattened array. - * @example - * - * _.flatten([1, [2], [3, [[4]]]]); - * // => [1, 2, 3, 4]; - * - * _.flatten([1, [2], [3, [[4]]]], true); - * // => [1, 2, 3, [[4]]]; - */ - function flatten(array, shallow) { - var index = -1, - length = array ? array.length : 0, - result = []; + /** + * Examines each element in a `collection`, returning an array of all elements + * that have the given `properties`. When checking `properties`, this method + * performs a deep comparison between values to determine if they are equivalent + * to each other. + * + * @static + * @memberOf _ + * @type Function + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Object} properties The object of property values to filter by. + * @returns {Array} Returns a new array of elements that have the given `properties`. + * @example + * + * var stooges = [ + * { 'name': 'moe', 'age': 40 }, + * { 'name': 'larry', 'age': 50 } + * ]; + * + * _.where(stooges, { 'age': 40 }); + * // => [{ 'name': 'moe', 'age': 40 }] + */ + var where = filter; - while (++index < length) { - var value = array[index]; + /*--------------------------------------------------------------------------*/ - // recursively flatten arrays (susceptible to call stack limits) - if (isArray(value)) { - push.apply(result, shallow ? value : flatten(value)); - } else { - result.push(value); + /** + * Creates an array with all falsey values of `array` removed. The values + * `false`, `null`, `0`, `""`, `undefined` and `NaN` are all falsey. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to compact. + * @returns {Array} Returns a new filtered array. + * @example + * + * _.compact([0, 1, false, 2, '', 3]); + * // => [1, 2, 3] + */ + function compact(array) { + var index = -1, + length = array ? array.length : 0, + result = []; + + while (++index < length) { + var value = array[index]; + if (value) { + result.push(value); + } } + return result; } - return result; - } - /** - * Gets the index at which the first occurrence of `value` is found using - * strict equality for comparisons, i.e. `===`. If the `array` is already - * sorted, passing `true` for `fromIndex` will run a faster binary search. - * - * @static - * @memberOf _ - * @category Arrays - * @param {Array} array The array to search. - * @param {Mixed} value The value to search for. - * @param {Boolean|Number} [fromIndex=0] The index to search from or `true` to - * perform a binary search on a sorted `array`. - * @returns {Number} Returns the index of the matched value or `-1`. - * @example - * - * _.indexOf([1, 2, 3, 1, 2, 3], 2); - * // => 1 - * - * _.indexOf([1, 2, 3, 1, 2, 3], 2, 3); - * // => 4 - * - * _.indexOf([1, 1, 2, 2, 3, 3], 2, true); - * // => 2 - */ - function indexOf(array, value, fromIndex) { - var index = -1, - length = array ? array.length : 0; - - if (typeof fromIndex == 'number') { - index = (fromIndex < 0 ? nativeMax(0, length + fromIndex) : fromIndex || 0) - 1; - } else if (fromIndex) { - index = sortedIndex(array, value); - return array[index] === value ? index : -1; + /** + * Creates an array of `array` elements not present in the other arrays + * using strict equality for comparisons, i.e. `===`. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to process. + * @param {Array} [array1, array2, ...] Arrays to check. + * @returns {Array} Returns a new array of `array` elements not present in the + * other arrays. + * @example + * + * _.difference([1, 2, 3, 4, 5], [5, 2, 10]); + * // => [1, 3, 4] + */ + function difference(array) { + var index = -1, + length = array ? array.length : 0, + flattened = concat.apply(arrayRef, arguments), + contains = cachedContains(flattened, length, 100), + result = []; + + while (++index < length) { + var value = array[index]; + if (!contains(value)) { + result.push(value); + } + } + return result; } - while (++index < length) { - if (array[index] === value) { - return index; + + /** + * This method is similar to `_.find`, except that it returns the index of + * the element that passes the callback check, instead of the element itself. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Mixed} Returns the index of the found element, else `-1`. + * @example + * + * _.findIndex(['apple', 'banana', 'beet'], function(food) { return /^b/.test(food); }); + * // => 1 + */ + function findIndex(collection, callback, thisArg) { + var index = -1, + length = collection ? collection.length : 0; + + callback = lodash.createCallback(callback, thisArg); + while (++index < length) { + if (callback(collection[index], index, collection)) { + return index; + } } + return -1; } - return -1; - } - /** - * Gets all but the last element of `array`. If a number `n` is passed, the - * last `n` elements are excluded from the result. If a `callback` function - * is passed, the last elements the `callback` returns truthy for are excluded - * from the result. The `callback` is bound to `thisArg` and invoked with three - * arguments; (value, index, array). - * - * If a property name is passed for `callback`, the created "_.pluck" style - * callback will return the property value of the given element. - * - * If an object is passed for `callback`, the created "_.where" style callback - * will return `true` for elements that have the propeties of the given object, - * else `false`. - * - * @static - * @memberOf _ - * @category Arrays - * @param {Array} array The array to query. - * @param {Function|Object|Number|String} [callback|n=1] The function called - * per element or the number of elements to exclude. If a property name or - * object is passed, it will be used to create a "_.pluck" or "_.where" - * style callback, respectively. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @returns {Array} Returns a slice of `array`. - * @example - * - * _.initial([1, 2, 3]); - * // => [1, 2] - * - * _.initial([1, 2, 3], 2); - * // => [1] - * - * _.initial([1, 2, 3], function(num) { - * return num > 1; - * }); - * // => [1] - * - * var food = [ - * { 'name': 'beet', 'organic': false }, - * { 'name': 'carrot', 'organic': true } - * ]; - * - * // using "_.pluck" callback shorthand - * _.initial(food, 'organic'); - * // => [{ 'name': 'beet', 'organic': false }] - * - * var food = [ - * { 'name': 'banana', 'type': 'fruit' }, - * { 'name': 'beet', 'type': 'vegetable' }, - * { 'name': 'carrot', 'type': 'vegetable' } - * ]; - * - * // using "_.where" callback shorthand - * _.initial(food, { 'type': 'vegetable' }); - * // => [{ 'name': 'banana', 'type': 'fruit' }] - */ - function initial(array, callback, thisArg) { - if (!array) { - return []; - } - var n = 0, - length = array.length; - - if (typeof callback != 'number' && callback != null) { - var index = length; - callback = createCallback(callback, thisArg); - while (index-- && callback(array[index], index, array)) { - n++; + /** + * Gets the first element of the `array`. If a number `n` is passed, the first + * `n` elements of the `array` are returned. If a `callback` function is passed, + * elements at the beginning of the array are returned as long as the `callback` + * returns truthy. The `callback` is bound to `thisArg` and invoked with three + * arguments; (value, index, array). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @alias head, take + * @category Arrays + * @param {Array} array The array to query. + * @param {Function|Object|Number|String} [callback|n] The function called + * per element or the number of elements to return. If a property name or + * object is passed, it will be used to create a "_.pluck" or "_.where" + * style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Mixed} Returns the first element(s) of `array`. + * @example + * + * _.first([1, 2, 3]); + * // => 1 + * + * _.first([1, 2, 3], 2); + * // => [1, 2] + * + * _.first([1, 2, 3], function(num) { + * return num < 3; + * }); + * // => [1, 2] + * + * var food = [ + * { 'name': 'banana', 'organic': true }, + * { 'name': 'beet', 'organic': false }, + * ]; + * + * // using "_.pluck" callback shorthand + * _.first(food, 'organic'); + * // => [{ 'name': 'banana', 'organic': true }] + * + * var food = [ + * { 'name': 'apple', 'type': 'fruit' }, + * { 'name': 'banana', 'type': 'fruit' }, + * { 'name': 'beet', 'type': 'vegetable' } + * ]; + * + * // using "_.where" callback shorthand + * _.first(food, { 'type': 'fruit' }); + * // => [{ 'name': 'apple', 'type': 'fruit' }, { 'name': 'banana', 'type': 'fruit' }] + */ + function first(array, callback, thisArg) { + if (array) { + var n = 0, + length = array.length; + + if (typeof callback != 'number' && callback != null) { + var index = -1; + callback = lodash.createCallback(callback, thisArg); + while (++index < length && callback(array[index], index, array)) { + n++; + } + } else { + n = callback; + if (n == null || thisArg) { + return array[0]; + } + } + return slice(array, 0, nativeMin(nativeMax(0, n), length)); } - } else { - n = (callback == null || thisArg) ? 1 : callback || n; } - return slice(array, 0, nativeMin(nativeMax(0, length - n), length)); - } - /** - * Computes the intersection of all the passed-in arrays using strict equality - * for comparisons, i.e. `===`. - * - * @static - * @memberOf _ - * @category Arrays - * @param {Array} [array1, array2, ...] Arrays to process. - * @returns {Array} Returns a new array of unique elements that are present - * in **all** of the arrays. - * @example - * - * _.intersection([1, 2, 3], [101, 2, 1, 10], [2, 1]); - * // => [1, 2] - */ - function intersection(array) { - var args = arguments, - argsLength = args.length, - cache = { '0': {} }, - index = -1, - length = array ? array.length : 0, - isLarge = length >= 100, - result = [], - seen = result; - - outer: - while (++index < length) { - var value = array[index]; - if (isLarge) { - var key = value + ''; - var inited = hasOwnProperty.call(cache[0], key) - ? !(seen = cache[0][key]) - : (seen = cache[0][key] = []); + /** + * Flattens a nested array (the nesting can be to any depth). If `isShallow` + * is truthy, `array` will only be flattened a single level. If `callback` + * is passed, each element of `array` is passed through a callback` before + * flattening. The `callback` is bound to `thisArg` and invoked with three + * arguments; (value, index, array). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to compact. + * @param {Boolean} [isShallow=false] A flag to indicate only flattening a single level. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Array} Returns a new flattened array. + * @example + * + * _.flatten([1, [2], [3, [[4]]]]); + * // => [1, 2, 3, 4]; + * + * _.flatten([1, [2], [3, [[4]]]], true); + * // => [1, 2, 3, [[4]]]; + * + * var stooges = [ + * { 'name': 'curly', 'quotes': ['Oh, a wise guy, eh?', 'Poifect!'] }, + * { 'name': 'moe', 'quotes': ['Spread out!', 'You knucklehead!'] } + * ]; + * + * // using "_.pluck" callback shorthand + * _.flatten(stooges, 'quotes'); + * // => ['Oh, a wise guy, eh?', 'Poifect!', 'Spread out!', 'You knucklehead!'] + */ + function flatten(array, isShallow, callback, thisArg) { + var index = -1, + length = array ? array.length : 0, + result = []; + + // juggle arguments + if (typeof isShallow != 'boolean' && isShallow != null) { + thisArg = callback; + callback = isShallow; + isShallow = false; } - if (inited || indexOf(seen, value) < 0) { - if (isLarge) { - seen.push(value); + if (callback != null) { + callback = lodash.createCallback(callback, thisArg); + } + while (++index < length) { + var value = array[index]; + if (callback) { + value = callback(value, index, array); } - var argsIndex = argsLength; - while (--argsIndex) { - if (!(cache[argsIndex] || (cache[argsIndex] = cachedContains(args[argsIndex], 0, 100)))(value)) { - continue outer; - } + // recursively flatten arrays (susceptible to call stack limits) + if (isArray(value)) { + push.apply(result, isShallow ? value : flatten(value)); + } else { + result.push(value); } - result.push(value); } + return result; } - return result; - } - /** - * Gets the last element of the `array`. If a number `n` is passed, the last - * `n` elements of the `array` are returned. If a `callback` function is passed, - * the last elements the `callback` returns truthy for are returned. The `callback` - * is bound to `thisArg` and invoked with three arguments; (value, index, array). - * - * - * If a property name is passed for `callback`, the created "_.pluck" style - * callback will return the property value of the given element. - * - * If an object is passed for `callback`, the created "_.where" style callback - * will return `true` for elements that have the propeties of the given object, - * else `false`. - * - * @static - * @memberOf _ - * @category Arrays - * @param {Array} array The array to query. - * @param {Function|Object|Number|String} [callback|n] The function called - * per element or the number of elements to return. If a property name or - * object is passed, it will be used to create a "_.pluck" or "_.where" - * style callback, respectively. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @returns {Mixed} Returns the last element(s) of `array`. - * @example - * - * _.last([1, 2, 3]); - * // => 3 - * - * _.last([1, 2, 3], 2); - * // => [2, 3] - * - * _.last([1, 2, 3], function(num) { - * return num > 1; - * }); - * // => [2, 3] - * - * var food = [ - * { 'name': 'beet', 'organic': false }, - * { 'name': 'carrot', 'organic': true } - * ]; - * - * // using "_.pluck" callback shorthand - * _.last(food, 'organic'); - * // => [{ 'name': 'carrot', 'organic': true }] - * - * var food = [ - * { 'name': 'banana', 'type': 'fruit' }, - * { 'name': 'beet', 'type': 'vegetable' }, - * { 'name': 'carrot', 'type': 'vegetable' } - * ]; - * - * // using "_.where" callback shorthand - * _.last(food, { 'type': 'vegetable' }); - * // => [{ 'name': 'beet', 'type': 'vegetable' }, { 'name': 'carrot', 'type': 'vegetable' }] - */ - function last(array, callback, thisArg) { - if (array) { + /** + * Gets the index at which the first occurrence of `value` is found using + * strict equality for comparisons, i.e. `===`. If the `array` is already + * sorted, passing `true` for `fromIndex` will run a faster binary search. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to search. + * @param {Mixed} value The value to search for. + * @param {Boolean|Number} [fromIndex=0] The index to search from or `true` to + * perform a binary search on a sorted `array`. + * @returns {Number} Returns the index of the matched value or `-1`. + * @example + * + * _.indexOf([1, 2, 3, 1, 2, 3], 2); + * // => 1 + * + * _.indexOf([1, 2, 3, 1, 2, 3], 2, 3); + * // => 4 + * + * _.indexOf([1, 1, 2, 2, 3, 3], 2, true); + * // => 2 + */ + function indexOf(array, value, fromIndex) { + var index = -1, + length = array ? array.length : 0; + + if (typeof fromIndex == 'number') { + index = (fromIndex < 0 ? nativeMax(0, length + fromIndex) : fromIndex || 0) - 1; + } else if (fromIndex) { + index = sortedIndex(array, value); + return array[index] === value ? index : -1; + } + while (++index < length) { + if (array[index] === value) { + return index; + } + } + return -1; + } + + /** + * Gets all but the last element of `array`. If a number `n` is passed, the + * last `n` elements are excluded from the result. If a `callback` function + * is passed, elements at the end of the array are excluded from the result + * as long as the `callback` returns truthy. The `callback` is bound to + * `thisArg` and invoked with three arguments; (value, index, array). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to query. + * @param {Function|Object|Number|String} [callback|n=1] The function called + * per element or the number of elements to exclude. If a property name or + * object is passed, it will be used to create a "_.pluck" or "_.where" + * style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Array} Returns a slice of `array`. + * @example + * + * _.initial([1, 2, 3]); + * // => [1, 2] + * + * _.initial([1, 2, 3], 2); + * // => [1] + * + * _.initial([1, 2, 3], function(num) { + * return num > 1; + * }); + * // => [1] + * + * var food = [ + * { 'name': 'beet', 'organic': false }, + * { 'name': 'carrot', 'organic': true } + * ]; + * + * // using "_.pluck" callback shorthand + * _.initial(food, 'organic'); + * // => [{ 'name': 'beet', 'organic': false }] + * + * var food = [ + * { 'name': 'banana', 'type': 'fruit' }, + * { 'name': 'beet', 'type': 'vegetable' }, + * { 'name': 'carrot', 'type': 'vegetable' } + * ]; + * + * // using "_.where" callback shorthand + * _.initial(food, { 'type': 'vegetable' }); + * // => [{ 'name': 'banana', 'type': 'fruit' }] + */ + function initial(array, callback, thisArg) { + if (!array) { + return []; + } var n = 0, length = array.length; if (typeof callback != 'number' && callback != null) { var index = length; - callback = createCallback(callback, thisArg); + callback = lodash.createCallback(callback, thisArg); while (index-- && callback(array[index], index, array)) { n++; } } else { - n = callback; - if (n == null || thisArg) { - return array[length - 1]; - } + n = (callback == null || thisArg) ? 1 : callback || n; } - return slice(array, nativeMax(0, length - n)); + return slice(array, 0, nativeMin(nativeMax(0, length - n), length)); } - } - /** - * Gets the index at which the last occurrence of `value` is found using strict - * equality for comparisons, i.e. `===`. If `fromIndex` is negative, it is used - * as the offset from the end of the collection. - * - * @static - * @memberOf _ - * @category Arrays - * @param {Array} array The array to search. - * @param {Mixed} value The value to search for. - * @param {Number} [fromIndex=array.length-1] The index to search from. - * @returns {Number} Returns the index of the matched value or `-1`. - * @example - * - * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2); - * // => 4 - * - * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2, 3); - * // => 1 - */ - function lastIndexOf(array, value, fromIndex) { - var index = array ? array.length : 0; - if (typeof fromIndex == 'number') { - index = (fromIndex < 0 ? nativeMax(0, index + fromIndex) : nativeMin(fromIndex, index - 1)) + 1; - } - while (index--) { - if (array[index] === value) { - return index; + /** + * Computes the intersection of all the passed-in arrays using strict equality + * for comparisons, i.e. `===`. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} [array1, array2, ...] Arrays to process. + * @returns {Array} Returns a new array of unique elements that are present + * in **all** of the arrays. + * @example + * + * _.intersection([1, 2, 3], [101, 2, 1, 10], [2, 1]); + * // => [1, 2] + */ + function intersection(array) { + var args = arguments, + argsLength = args.length, + cache = { '0': {} }, + index = -1, + length = array ? array.length : 0, + isLarge = length >= 100, + result = [], + seen = result; + + outer: + while (++index < length) { + var value = array[index]; + if (isLarge) { + var key = String(value); + var inited = hasOwnProperty.call(cache[0], key) + ? !(seen = cache[0][key]) + : (seen = cache[0][key] = []); + } + if (inited || indexOf(seen, value) < 0) { + if (isLarge) { + seen.push(value); + } + var argsIndex = argsLength; + while (--argsIndex) { + if (!(cache[argsIndex] || (cache[argsIndex] = cachedContains(args[argsIndex], 0, 100)))(value)) { + continue outer; + } + } + result.push(value); + } } + return result; } - return -1; - } - /** - * Creates an object composed from arrays of `keys` and `values`. Pass either - * a single two dimensional array, i.e. `[[key1, value1], [key2, value2]]`, or - * two arrays, one of `keys` and one of corresponding `values`. - * - * @static - * @memberOf _ - * @category Arrays - * @param {Array} keys The array of keys. - * @param {Array} [values=[]] The array of values. - * @returns {Object} Returns an object composed of the given keys and - * corresponding values. - * @example - * - * _.object(['moe', 'larry'], [30, 40]); - * // => { 'moe': 30, 'larry': 40 } - */ - function object(keys, values) { - var index = -1, - length = keys ? keys.length : 0, - result = {}; - - while (++index < length) { - var key = keys[index]; - if (values) { - result[key] = values[index]; - } else { - result[key[0]] = key[1]; + /** + * Gets the last element of the `array`. If a number `n` is passed, the + * last `n` elements of the `array` are returned. If a `callback` function + * is passed, elements at the end of the array are returned as long as the + * `callback` returns truthy. The `callback` is bound to `thisArg` and + * invoked with three arguments;(value, index, array). + * + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to query. + * @param {Function|Object|Number|String} [callback|n] The function called + * per element or the number of elements to return. If a property name or + * object is passed, it will be used to create a "_.pluck" or "_.where" + * style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Mixed} Returns the last element(s) of `array`. + * @example + * + * _.last([1, 2, 3]); + * // => 3 + * + * _.last([1, 2, 3], 2); + * // => [2, 3] + * + * _.last([1, 2, 3], function(num) { + * return num > 1; + * }); + * // => [2, 3] + * + * var food = [ + * { 'name': 'beet', 'organic': false }, + * { 'name': 'carrot', 'organic': true } + * ]; + * + * // using "_.pluck" callback shorthand + * _.last(food, 'organic'); + * // => [{ 'name': 'carrot', 'organic': true }] + * + * var food = [ + * { 'name': 'banana', 'type': 'fruit' }, + * { 'name': 'beet', 'type': 'vegetable' }, + * { 'name': 'carrot', 'type': 'vegetable' } + * ]; + * + * // using "_.where" callback shorthand + * _.last(food, { 'type': 'vegetable' }); + * // => [{ 'name': 'beet', 'type': 'vegetable' }, { 'name': 'carrot', 'type': 'vegetable' }] + */ + function last(array, callback, thisArg) { + if (array) { + var n = 0, + length = array.length; + + if (typeof callback != 'number' && callback != null) { + var index = length; + callback = lodash.createCallback(callback, thisArg); + while (index-- && callback(array[index], index, array)) { + n++; + } + } else { + n = callback; + if (n == null || thisArg) { + return array[length - 1]; + } + } + return slice(array, nativeMax(0, length - n)); } } - return result; - } - - /** - * Creates an array of numbers (positive and/or negative) progressing from - * `start` up to but not including `end`. - * - * @static - * @memberOf _ - * @category Arrays - * @param {Number} [start=0] The start of the range. - * @param {Number} end The end of the range. - * @param {Number} [step=1] The value to increment or descrement by. - * @returns {Array} Returns a new range array. - * @example - * - * _.range(10); - * // => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - * - * _.range(1, 11); - * // => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - * - * _.range(0, 30, 5); - * // => [0, 5, 10, 15, 20, 25] - * - * _.range(0, -10, -1); - * // => [0, -1, -2, -3, -4, -5, -6, -7, -8, -9] - * - * _.range(0); - * // => [] - */ - function range(start, end, step) { - start = +start || 0; - step = +step || 1; - if (end == null) { - end = start; - start = 0; - } - // use `Array(length)` so V8 will avoid the slower "dictionary" mode - // http://youtu.be/XAqIpGU8ZZk#t=17m25s - var index = -1, - length = nativeMax(0, ceil((end - start) / step)), - result = Array(length); - - while (++index < length) { - result[index] = start; - start += step; + /** + * Gets the index at which the last occurrence of `value` is found using strict + * equality for comparisons, i.e. `===`. If `fromIndex` is negative, it is used + * as the offset from the end of the collection. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to search. + * @param {Mixed} value The value to search for. + * @param {Number} [fromIndex=array.length-1] The index to search from. + * @returns {Number} Returns the index of the matched value or `-1`. + * @example + * + * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2); + * // => 4 + * + * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2, 3); + * // => 1 + */ + function lastIndexOf(array, value, fromIndex) { + var index = array ? array.length : 0; + if (typeof fromIndex == 'number') { + index = (fromIndex < 0 ? nativeMax(0, index + fromIndex) : nativeMin(fromIndex, index - 1)) + 1; + } + while (index--) { + if (array[index] === value) { + return index; + } + } + return -1; } - return result; - } - /** - * The opposite of `_.initial`, this method gets all but the first value of `array`. - * If a number `n` is passed, the first `n` values are excluded from the result. - * If a `callback` function is passed, the first elements the `callback` returns - * truthy for are excluded from the result. The `callback` is bound to `thisArg` - * and invoked with three arguments; (value, index, array). - * - * If a property name is passed for `callback`, the created "_.pluck" style - * callback will return the property value of the given element. - * - * If an object is passed for `callback`, the created "_.where" style callback - * will return `true` for elements that have the propeties of the given object, - * else `false`. - * - * @static - * @memberOf _ - * @alias drop, tail - * @category Arrays - * @param {Array} array The array to query. - * @param {Function|Object|Number|String} [callback|n=1] The function called - * per element or the number of elements to exclude. If a property name or - * object is passed, it will be used to create a "_.pluck" or "_.where" - * style callback, respectively. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @returns {Array} Returns a slice of `array`. - * @example - * - * _.rest([1, 2, 3]); - * // => [2, 3] - * - * _.rest([1, 2, 3], 2); - * // => [3] - * - * _.rest([1, 2, 3], function(num) { - * return num < 3; - * }); - * // => [3] - * - * var food = [ - * { 'name': 'banana', 'organic': true }, - * { 'name': 'beet', 'organic': false }, - * ]; - * - * // using "_.pluck" callback shorthand - * _.rest(food, 'organic'); - * // => [{ 'name': 'beet', 'organic': false }] - * - * var food = [ - * { 'name': 'apple', 'type': 'fruit' }, - * { 'name': 'banana', 'type': 'fruit' }, - * { 'name': 'beet', 'type': 'vegetable' } - * ]; - * - * // using "_.where" callback shorthand - * _.rest(food, { 'type': 'fruit' }); - * // => [{ 'name': 'beet', 'type': 'vegetable' }] - */ - function rest(array, callback, thisArg) { - if (typeof callback != 'number' && callback != null) { - var n = 0, - index = -1, - length = array ? array.length : 0; + /** + * Creates an array of numbers (positive and/or negative) progressing from + * `start` up to but not including `end`. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Number} [start=0] The start of the range. + * @param {Number} end The end of the range. + * @param {Number} [step=1] The value to increment or decrement by. + * @returns {Array} Returns a new range array. + * @example + * + * _.range(10); + * // => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + * + * _.range(1, 11); + * // => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + * + * _.range(0, 30, 5); + * // => [0, 5, 10, 15, 20, 25] + * + * _.range(0, -10, -1); + * // => [0, -1, -2, -3, -4, -5, -6, -7, -8, -9] + * + * _.range(0); + * // => [] + */ + function range(start, end, step) { + start = +start || 0; + step = +step || 1; - callback = createCallback(callback, thisArg); - while (++index < length && callback(array[index], index, array)) { - n++; + if (end == null) { + end = start; + start = 0; } - } else { - n = (callback == null || thisArg) ? 1 : nativeMax(0, callback); - } - return slice(array, n); - } + // use `Array(length)` so V8 will avoid the slower "dictionary" mode + // http://youtu.be/XAqIpGU8ZZk#t=17m25s + var index = -1, + length = nativeMax(0, ceil((end - start) / step)), + result = Array(length); - /** - * Uses a binary search to determine the smallest index at which the `value` - * should be inserted into `array` in order to maintain the sort order of the - * sorted `array`. If `callback` is passed, it will be executed for `value` and - * each element in `array` to compute their sort ranking. The `callback` is - * bound to `thisArg` and invoked with one argument; (value). - * - * If a property name is passed for `callback`, the created "_.pluck" style - * callback will return the property value of the given element. - * - * If an object is passed for `callback`, the created "_.where" style callback - * will return `true` for elements that have the propeties of the given object, - * else `false`. - * - * @static - * @memberOf _ - * @category Arrays - * @param {Array} array The array to iterate over. - * @param {Mixed} value The value to evaluate. - * @param {Function|Object|String} [callback=identity] The function called per - * iteration. If a property name or object is passed, it will be used to create - * a "_.pluck" or "_.where" style callback, respectively. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @returns {Number} Returns the index at which the value should be inserted - * into `array`. - * @example - * - * _.sortedIndex([20, 30, 50], 40); - * // => 2 - * - * // using "_.pluck" callback shorthand - * _.sortedIndex([{ 'x': 20 }, { 'x': 30 }, { 'x': 50 }], { 'x': 40 }, 'x'); - * // => 2 - * - * var dict = { - * 'wordToNumber': { 'twenty': 20, 'thirty': 30, 'fourty': 40, 'fifty': 50 } - * }; - * - * _.sortedIndex(['twenty', 'thirty', 'fifty'], 'fourty', function(word) { - * return dict.wordToNumber[word]; - * }); - * // => 2 - * - * _.sortedIndex(['twenty', 'thirty', 'fifty'], 'fourty', function(word) { - * return this.wordToNumber[word]; - * }, dict); - * // => 2 - */ - function sortedIndex(array, value, callback, thisArg) { - var low = 0, - high = array ? array.length : low; - - // explicitly reference `identity` for better inlining in Firefox - callback = callback ? createCallback(callback, thisArg, 1) : identity; - value = callback(value); - - while (low < high) { - var mid = (low + high) >>> 1; - callback(array[mid]) < value - ? low = mid + 1 - : high = mid; + while (++index < length) { + result[index] = start; + start += step; + } + return result; } - return low; - } - /** - * Computes the union of the passed-in arrays using strict equality for - * comparisons, i.e. `===`. - * - * @static - * @memberOf _ - * @category Arrays - * @param {Array} [array1, array2, ...] Arrays to process. - * @returns {Array} Returns a new array of unique values, in order, that are - * present in one or more of the arrays. - * @example - * - * _.union([1, 2, 3], [101, 2, 1, 10], [2, 1]); - * // => [1, 2, 3, 101, 10] - */ - function union() { - return uniq(concat.apply(arrayRef, arguments)); - } - - /** - * Creates a duplicate-value-free version of the `array` using strict equality - * for comparisons, i.e. `===`. If the `array` is already sorted, passing `true` - * for `isSorted` will run a faster algorithm. If `callback` is passed, each - * element of `array` is passed through a callback` before uniqueness is computed. - * The `callback` is bound to `thisArg` and invoked with three arguments; (value, index, array). - * - * If a property name is passed for `callback`, the created "_.pluck" style - * callback will return the property value of the given element. - * - * If an object is passed for `callback`, the created "_.where" style callback - * will return `true` for elements that have the propeties of the given object, - * else `false`. - * - * @static - * @memberOf _ - * @alias unique - * @category Arrays - * @param {Array} array The array to process. - * @param {Boolean} [isSorted=false] A flag to indicate that the `array` is already sorted. - * @param {Function|Object|String} [callback=identity] The function called per - * iteration. If a property name or object is passed, it will be used to create - * a "_.pluck" or "_.where" style callback, respectively. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @returns {Array} Returns a duplicate-value-free array. - * @example - * - * _.uniq([1, 2, 1, 3, 1]); - * // => [1, 2, 3] - * - * _.uniq([1, 1, 2, 2, 3], true); - * // => [1, 2, 3] - * - * _.uniq([1, 2, 1.5, 3, 2.5], function(num) { return Math.floor(num); }); - * // => [1, 2, 3] - * - * _.uniq([1, 2, 1.5, 3, 2.5], function(num) { return this.floor(num); }, Math); - * // => [1, 2, 3] - * - * // using "_.pluck" callback shorthand - * _.uniq([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x'); - * // => [{ 'x': 1 }, { 'x': 2 }] - */ - function uniq(array, isSorted, callback, thisArg) { - var index = -1, - length = array ? array.length : 0, - result = [], - seen = result; - - // juggle arguments - if (typeof isSorted == 'function') { - thisArg = callback; - callback = isSorted; - isSorted = false; - } - // init value cache for large arrays - var isLarge = !isSorted && length >= 75; - if (isLarge) { - var cache = {}; - } - if (callback) { - seen = []; - callback = createCallback(callback, thisArg); - } - while (++index < length) { - var value = array[index], - computed = callback ? callback(value, index, array) : value; + /** + * The opposite of `_.initial`, this method gets all but the first value of + * `array`. If a number `n` is passed, the first `n` values are excluded from + * the result. If a `callback` function is passed, elements at the beginning + * of the array are excluded from the result as long as the `callback` returns + * truthy. The `callback` is bound to `thisArg` and invoked with three + * arguments; (value, index, array). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @alias drop, tail + * @category Arrays + * @param {Array} array The array to query. + * @param {Function|Object|Number|String} [callback|n=1] The function called + * per element or the number of elements to exclude. If a property name or + * object is passed, it will be used to create a "_.pluck" or "_.where" + * style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Array} Returns a slice of `array`. + * @example + * + * _.rest([1, 2, 3]); + * // => [2, 3] + * + * _.rest([1, 2, 3], 2); + * // => [3] + * + * _.rest([1, 2, 3], function(num) { + * return num < 3; + * }); + * // => [3] + * + * var food = [ + * { 'name': 'banana', 'organic': true }, + * { 'name': 'beet', 'organic': false }, + * ]; + * + * // using "_.pluck" callback shorthand + * _.rest(food, 'organic'); + * // => [{ 'name': 'beet', 'organic': false }] + * + * var food = [ + * { 'name': 'apple', 'type': 'fruit' }, + * { 'name': 'banana', 'type': 'fruit' }, + * { 'name': 'beet', 'type': 'vegetable' } + * ]; + * + * // using "_.where" callback shorthand + * _.rest(food, { 'type': 'fruit' }); + * // => [{ 'name': 'beet', 'type': 'vegetable' }] + */ + function rest(array, callback, thisArg) { + if (typeof callback != 'number' && callback != null) { + var n = 0, + index = -1, + length = array ? array.length : 0; - if (isLarge) { - var key = computed + ''; - var inited = hasOwnProperty.call(cache, key) - ? !(seen = cache[key]) - : (seen = cache[key] = []); - } - if (isSorted - ? !index || seen[seen.length - 1] !== computed - : inited || indexOf(seen, computed) < 0 - ) { - if (callback || isLarge) { - seen.push(computed); + callback = lodash.createCallback(callback, thisArg); + while (++index < length && callback(array[index], index, array)) { + n++; } - result.push(value); + } else { + n = (callback == null || thisArg) ? 1 : nativeMax(0, callback); } + return slice(array, n); } - return result; - } - /** - * Creates an array with all occurrences of the passed values removed using - * strict equality for comparisons, i.e. `===`. - * - * @static - * @memberOf _ - * @category Arrays - * @param {Array} array The array to filter. - * @param {Mixed} [value1, value2, ...] Values to remove. - * @returns {Array} Returns a new filtered array. - * @example - * - * _.without([1, 2, 1, 0, 3, 1, 4], 0, 1); - * // => [2, 3, 4] - */ - function without(array) { - var index = -1, - length = array ? array.length : 0, - contains = cachedContains(arguments, 1), - result = []; - - while (++index < length) { - var value = array[index]; - if (!contains(value)) { - result.push(value); + /** + * Uses a binary search to determine the smallest index at which the `value` + * should be inserted into `array` in order to maintain the sort order of the + * sorted `array`. If `callback` is passed, it will be executed for `value` and + * each element in `array` to compute their sort ranking. The `callback` is + * bound to `thisArg` and invoked with one argument; (value). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to iterate over. + * @param {Mixed} value The value to evaluate. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Number} Returns the index at which the value should be inserted + * into `array`. + * @example + * + * _.sortedIndex([20, 30, 50], 40); + * // => 2 + * + * // using "_.pluck" callback shorthand + * _.sortedIndex([{ 'x': 20 }, { 'x': 30 }, { 'x': 50 }], { 'x': 40 }, 'x'); + * // => 2 + * + * var dict = { + * 'wordToNumber': { 'twenty': 20, 'thirty': 30, 'fourty': 40, 'fifty': 50 } + * }; + * + * _.sortedIndex(['twenty', 'thirty', 'fifty'], 'fourty', function(word) { + * return dict.wordToNumber[word]; + * }); + * // => 2 + * + * _.sortedIndex(['twenty', 'thirty', 'fifty'], 'fourty', function(word) { + * return this.wordToNumber[word]; + * }, dict); + * // => 2 + */ + function sortedIndex(array, value, callback, thisArg) { + var low = 0, + high = array ? array.length : low; + + // explicitly reference `identity` for better inlining in Firefox + callback = callback ? lodash.createCallback(callback, thisArg, 1) : identity; + value = callback(value); + + while (low < high) { + var mid = (low + high) >>> 1; + (callback(array[mid]) < value) + ? low = mid + 1 + : high = mid; } + return low; } - return result; - } - /** - * Groups the elements of each array at their corresponding indexes. Useful for - * separate data sources that are coordinated through matching array indexes. - * For a matrix of nested arrays, `_.zip.apply(...)` can transpose the matrix - * in a similar fashion. - * - * @static - * @memberOf _ - * @category Arrays - * @param {Array} [array1, array2, ...] Arrays to process. - * @returns {Array} Returns a new array of grouped elements. - * @example - * - * _.zip(['moe', 'larry'], [30, 40], [true, false]); - * // => [['moe', 30, true], ['larry', 40, false]] - */ - function zip(array) { - var index = -1, - length = array ? max(pluck(arguments, 'length')) : 0, - result = Array(length); + /** + * Computes the union of the passed-in arrays using strict equality for + * comparisons, i.e. `===`. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} [array1, array2, ...] Arrays to process. + * @returns {Array} Returns a new array of unique values, in order, that are + * present in one or more of the arrays. + * @example + * + * _.union([1, 2, 3], [101, 2, 1, 10], [2, 1]); + * // => [1, 2, 3, 101, 10] + */ + function union() { + return uniq(concat.apply(arrayRef, arguments)); + } - while (++index < length) { - result[index] = pluck(arguments, index); + /** + * Creates a duplicate-value-free version of the `array` using strict equality + * for comparisons, i.e. `===`. If the `array` is already sorted, passing `true` + * for `isSorted` will run a faster algorithm. If `callback` is passed, each + * element of `array` is passed through a callback` before uniqueness is computed. + * The `callback` is bound to `thisArg` and invoked with three arguments; (value, index, array). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @alias unique + * @category Arrays + * @param {Array} array The array to process. + * @param {Boolean} [isSorted=false] A flag to indicate that the `array` is already sorted. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Array} Returns a duplicate-value-free array. + * @example + * + * _.uniq([1, 2, 1, 3, 1]); + * // => [1, 2, 3] + * + * _.uniq([1, 1, 2, 2, 3], true); + * // => [1, 2, 3] + * + * _.uniq([1, 2, 1.5, 3, 2.5], function(num) { return Math.floor(num); }); + * // => [1, 2, 3] + * + * _.uniq([1, 2, 1.5, 3, 2.5], function(num) { return this.floor(num); }, Math); + * // => [1, 2, 3] + * + * // using "_.pluck" callback shorthand + * _.uniq([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x'); + * // => [{ 'x': 1 }, { 'x': 2 }] + */ + function uniq(array, isSorted, callback, thisArg) { + var index = -1, + length = array ? array.length : 0, + result = [], + seen = result; + + // juggle arguments + if (typeof isSorted != 'boolean' && isSorted != null) { + thisArg = callback; + callback = isSorted; + isSorted = false; + } + // init value cache for large arrays + var isLarge = !isSorted && length >= 75; + if (isLarge) { + var cache = {}; + } + if (callback != null) { + seen = []; + callback = lodash.createCallback(callback, thisArg); + } + while (++index < length) { + var value = array[index], + computed = callback ? callback(value, index, array) : value; + + if (isLarge) { + var key = String(computed); + var inited = hasOwnProperty.call(cache, key) + ? !(seen = cache[key]) + : (seen = cache[key] = []); + } + if (isSorted + ? !index || seen[seen.length - 1] !== computed + : inited || indexOf(seen, computed) < 0 + ) { + if (callback || isLarge) { + seen.push(computed); + } + result.push(value); + } + } + return result; } - return result; - } - /*--------------------------------------------------------------------------*/ + /** + * Creates an array with all occurrences of the passed values removed using + * strict equality for comparisons, i.e. `===`. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to filter. + * @param {Mixed} [value1, value2, ...] Values to remove. + * @returns {Array} Returns a new filtered array. + * @example + * + * _.without([1, 2, 1, 0, 3, 1, 4], 0, 1); + * // => [2, 3, 4] + */ + function without(array) { + var index = -1, + length = array ? array.length : 0, + contains = cachedContains(arguments, 1, 30), + result = []; - /** - * Creates a function that is restricted to executing `func` only after it is - * called `n` times. The `func` is executed with the `this` binding of the - * created function. - * - * @static - * @memberOf _ - * @category Functions - * @param {Number} n The number of times the function must be called before - * it is executed. - * @param {Function} func The function to restrict. - * @returns {Function} Returns the new restricted function. - * @example - * - * var renderNotes = _.after(notes.length, render); - * _.forEach(notes, function(note) { - * note.asyncSave({ 'success': renderNotes }); - * }); - * // `renderNotes` is run once, after all notes have saved - */ - function after(n, func) { - if (n < 1) { - return func(); + while (++index < length) { + var value = array[index]; + if (!contains(value)) { + result.push(value); + } + } + return result; } - return function() { - if (--n < 1) { - return func.apply(this, arguments); + + /** + * Groups the elements of each array at their corresponding indexes. Useful for + * separate data sources that are coordinated through matching array indexes. + * For a matrix of nested arrays, `_.zip.apply(...)` can transpose the matrix + * in a similar fashion. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} [array1, array2, ...] Arrays to process. + * @returns {Array} Returns a new array of grouped elements. + * @example + * + * _.zip(['moe', 'larry'], [30, 40], [true, false]); + * // => [['moe', 30, true], ['larry', 40, false]] + */ + function zip(array) { + var index = -1, + length = array ? max(pluck(arguments, 'length')) : 0, + result = Array(length); + + while (++index < length) { + result[index] = pluck(arguments, index); } - }; - } + return result; + } - /** - * Creates a function that, when called, invokes `func` with the `this` - * binding of `thisArg` and prepends any additional `bind` arguments to those - * passed to the bound function. - * - * @static - * @memberOf _ - * @category Functions - * @param {Function} func The function to bind. - * @param {Mixed} [thisArg] The `this` binding of `func`. - * @param {Mixed} [arg1, arg2, ...] Arguments to be partially applied. - * @returns {Function} Returns the new bound function. - * @example - * - * var func = function(greeting) { - * return greeting + ' ' + this.name; - * }; - * - * func = _.bind(func, { 'name': 'moe' }, 'hi'); - * func(); - * // => 'hi moe' - */ - function bind(func, thisArg) { - // use `Function#bind` if it exists and is fast - // (in V8 `Function#bind` is slower except when partially applied) - return isBindFast || (nativeBind && arguments.length > 2) - ? nativeBind.call.apply(nativeBind, arguments) - : createBound(func, thisArg, slice(arguments, 2)); - } + /** + * Creates an object composed from arrays of `keys` and `values`. Pass either + * a single two dimensional array, i.e. `[[key1, value1], [key2, value2]]`, or + * two arrays, one of `keys` and one of corresponding `values`. + * + * @static + * @memberOf _ + * @alias object + * @category Arrays + * @param {Array} keys The array of keys. + * @param {Array} [values=[]] The array of values. + * @returns {Object} Returns an object composed of the given keys and + * corresponding values. + * @example + * + * _.zipObject(['moe', 'larry'], [30, 40]); + * // => { 'moe': 30, 'larry': 40 } + */ + function zipObject(keys, values) { + var index = -1, + length = keys ? keys.length : 0, + result = {}; - /** - * Binds methods on `object` to `object`, overwriting the existing method. - * Method names may be specified as individual arguments or as arrays of method - * names. If no method names are provided, all the function properties of `object` - * will be bound. - * - * @static - * @memberOf _ - * @category Functions - * @param {Object} object The object to bind and assign the bound methods to. - * @param {String} [methodName1, methodName2, ...] Method names on the object to bind. - * @returns {Object} Returns `object`. - * @example - * - * var view = { - * 'label': 'docs', - * 'onClick': function() { alert('clicked ' + this.label); } - * }; - * - * _.bindAll(view); - * jQuery('#docs').on('click', view.onClick); - * // => alerts 'clicked docs', when the button is clicked - */ - function bindAll(object) { - var funcs = concat.apply(arrayRef, arguments), - index = funcs.length > 1 ? 0 : (funcs = functions(object), -1), - length = funcs.length; - - while (++index < length) { - var key = funcs[index]; - object[key] = bind(object[key], object); + while (++index < length) { + var key = keys[index]; + if (values) { + result[key] = values[index]; + } else { + result[key[0]] = key[1]; + } + } + return result; } - return object; - } - /** - * Creates a function that, when called, invokes the method at `object[key]` - * and prepends any additional `bindKey` arguments to those passed to the bound - * function. This method differs from `_.bind` by allowing bound functions to - * reference methods that will be redefined or don't yet exist. - * See http://michaux.ca/articles/lazy-function-definition-pattern. - * - * @static - * @memberOf _ - * @category Functions - * @param {Object} object The object the method belongs to. - * @param {String} key The key of the method. - * @param {Mixed} [arg1, arg2, ...] Arguments to be partially applied. - * @returns {Function} Returns the new bound function. - * @example - * - * var object = { - * 'name': 'moe', - * 'greet': function(greeting) { - * return greeting + ' ' + this.name; - * } - * }; - * - * var func = _.bindKey(object, 'greet', 'hi'); - * func(); - * // => 'hi moe' - * - * object.greet = function(greeting) { - * return greeting + ', ' + this.name + '!'; - * }; - * - * func(); - * // => 'hi, moe!' - */ - function bindKey(object, key) { - return createBound(object, key, slice(arguments, 2)); - } + /*--------------------------------------------------------------------------*/ - /** - * Creates a function that is the composition of the passed functions, - * where each function consumes the return value of the function that follows. - * For example, composing the functions `f()`, `g()`, and `h()` produces `f(g(h()))`. - * Each function is executed with the `this` binding of the composed function. - * - * @static - * @memberOf _ - * @category Functions - * @param {Function} [func1, func2, ...] Functions to compose. - * @returns {Function} Returns the new composed function. - * @example - * - * var greet = function(name) { return 'hi ' + name; }; - * var exclaim = function(statement) { return statement + '!'; }; - * var welcome = _.compose(exclaim, greet); - * welcome('moe'); - * // => 'hi moe!' - */ - function compose() { - var funcs = arguments; - return function() { - var args = arguments, + /** + * If `n` is greater than `0`, a function is created that is restricted to + * executing `func`, with the `this` binding and arguments of the created + * function, only after it is called `n` times. If `n` is less than `1`, + * `func` is executed immediately, without a `this` binding or additional + * arguments, and its result is returned. + * + * @static + * @memberOf _ + * @category Functions + * @param {Number} n The number of times the function must be called before + * it is executed. + * @param {Function} func The function to restrict. + * @returns {Function} Returns the new restricted function. + * @example + * + * var renderNotes = _.after(notes.length, render); + * _.forEach(notes, function(note) { + * note.asyncSave({ 'success': renderNotes }); + * }); + * // `renderNotes` is run once, after all notes have saved + */ + function after(n, func) { + if (n < 1) { + return func(); + } + return function() { + if (--n < 1) { + return func.apply(this, arguments); + } + }; + } + + /** + * Creates a function that, when called, invokes `func` with the `this` + * binding of `thisArg` and prepends any additional `bind` arguments to those + * passed to the bound function. + * + * @static + * @memberOf _ + * @category Functions + * @param {Function} func The function to bind. + * @param {Mixed} [thisArg] The `this` binding of `func`. + * @param {Mixed} [arg1, arg2, ...] Arguments to be partially applied. + * @returns {Function} Returns the new bound function. + * @example + * + * var func = function(greeting) { + * return greeting + ' ' + this.name; + * }; + * + * func = _.bind(func, { 'name': 'moe' }, 'hi'); + * func(); + * // => 'hi moe' + */ + function bind(func, thisArg) { + // use `Function#bind` if it exists and is fast + // (in V8 `Function#bind` is slower except when partially applied) + return support.fastBind || (nativeBind && arguments.length > 2) + ? nativeBind.call.apply(nativeBind, arguments) + : createBound(func, thisArg, slice(arguments, 2)); + } + + /** + * Binds methods on `object` to `object`, overwriting the existing method. + * Method names may be specified as individual arguments or as arrays of method + * names. If no method names are provided, all the function properties of `object` + * will be bound. + * + * @static + * @memberOf _ + * @category Functions + * @param {Object} object The object to bind and assign the bound methods to. + * @param {String} [methodName1, methodName2, ...] Method names on the object to bind. + * @returns {Object} Returns `object`. + * @example + * + * var view = { + * 'label': 'docs', + * 'onClick': function() { alert('clicked ' + this.label); } + * }; + * + * _.bindAll(view); + * jQuery('#docs').on('click', view.onClick); + * // => alerts 'clicked docs', when the button is clicked + */ + function bindAll(object) { + var funcs = concat.apply(arrayRef, arguments), + index = funcs.length > 1 ? 0 : (funcs = functions(object), -1), length = funcs.length; - while (length--) { - args = [funcs[length].apply(this, args)]; + while (++index < length) { + var key = funcs[index]; + object[key] = bind(object[key], object); } - return args[0]; - }; - } + return object; + } - /** - * Creates a function that will delay the execution of `func` until after - * `wait` milliseconds have elapsed since the last time it was invoked. Pass - * `true` for `immediate` to cause debounce to invoke `func` on the leading, - * instead of the trailing, edge of the `wait` timeout. Subsequent calls to - * the debounced function will return the result of the last `func` call. - * - * @static - * @memberOf _ - * @category Functions - * @param {Function} func The function to debounce. - * @param {Number} wait The number of milliseconds to delay. - * @param {Boolean} immediate A flag to indicate execution is on the leading - * edge of the timeout. - * @returns {Function} Returns the new debounced function. - * @example - * - * var lazyLayout = _.debounce(calculateLayout, 300); - * jQuery(window).on('resize', lazyLayout); - */ - function debounce(func, wait, immediate) { - var args, - result, - thisArg, - timeoutId; - - function delayed() { - timeoutId = null; - if (!immediate) { - result = func.apply(thisArg, args); + /** + * Creates a function that, when called, invokes the method at `object[key]` + * and prepends any additional `bindKey` arguments to those passed to the bound + * function. This method differs from `_.bind` by allowing bound functions to + * reference methods that will be redefined or don't yet exist. + * See http://michaux.ca/articles/lazy-function-definition-pattern. + * + * @static + * @memberOf _ + * @category Functions + * @param {Object} object The object the method belongs to. + * @param {String} key The key of the method. + * @param {Mixed} [arg1, arg2, ...] Arguments to be partially applied. + * @returns {Function} Returns the new bound function. + * @example + * + * var object = { + * 'name': 'moe', + * 'greet': function(greeting) { + * return greeting + ' ' + this.name; + * } + * }; + * + * var func = _.bindKey(object, 'greet', 'hi'); + * func(); + * // => 'hi moe' + * + * object.greet = function(greeting) { + * return greeting + ', ' + this.name + '!'; + * }; + * + * func(); + * // => 'hi, moe!' + */ + function bindKey(object, key) { + return createBound(object, key, slice(arguments, 2), indicatorObject); + } + + /** + * Creates a function that is the composition of the passed functions, + * where each function consumes the return value of the function that follows. + * For example, composing the functions `f()`, `g()`, and `h()` produces `f(g(h()))`. + * Each function is executed with the `this` binding of the composed function. + * + * @static + * @memberOf _ + * @category Functions + * @param {Function} [func1, func2, ...] Functions to compose. + * @returns {Function} Returns the new composed function. + * @example + * + * var greet = function(name) { return 'hi ' + name; }; + * var exclaim = function(statement) { return statement + '!'; }; + * var welcome = _.compose(exclaim, greet); + * welcome('moe'); + * // => 'hi moe!' + */ + function compose() { + var funcs = arguments; + return function() { + var args = arguments, + length = funcs.length; + + while (length--) { + args = [funcs[length].apply(this, args)]; + } + return args[0]; + }; + } + + /** + * Produces a callback bound to an optional `thisArg`. If `func` is a property + * name, the created callback will return the property value for a given element. + * If `func` is an object, the created callback will return `true` for elements + * that contain the equivalent object properties, otherwise it will return `false`. + * + * Note: All Lo-Dash methods, that accept a `callback` argument, use `_.createCallback`. + * + * @static + * @memberOf _ + * @category Functions + * @param {Mixed} [func=identity] The value to convert to a callback. + * @param {Mixed} [thisArg] The `this` binding of the created callback. + * @param {Number} [argCount=3] The number of arguments the callback accepts. + * @returns {Function} Returns a callback function. + * @example + * + * var stooges = [ + * { 'name': 'moe', 'age': 40 }, + * { 'name': 'larry', 'age': 50 } + * ]; + * + * // wrap to create custom callback shorthands + * _.createCallback = _.wrap(_.createCallback, function(func, callback, thisArg) { + * var match = /^(.+?)__([gl]t)(.+)$/.exec(callback); + * return !match ? func(callback, thisArg) : function(object) { + * return match[2] == 'gt' ? object[match[1]] > match[3] : object[match[1]] < match[3]; + * }; + * }); + * + * _.filter(stooges, 'age__gt45'); + * // => [{ 'name': 'larry', 'age': 50 }] + * + * // create mixins with support for "_.pluck" and "_.where" callback shorthands + * _.mixin({ + * 'toLookup': function(collection, callback, thisArg) { + * callback = _.createCallback(callback, thisArg); + * return _.reduce(collection, function(result, value, index, collection) { + * return (result[callback(value, index, collection)] = value, result); + * }, {}); + * } + * }); + * + * _.toLookup(stooges, 'name'); + * // => { 'moe': { 'name': 'moe', 'age': 40 }, 'larry': { 'name': 'larry', 'age': 50 } } + */ + function createCallback(func, thisArg, argCount) { + if (func == null) { + return identity; } + var type = typeof func; + if (type != 'function') { + if (type != 'object') { + return function(object) { + return object[func]; + }; + } + var props = keys(func); + return function(object) { + var length = props.length, + result = false; + while (length--) { + if (!(result = isEqual(object[props[length]], func[props[length]], indicatorObject))) { + break; + } + } + return result; + }; + } + if (typeof thisArg != 'undefined') { + if (argCount === 1) { + return function(value) { + return func.call(thisArg, value); + }; + } + if (argCount === 2) { + return function(a, b) { + return func.call(thisArg, a, b); + }; + } + if (argCount === 4) { + return function(accumulator, value, index, collection) { + return func.call(thisArg, accumulator, value, index, collection); + }; + } + return function(value, index, collection) { + return func.call(thisArg, value, index, collection); + }; + } + return func; } - return function() { - var isImmediate = immediate && !timeoutId; - args = arguments; - thisArg = this; - clearTimeout(timeoutId); - timeoutId = setTimeout(delayed, wait); + /** + * Creates a function that will delay the execution of `func` until after + * `wait` milliseconds have elapsed since the last time it was invoked. Pass + * `true` for `immediate` to cause debounce to invoke `func` on the leading, + * instead of the trailing, edge of the `wait` timeout. Subsequent calls to + * the debounced function will return the result of the last `func` call. + * + * @static + * @memberOf _ + * @category Functions + * @param {Function} func The function to debounce. + * @param {Number} wait The number of milliseconds to delay. + * @param {Boolean} immediate A flag to indicate execution is on the leading + * edge of the timeout. + * @returns {Function} Returns the new debounced function. + * @example + * + * var lazyLayout = _.debounce(calculateLayout, 300); + * jQuery(window).on('resize', lazyLayout); + */ + function debounce(func, wait, immediate) { + var args, + result, + thisArg, + timeoutId; - if (isImmediate) { - result = func.apply(thisArg, args); + function delayed() { + timeoutId = null; + if (!immediate) { + result = func.apply(thisArg, args); + } } - return result; - }; - } + return function() { + var isImmediate = immediate && !timeoutId; + args = arguments; + thisArg = this; - /** - * Executes the `func` function after `wait` milliseconds. Additional arguments - * will be passed to `func` when it is invoked. - * - * @static - * @memberOf _ - * @category Functions - * @param {Function} func The function to delay. - * @param {Number} wait The number of milliseconds to delay execution. - * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the function with. - * @returns {Number} Returns the `setTimeout` timeout id. - * @example - * - * var log = _.bind(console.log, console); - * _.delay(log, 1000, 'logged later'); - * // => 'logged later' (Appears after one second.) - */ - function delay(func, wait) { - var args = slice(arguments, 2); - return setTimeout(function() { func.apply(undefined, args); }, wait); - } + clearTimeout(timeoutId); + timeoutId = setTimeout(delayed, wait); - /** - * Defers executing the `func` function until the current call stack has cleared. - * Additional arguments will be passed to `func` when it is invoked. - * - * @static - * @memberOf _ - * @category Functions - * @param {Function} func The function to defer. - * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the function with. - * @returns {Number} Returns the `setTimeout` timeout id. - * @example - * - * _.defer(function() { alert('deferred'); }); - * // returns from the function before `alert` is called - */ - function defer(func) { - var args = slice(arguments, 1); - return setTimeout(function() { func.apply(undefined, args); }, 1); - } - // use `setImmediate` if it's available in Node.js - if (isV8 && freeModule && typeof setImmediate == 'function') { - defer = bind(setImmediate, window); - } + if (isImmediate) { + result = func.apply(thisArg, args); + } + return result; + }; + } - /** - * Creates a function that memoizes the result of `func`. If `resolver` is - * passed, it will be used to determine the cache key for storing the result - * based on the arguments passed to the memoized function. By default, the first - * argument passed to the memoized function is used as the cache key. The `func` - * is executed with the `this` binding of the memoized function. - * - * @static - * @memberOf _ - * @category Functions - * @param {Function} func The function to have its output memoized. - * @param {Function} [resolver] A function used to resolve the cache key. - * @returns {Function} Returns the new memoizing function. - * @example - * - * var fibonacci = _.memoize(function(n) { - * return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2); - * }); - */ - function memoize(func, resolver) { - var cache = {}; - return function() { - var key = (resolver ? resolver.apply(this, arguments) : arguments[0]) + ''; - return hasOwnProperty.call(cache, key) - ? cache[key] - : (cache[key] = func.apply(this, arguments)); - }; - } + /** + * Defers executing the `func` function until the current call stack has cleared. + * Additional arguments will be passed to `func` when it is invoked. + * + * @static + * @memberOf _ + * @category Functions + * @param {Function} func The function to defer. + * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the function with. + * @returns {Number} Returns the timer id. + * @example + * + * _.defer(function() { alert('deferred'); }); + * // returns from the function before `alert` is called + */ + function defer(func) { + var args = slice(arguments, 1); + return setTimeout(function() { func.apply(undefined, args); }, 1); + } + // use `setImmediate` if it's available in Node.js + if (isV8 && freeModule && typeof setImmediate == 'function') { + defer = bind(setImmediate, context); + } - /** - * Creates a function that is restricted to execute `func` once. Repeat calls to - * the function will return the value of the first call. The `func` is executed - * with the `this` binding of the created function. - * - * @static - * @memberOf _ - * @category Functions - * @param {Function} func The function to restrict. - * @returns {Function} Returns the new restricted function. - * @example - * - * var initialize = _.once(createApplication); - * initialize(); - * initialize(); - * // `initialize` executes `createApplication` once - */ - function once(func) { - var ran, - result; + /** + * Executes the `func` function after `wait` milliseconds. Additional arguments + * will be passed to `func` when it is invoked. + * + * @static + * @memberOf _ + * @category Functions + * @param {Function} func The function to delay. + * @param {Number} wait The number of milliseconds to delay execution. + * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the function with. + * @returns {Number} Returns the timer id. + * @example + * + * var log = _.bind(console.log, console); + * _.delay(log, 1000, 'logged later'); + * // => 'logged later' (Appears after one second.) + */ + function delay(func, wait) { + var args = slice(arguments, 2); + return setTimeout(function() { func.apply(undefined, args); }, wait); + } - return function() { - if (ran) { - return result; - } - ran = true; - result = func.apply(this, arguments); + /** + * Creates a function that memoizes the result of `func`. If `resolver` is + * passed, it will be used to determine the cache key for storing the result + * based on the arguments passed to the memoized function. By default, the first + * argument passed to the memoized function is used as the cache key. The `func` + * is executed with the `this` binding of the memoized function. + * + * @static + * @memberOf _ + * @category Functions + * @param {Function} func The function to have its output memoized. + * @param {Function} [resolver] A function used to resolve the cache key. + * @returns {Function} Returns the new memoizing function. + * @example + * + * var fibonacci = _.memoize(function(n) { + * return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2); + * }); + */ + function memoize(func, resolver) { + var cache = {}; + return function() { + var key = String(resolver ? resolver.apply(this, arguments) : arguments[0]); + return hasOwnProperty.call(cache, key) + ? cache[key] + : (cache[key] = func.apply(this, arguments)); + }; + } - // clear the `func` variable so the function may be garbage collected - func = null; - return result; - }; - } + /** + * Creates a function that is restricted to execute `func` once. Repeat calls to + * the function will return the value of the first call. The `func` is executed + * with the `this` binding of the created function. + * + * @static + * @memberOf _ + * @category Functions + * @param {Function} func The function to restrict. + * @returns {Function} Returns the new restricted function. + * @example + * + * var initialize = _.once(createApplication); + * initialize(); + * initialize(); + * // `initialize` executes `createApplication` once + */ + function once(func) { + var ran, + result; - /** - * Creates a function that, when called, invokes `func` with any additional - * `partial` arguments prepended to those passed to the new function. This - * method is similar to `_.bind`, except it does **not** alter the `this` binding. - * - * @static - * @memberOf _ - * @category Functions - * @param {Function} func The function to partially apply arguments to. - * @param {Mixed} [arg1, arg2, ...] Arguments to be partially applied. - * @returns {Function} Returns the new partially applied function. - * @example - * - * var greet = function(greeting, name) { return greeting + ' ' + name; }; - * var hi = _.partial(greet, 'hi'); - * hi('moe'); - * // => 'hi moe' - */ - function partial(func) { - return createBound(func, slice(arguments, 1)); - } + return function() { + if (ran) { + return result; + } + ran = true; + result = func.apply(this, arguments); - /** - * This method is similar to `_.partial`, except that `partial` arguments are - * appended to those passed to the new function. - * - * @static - * @memberOf _ - * @category Functions - * @param {Function} func The function to partially apply arguments to. - * @param {Mixed} [arg1, arg2, ...] Arguments to be partially applied. - * @returns {Function} Returns the new partially applied function. - * @example - * - * var defaultsDeep = _.partialRight(_.merge, _.defaults); - * - * var options = { - * 'variable': 'data', - * 'imports': { 'jq': $ } - * }; - * - * defaultsDeep(options, _.templateSettings); - * - * options.variable - * // => 'data' - * - * options.imports - * // => { '_': _, 'jq': $ } - */ - function partialRight(func) { - return createBound(func, slice(arguments, 1), null, indicatorObject); - } + // clear the `func` variable so the function may be garbage collected + func = null; + return result; + }; + } - /** - * Creates a function that, when executed, will only call the `func` - * function at most once per every `wait` milliseconds. If the throttled - * function is invoked more than once during the `wait` timeout, `func` will - * also be called on the trailing edge of the timeout. Subsequent calls to the - * throttled function will return the result of the last `func` call. - * - * @static - * @memberOf _ - * @category Functions - * @param {Function} func The function to throttle. - * @param {Number} wait The number of milliseconds to throttle executions to. - * @returns {Function} Returns the new throttled function. - * @example - * - * var throttled = _.throttle(updatePosition, 100); - * jQuery(window).on('scroll', throttled); - */ - function throttle(func, wait) { - var args, - result, - thisArg, - timeoutId, - lastCalled = 0; - - function trailingCall() { - lastCalled = new Date; - timeoutId = null; - result = func.apply(thisArg, args); + /** + * Creates a function that, when called, invokes `func` with any additional + * `partial` arguments prepended to those passed to the new function. This + * method is similar to `_.bind`, except it does **not** alter the `this` binding. + * + * @static + * @memberOf _ + * @category Functions + * @param {Function} func The function to partially apply arguments to. + * @param {Mixed} [arg1, arg2, ...] Arguments to be partially applied. + * @returns {Function} Returns the new partially applied function. + * @example + * + * var greet = function(greeting, name) { return greeting + ' ' + name; }; + * var hi = _.partial(greet, 'hi'); + * hi('moe'); + * // => 'hi moe' + */ + function partial(func) { + return createBound(func, slice(arguments, 1)); } - return function() { - var now = new Date, - remaining = wait - (now - lastCalled); - args = arguments; - thisArg = this; + /** + * This method is similar to `_.partial`, except that `partial` arguments are + * appended to those passed to the new function. + * + * @static + * @memberOf _ + * @category Functions + * @param {Function} func The function to partially apply arguments to. + * @param {Mixed} [arg1, arg2, ...] Arguments to be partially applied. + * @returns {Function} Returns the new partially applied function. + * @example + * + * var defaultsDeep = _.partialRight(_.merge, _.defaults); + * + * var options = { + * 'variable': 'data', + * 'imports': { 'jq': $ } + * }; + * + * defaultsDeep(options, _.templateSettings); + * + * options.variable + * // => 'data' + * + * options.imports + * // => { '_': _, 'jq': $ } + */ + function partialRight(func) { + return createBound(func, slice(arguments, 1), null, indicatorObject); + } - if (remaining <= 0) { - clearTimeout(timeoutId); + /** + * Creates a function that, when executed, will only call the `func` + * function at most once per every `wait` milliseconds. If the throttled + * function is invoked more than once during the `wait` timeout, `func` will + * also be called on the trailing edge of the timeout. Subsequent calls to the + * throttled function will return the result of the last `func` call. + * + * @static + * @memberOf _ + * @category Functions + * @param {Function} func The function to throttle. + * @param {Number} wait The number of milliseconds to throttle executions to. + * @returns {Function} Returns the new throttled function. + * @example + * + * var throttled = _.throttle(updatePosition, 100); + * jQuery(window).on('scroll', throttled); + */ + function throttle(func, wait) { + var args, + result, + thisArg, + timeoutId, + lastCalled = 0; + + function trailingCall() { + lastCalled = new Date; timeoutId = null; - lastCalled = now; result = func.apply(thisArg, args); } - else if (!timeoutId) { - timeoutId = setTimeout(trailingCall, remaining); - } - return result; - }; - } + return function() { + var now = new Date, + remaining = wait - (now - lastCalled); + + args = arguments; + thisArg = this; + + if (remaining <= 0) { + clearTimeout(timeoutId); + timeoutId = null; + lastCalled = now; + result = func.apply(thisArg, args); + } + else if (!timeoutId) { + timeoutId = setTimeout(trailingCall, remaining); + } + return result; + }; + } - /** - * Creates a function that passes `value` to the `wrapper` function as its - * first argument. Additional arguments passed to the function are appended - * to those passed to the `wrapper` function. The `wrapper` is executed with - * the `this` binding of the created function. - * - * @static - * @memberOf _ - * @category Functions - * @param {Mixed} value The value to wrap. - * @param {Function} wrapper The wrapper function. - * @returns {Function} Returns the new function. - * @example - * - * var hello = function(name) { return 'hello ' + name; }; - * hello = _.wrap(hello, function(func) { - * return 'before, ' + func('moe') + ', after'; - * }); - * hello(); - * // => 'before, hello moe, after' - */ - function wrap(value, wrapper) { - return function() { - var args = [value]; - push.apply(args, arguments); - return wrapper.apply(this, args); - }; - } + /** + * Creates a function that passes `value` to the `wrapper` function as its + * first argument. Additional arguments passed to the function are appended + * to those passed to the `wrapper` function. The `wrapper` is executed with + * the `this` binding of the created function. + * + * @static + * @memberOf _ + * @category Functions + * @param {Mixed} value The value to wrap. + * @param {Function} wrapper The wrapper function. + * @returns {Function} Returns the new function. + * @example + * + * var hello = function(name) { return 'hello ' + name; }; + * hello = _.wrap(hello, function(func) { + * return 'before, ' + func('moe') + ', after'; + * }); + * hello(); + * // => 'before, hello moe, after' + */ + function wrap(value, wrapper) { + return function() { + var args = [value]; + push.apply(args, arguments); + return wrapper.apply(this, args); + }; + } - /*--------------------------------------------------------------------------*/ + /*--------------------------------------------------------------------------*/ - /** - * Converts the characters `&`, `<`, `>`, `"`, and `'` in `string` to their - * corresponding HTML entities. - * - * @static - * @memberOf _ - * @category Utilities - * @param {String} string The string to escape. - * @returns {String} Returns the escaped string. - * @example - * - * _.escape('Moe, Larry & Curly'); - * // => 'Moe, Larry & Curly' - */ - function escape(string) { - return string == null ? '' : (string + '').replace(reUnescapedHtml, escapeHtmlChar); - } + /** + * Converts the characters `&`, `<`, `>`, `"`, and `'` in `string` to their + * corresponding HTML entities. + * + * @static + * @memberOf _ + * @category Utilities + * @param {String} string The string to escape. + * @returns {String} Returns the escaped string. + * @example + * + * _.escape('Moe, Larry & Curly'); + * // => 'Moe, Larry & Curly' + */ + function escape(string) { + return string == null ? '' : String(string).replace(reUnescapedHtml, escapeHtmlChar); + } - /** - * This function returns the first argument passed to it. - * - * @static - * @memberOf _ - * @category Utilities - * @param {Mixed} value Any value. - * @returns {Mixed} Returns `value`. - * @example - * - * var moe = { 'name': 'moe' }; - * moe === _.identity(moe); - * // => true - */ - function identity(value) { - return value; - } + /** + * This function returns the first argument passed to it. + * + * @static + * @memberOf _ + * @category Utilities + * @param {Mixed} value Any value. + * @returns {Mixed} Returns `value`. + * @example + * + * var moe = { 'name': 'moe' }; + * moe === _.identity(moe); + * // => true + */ + function identity(value) { + return value; + } - /** - * Adds functions properties of `object` to the `lodash` function and chainable - * wrapper. - * - * @static - * @memberOf _ - * @category Utilities - * @param {Object} object The object of function properties to add to `lodash`. - * @example - * - * _.mixin({ - * 'capitalize': function(string) { - * return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase(); - * } - * }); - * - * _.capitalize('moe'); - * // => 'Moe' - * - * _('moe').capitalize(); - * // => 'Moe' - */ - function mixin(object) { - forEach(functions(object), function(methodName) { - var func = lodash[methodName] = object[methodName]; + /** + * Adds functions properties of `object` to the `lodash` function and chainable + * wrapper. + * + * @static + * @memberOf _ + * @category Utilities + * @param {Object} object The object of function properties to add to `lodash`. + * @example + * + * _.mixin({ + * 'capitalize': function(string) { + * return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase(); + * } + * }); + * + * _.capitalize('moe'); + * // => 'Moe' + * + * _('moe').capitalize(); + * // => 'Moe' + */ + function mixin(object) { + forEach(functions(object), function(methodName) { + var func = lodash[methodName] = object[methodName]; + + lodash.prototype[methodName] = function() { + var value = this.__wrapped__, + args = [value]; + + push.apply(args, arguments); + var result = func.apply(lodash, args); + return (value && typeof value == 'object' && value == result) + ? this + : new lodashWrapper(result); + }; + }); + } - lodash.prototype[methodName] = function() { - var args = [this.__wrapped__]; - push.apply(args, arguments); - return new lodash(func.apply(lodash, args)); - }; - }); - } + /** + * Reverts the '_' variable to its previous value and returns a reference to + * the `lodash` function. + * + * @static + * @memberOf _ + * @category Utilities + * @returns {Function} Returns the `lodash` function. + * @example + * + * var lodash = _.noConflict(); + */ + function noConflict() { + context._ = oldDash; + return this; + } - /** - * Reverts the '_' variable to its previous value and returns a reference to - * the `lodash` function. - * - * @static - * @memberOf _ - * @category Utilities - * @returns {Function} Returns the `lodash` function. - * @example - * - * var lodash = _.noConflict(); - */ - function noConflict() { - window._ = oldDash; - return this; - } + /** + * Converts the given `value` into an integer of the specified `radix`. + * + * Note: This method avoids differences in native ES3 and ES5 `parseInt` + * implementations. See http://es5.github.com/#E. + * + * @static + * @memberOf _ + * @category Utilities + * @param {Mixed} value The value to parse. + * @returns {Number} Returns the new integer value. + * @example + * + * _.parseInt('08'); + * // => 8 + */ + var parseInt = nativeParseInt('08') == 8 ? nativeParseInt : function(value, radix) { + // Firefox and Opera still follow the ES3 specified implementation of `parseInt` + return nativeParseInt(isString(value) ? value.replace(reLeadingZeros, '') : value, radix || 0); + }; - /** - * Produces a random number between `min` and `max` (inclusive). If only one - * argument is passed, a number between `0` and the given number will be returned. - * - * @static - * @memberOf _ - * @category Utilities - * @param {Number} [min=0] The minimum possible value. - * @param {Number} [max=1] The maximum possible value. - * @returns {Number} Returns a random number. - * @example - * - * _.random(0, 5); - * // => a number between 0 and 5 - * - * _.random(5); - * // => also a number between 0 and 5 - */ - function random(min, max) { - if (min == null && max == null) { - max = 1; + /** + * Produces a random number between `min` and `max` (inclusive). If only one + * argument is passed, a number between `0` and the given number will be returned. + * + * @static + * @memberOf _ + * @category Utilities + * @param {Number} [min=0] The minimum possible value. + * @param {Number} [max=1] The maximum possible value. + * @returns {Number} Returns a random number. + * @example + * + * _.random(0, 5); + * // => a number between 0 and 5 + * + * _.random(5); + * // => also a number between 0 and 5 + */ + function random(min, max) { + if (min == null && max == null) { + max = 1; + } + min = +min || 0; + if (max == null) { + max = min; + min = 0; + } + return min + floor(nativeRandom() * ((+max || 0) - min + 1)); } - min = +min || 0; - if (max == null) { - max = min; - min = 0; + + /** + * Resolves the value of `property` on `object`. If `property` is a function, + * it will be invoked with the `this` binding of `object` and its result returned, + * else the property value is returned. If `object` is falsey, then `undefined` + * is returned. + * + * @static + * @memberOf _ + * @category Utilities + * @param {Object} object The object to inspect. + * @param {String} property The property to get the value of. + * @returns {Mixed} Returns the resolved value. + * @example + * + * var object = { + * 'cheese': 'crumpets', + * 'stuff': function() { + * return 'nonsense'; + * } + * }; + * + * _.result(object, 'cheese'); + * // => 'crumpets' + * + * _.result(object, 'stuff'); + * // => 'nonsense' + */ + function result(object, property) { + var value = object ? object[property] : undefined; + return isFunction(value) ? object[property]() : value; } - return min + floor(nativeRandom() * ((+max || 0) - min + 1)); - } - /** - * Resolves the value of `property` on `object`. If `property` is a function, - * it will be invoked and its result returned, else the property value is - * returned. If `object` is falsey, then `null` is returned. - * - * @static - * @memberOf _ - * @category Utilities - * @param {Object} object The object to inspect. - * @param {String} property The property to get the value of. - * @returns {Mixed} Returns the resolved value. - * @example - * - * var object = { - * 'cheese': 'crumpets', - * 'stuff': function() { - * return 'nonsense'; - * } - * }; - * - * _.result(object, 'cheese'); - * // => 'crumpets' - * - * _.result(object, 'stuff'); - * // => 'nonsense' - */ - function result(object, property) { - var value = object ? object[property] : undefined; - return isFunction(value) ? object[property]() : value; - } + /** + * A micro-templating method that handles arbitrary delimiters, preserves + * whitespace, and correctly escapes quotes within interpolated code. + * + * Note: In the development build, `_.template` utilizes sourceURLs for easier + * debugging. See http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl + * + * Note: Lo-Dash may be used in Chrome extensions by either creating a `lodash csp` + * build and using precompiled templates, or loading Lo-Dash in a sandbox. + * + * For more information on precompiling templates see: + * http://lodash.com/#custom-builds + * + * For more information on Chrome extension sandboxes see: + * http://developer.chrome.com/stable/extensions/sandboxingEval.html + * + * @static + * @memberOf _ + * @category Utilities + * @param {String} text The template text. + * @param {Obect} data The data object used to populate the text. + * @param {Object} options The options object. + * escape - The "escape" delimiter regexp. + * evaluate - The "evaluate" delimiter regexp. + * interpolate - The "interpolate" delimiter regexp. + * sourceURL - The sourceURL of the template's compiled source. + * variable - The data object variable name. + * @returns {Function|String} Returns a compiled function when no `data` object + * is given, else it returns the interpolated text. + * @example + * + * // using a compiled template + * var compiled = _.template('hello <%= name %>'); + * compiled({ 'name': 'moe' }); + * // => 'hello moe' + * + * var list = '<% _.forEach(people, function(name) { %>
  • <%= name %>
  • <% }); %>'; + * _.template(list, { 'people': ['moe', 'larry'] }); + * // => '
  • moe
  • larry
  • ' + * + * // using the "escape" delimiter to escape HTML in data property values + * _.template('<%- value %>', { 'value': ' + diff --git a/perf/perf.js b/perf/perf.js index 4ff9cf5bc9..42d72b5495 100644 --- a/perf/perf.js +++ b/perf/perf.js @@ -14,7 +14,9 @@ ); var last = result[result.length - 1]; - result = (result.length > min && last != 'test.js') ? last : '../lodash.js'; + result = (result.length > min && !/perf(?:\.js)?$/.test(last)) + ? last + : '../lodash.js'; try { result = require('fs').realpathSync(result); @@ -23,42 +25,52 @@ return result; }()); - /** Load Benchmark.js */ - var Benchmark = - window.Benchmark || ( - Benchmark = load('../vendor/benchmark.js/benchmark.js') || window.Benchmark, - Benchmark.Benchmark || Benchmark - ); - /** Load Lo-Dash */ - var lodash = - window.lodash || ( - lodash = load(filePath) || window._, - lodash = lodash._ || lodash, - lodash.noConflict() - ); + var lodash = window.lodash || (window.lodash = ( + lodash = load(filePath) || window._, + lodash = lodash._ || lodash, + lodash.noConflict() + )); + + /** Load Benchmark.js */ + var Benchmark = window.Benchmark || (window.Benchmark = ( + Benchmark = load('../vendor/benchmark.js/benchmark.js') || window.Benchmark, + Benchmark = Benchmark.Benchmark || Benchmark, + Benchmark.runInContext(lodash.extend({}, window, { '_': lodash })) + )); /** Load Underscore */ - var _ = - window._ || ( - _ = load('../vendor/underscore/underscore.js') || window._, - _._ || _ - ); + var _ = window._ || (window._ = ( + _ = load('../vendor/underscore/underscore.js') || window._, + _._ || _ + )); /** Used to access the Firebug Lite panel (set by `run`) */ var fbPanel; + /** Used to match path separators */ + var rePathSeparator = /[\/\\]/; + + /** Used to detect primitive types */ + var rePrimitive = /^(?:boolean|number|string|undefined)$/; + + /** Used to match RegExp special characters */ + var reSpecialChars = /[.*+?^=!:${}()|[\]\/\\]/g; + /** Used to score performance */ var score = { 'a': 0, 'b': 0 }; /** Used to queue benchmark suites */ var suites = []; + /** Used to resolve a value's internal [[Class]] */ + var toString = Object.prototype.toString; + /** The `ui` object */ - var ui = window.ui || { + var ui = window.ui || ({ 'buildPath': basename(filePath, '.js'), 'otherPath': 'underscore' - }; + }); /** The Lo-Dash build basename */ var buildName = basename(ui.buildPath, '.js'); @@ -66,15 +78,14 @@ /** The other library basename */ var otherName = basename(ui.otherPath, '.js'); - /** Expose functions to the global object */ - window._ = _; - window.Benchmark = Benchmark; - window.lodash = lodash; + /** Detect if in a browser environment */ + var isBrowser = isHostType(window, 'document') && isHostType(window, 'navigator'); - /** Add `console.log()` support for Narwhal and RingoJS */ - if (!window.console && window.print) { - window.console = { 'log': window.print }; - } + /** Detect Java environment */ + var isJava = !isBrowser && /Java/.test(toString.call(window.java)); + + /** Add `console.log()` support for Narwhal, Rhino, and RingoJS */ + var console = window.console || (window.console = { 'log': window.print }); /*--------------------------------------------------------------------------*/ @@ -88,10 +99,10 @@ * @returns {String} Returns the basename. */ function basename(filePath, extension) { - var result = (filePath || '').split(/[\\/]/).pop(); - return arguments.length < 2 + var result = (filePath || '').split(rePathSeparator).pop(); + return (arguments.length < 2) ? result - : result.replace(RegExp(extension.replace(/[.*+?^=!:${}()|[\]\/\\]/g, '\\$&') + '$'), ''); + : result.replace(RegExp(extension.replace(reSpecialChars, '\\$&') + '$'), ''); } /** @@ -107,6 +118,24 @@ return isFinite(result) ? result : 0; } + /** + * Host objects can return type values that are different from their actual + * data type. The objects we are concerned with usually return non-primitive + * types of "object", "function", or "unknown". + * + * @private + * @param {Mixed} object The owner of the property. + * @param {String} property The property to check. + * @returns {Boolean} Returns `true` if the property value is a non-primitive, else `false`. + */ + function isHostType(object, property) { + if (object == null) { + return false; + } + var type = typeof object[property]; + return !rePrimitive.test(type) && (type != 'object' || !!object[property]); + } + /** * Logs text to the console. * @@ -132,7 +161,7 @@ fbPanel.getElementById('fbPanel1'); log('\nSit back and relax, this may take a while.'); - suites[0].run(); + suites[0].run({ 'async': !isJava }); } /*--------------------------------------------------------------------------*/ @@ -175,7 +204,7 @@ if (suites.length) { // run next suite - suites[0].run(); + suites[0].run({ 'async': !isJava }); } else { var fastestTotalHz = Math.max(score.a, score.b), @@ -199,8 +228,7 @@ lodash.extend(Benchmark.options, { 'async': true, 'setup': '\ - var window = Function("return this || global")(),\ - _ = window._,\ + var _ = window._,\ lodash = window.lodash,\ belt = this.name == "Lo-Dash" ? lodash : _;\ \ @@ -222,18 +250,18 @@ }\ \ if (typeof bind != "undefined") {\ - var contextObject = { "name": "moe" },\ + var thisArg = { "name": "moe" },\ ctor = function() {};\ \ var func = function(greeting, punctuation) {\ return greeting + ", " + this.name + (punctuation || ".");\ };\ \ - var lodashBoundNormal = lodash.bind(func, contextObject),\ - lodashBoundPartial = lodash.bind(func, contextObject, "hi");\ + var _boundNormal = _.bind(func, thisArg),\ + _boundPartial = _.bind(func, thisArg, "hi");\ \ - var _boundNormal = _.bind(func, contextObject),\ - _boundPartial = _.bind(func, contextObject, "hi");\ + var lodashBoundNormal = lodash.bind(func, thisArg),\ + lodashBoundPartial = lodash.bind(func, thisArg, "hi");\ }\ \ if (typeof bindAll != "undefined") {\ @@ -248,6 +276,10 @@ }, {});\ }\ }\ + if (typeof chaining != "undefined") {\ + var _chaining = _.chain ? _(numbers).chain() : _(numbers),\ + lodashChaining = lodash(numbers);\ + }\ if (typeof compact != "undefined") {\ var uncompacted = numbers.slice();\ uncompacted[2] = false;\ @@ -445,16 +477,68 @@ \ var settingsObject = { "variable": "data" };\ \ - var lodashTpl = lodash.template(tpl),\ - lodashTplVerbose = lodash.template(tplVerbose, null, settingsObject);\ - \ var _tpl = _.template(tpl),\ _tplVerbose = _.template(tplVerbose, null, settingsObject);\ + \ + var lodashTpl = lodash.template(tpl),\ + lodashTplVerbose = lodash.template(tplVerbose, null, settingsObject);\ + }\ + if (typeof where != "undefined") {\ + var _findWhere = _.findWhere || _.find,\ + lodashFindWhere = lodash.findWhere || lodash.find,\ + whereObject = { "num": 9 };\ }' }); /*--------------------------------------------------------------------------*/ + suites.push( + Benchmark.Suite('`_(...)` with a number') + .add(buildName, '\ + lodash(2)' + ) + .add(otherName, '\ + _(2)' + ) + ); + + suites.push( + Benchmark.Suite('`_(...)` with an array') + .add(buildName, '\ + lodash(numbers)' + ) + .add(otherName, '\ + _(numbers)' + ) + ); + + suites.push( + Benchmark.Suite('`_(...)` with an object') + .add(buildName, '\ + lodash(object)' + ) + .add(otherName, '\ + _(object)' + ) + ); + + // avoid Underscore induced `OutOfMemoryError` in Rhino, Narwhal, and Ringo + if (!isJava) { + suites.push( + Benchmark.Suite('`_(...).tap(...)`') + .add(buildName, { + 'fn': 'lodashChaining.tap(lodash.identity)', + 'teardown': 'function chaining(){}' + }) + .add(otherName, { + 'fn': '_chaining.tap(_.identity)', + 'teardown': 'function chaining(){}' + }) + ); + } + + /*--------------------------------------------------------------------------*/ + suites.push( Benchmark.Suite('`_.bind` (uses native `Function#bind` if available and inferred fast)') .add(buildName, { @@ -650,13 +734,13 @@ ); suites.push( - Benchmark.Suite('`_.difference` iterating 30 elements') + Benchmark.Suite('`_.difference` iterating 100 elements') .add(buildName, { - 'fn': 'lodash.difference(thirtyValues, thirtyValues2)', + 'fn': 'lodash.difference(hundredValues, hundredValues2)', 'teardown': 'function multiArrays(){}' }) .add(otherName, { - 'fn': '_.difference(thirtyValues, thirtyValues2)', + 'fn': '_.difference(hundredValues, hundredValues2)', 'teardown': 'function multiArrays(){}' }) ); @@ -839,15 +923,20 @@ ) ); - suites.push( - Benchmark.Suite('`_.find` with `properties`') - .add(buildName, '\ - lodash.find(objects, { "num": 9 });' - ) - .add(otherName, '\ - _.findWhere(objects, { "num": 9 });' - ) - ); + // avoid Underscore induced `OutOfMemoryError` in Rhino, Narwhal, and Ringo + if (!isJava) { + suites.push( + Benchmark.Suite('`_.find` with `properties`') + .add(buildName, { + 'fn': 'lodashFindWhere(objects, whereObject)', + 'teardown': 'function where(){}' + }) + .add(otherName, { + 'fn': '_findWhere(objects, whereObject)', + 'teardown': 'function where(){}' + }) + ); + } /*--------------------------------------------------------------------------*/ @@ -1228,7 +1317,7 @@ 'teardown': 'function omit(){}' }) .add(otherName, { - 'fn': 'result = _.omit(wordToNumber, words)', + 'fn': '_.omit(wordToNumber, words)', 'teardown': 'function omit(){}' }) ); @@ -1291,13 +1380,13 @@ Benchmark.Suite('`_.reduce` iterating an object') .add(buildName, '\ lodash.reduce(object, function(result, value, key) {\ - result.push([key, value]);\ + result.push(key, value);\ return result;\ }, []);' ) .add(otherName, '\ _.reduce(object, function(result, value, key) {\ - result.push([key, value]);\ + result.push(key, value);\ return result;\ }, []);' ) @@ -1325,13 +1414,13 @@ Benchmark.Suite('`_.reduceRight` iterating an object') .add(buildName, '\ lodash.reduceRight(object, function(result, value, key) {\ - result.push([key, value]);\ + result.push(key, value);\ return result;\ }, []);' ) .add(otherName, '\ _.reduceRight(object, function(result, value, key) {\ - result.push([key, value]);\ + result.push(key, value);\ return result;\ }, []);' ) @@ -1677,12 +1766,14 @@ suites.push( Benchmark.Suite('`_.where`') - .add(buildName, '\ - lodash.where(objects, { "num": 9 });' - ) - .add(otherName, '\ - _.where(objects, { "num": 9 });' - ) + .add(buildName, { + 'fn': 'lodash.where(objects, whereObject);', + 'teardown': 'function where(){}' + }) + .add(otherName, { + 'fn': '_.where(objects, whereObject);', + 'teardown': 'function where(){}' + }) ); /*--------------------------------------------------------------------------*/ @@ -1714,7 +1805,6 @@ if (Benchmark.platform + '') { log(Benchmark.platform); } - // in the browser, expose `run` to be called later if (window.document && !window.phantom) { window.run = run; diff --git a/perf/run-perf.sh b/perf/run-perf.sh index 8593cd6bd7..b1406315b4 100755 --- a/perf/run-perf.sh +++ b/perf/run-perf.sh @@ -3,7 +3,7 @@ cd "$(dirname "$0")" echo "Running performance suite in node..." node perf.js ../dist/lodash.js && node perf.js ../dist/lodash.min.js -for cmd in rhino narwhal ringo phantomjs; do +for cmd in rhino "rhino -require" narwhal ringo phantomjs; do echo "" echo "Running performance suite in $cmd..." $cmd perf.js ../dist/lodash.compat.js && $cmd perf.js ../dist/lodash.compat.min.js diff --git a/test/index.html b/test/index.html index 182971f218..d813aa57f9 100644 --- a/test/index.html +++ b/test/index.html @@ -4,12 +4,18 @@ Lo-Dash Test Suite + -
    - + +
    +
    ``` @@ -68,25 +71,14 @@ load('benchmark.js'); In an AMD loader like [RequireJS](http://requirejs.org/): ```js -require({ - 'paths': { - 'benchmark': 'path/to/benchmark' - } -}, -['benchmark'], function(Benchmark) { - console.log(Benchmark.version); -}); - -// or with platform.js -// https://github.com/bestiejs/platform.js require({ 'paths': { 'benchmark': 'path/to/benchmark', + 'lodash': 'path/to/lodash', 'platform': 'path/to/platform' } }, -['benchmark', 'platform'], function(Benchmark, platform) { - Benchmark.platform = platform; +['benchmark'], function(Benchmark) { console.log(Benchmark.platform.name); }); ``` @@ -108,7 +100,7 @@ suite.add('RegExp#test', function() { console.log(String(event.target)); }) .on('complete', function() { - console.log('Fastest is ' + this.filter('fastest').pluck('name')); + console.log('Fastest is ' + _.pluck(this.filter('fastest'), 'name')); }) // run async .run({ 'async': true }); @@ -121,7 +113,7 @@ suite.add('RegExp#test', function() { ## BestieJS -Benchmark.js is part of the BestieJS *"Best in Class"* module collection. This means we promote solid browser/environment support, ES5 precedents, unit testing, and plenty of documentation. +Benchmark.js is part of the BestieJS *"Best in Class"* module collection. This means we promote solid browser/environment support, ES5+ precedents, unit testing, and plenty of documentation. ## Authors diff --git a/vendor/benchmark.js/benchmark.js b/vendor/benchmark.js/benchmark.js index 6adf726ce4..eb274b420e 100644 --- a/vendor/benchmark.js/benchmark.js +++ b/vendor/benchmark.js/benchmark.js @@ -8,70 +8,37 @@ ;(function(window, undefined) { 'use strict'; - /** Used to assign each benchmark an incrimented id */ - var counter = 0; - - /** Detect DOM document object */ - var doc = isHostType(window, 'document') && document; - /** Detect free variable `define` */ var freeDefine = typeof define == 'function' && typeof define.amd == 'object' && define.amd && define; /** Detect free variable `exports` */ - var freeExports = typeof exports == 'object' && exports && - (typeof global == 'object' && global && global == global.global && (window = global), exports); + var freeExports = typeof exports == 'object' && exports; + + /** Detect free variable `module` */ + var freeModule = typeof module == 'object' && module && module.exports == freeExports && module; /** Detect free variable `require` */ var freeRequire = typeof require == 'function' && require; - /** Used to store the `Object` built-in in case it's overwritten later */ - var Object = window.Object; - - /** Used to crawl all properties regardless of enumerability */ - var getAllKeys = Object.getOwnPropertyNames; - - /** Used to get property descriptors */ - var getDescriptor = Object.getOwnPropertyDescriptor; - - /** Used in case an object doesn't have its own method */ - var hasOwnProperty = {}.hasOwnProperty; - - /** Used to check if an object is extensible */ - var isExtensible = Object.isExtensible || function() { return true; }; - - /** Used to access Wade Simmons' Node microtime module */ - var microtimeObject = req('microtime'); - - /** Used to access the browser's high resolution timer */ - var perfObject = isHostType(window, 'performance') && performance; - - /** Used to call the browser's high resolution timer */ - var perfName = perfObject && ( - perfObject.now && 'now' || - perfObject.webkitNow && 'webkitNow' - ); - - /** Used to access Node's high resolution timer */ - var processObject = isHostType(window, 'process') && process; - - /** Used to check if an own property is enumerable */ - var propertyIsEnumerable = {}.propertyIsEnumerable; - - /** Used to set property descriptors */ - var setDescriptor = Object.defineProperty; - - /** Used to resolve a value's internal [[Class]] */ - var toString = {}.toString; + /** Detect free variable `global` and use it as `window` */ + var freeGlobal = typeof global == 'object' && global; + if (freeGlobal.global === freeGlobal) { + window = freeGlobal; + } - /** Used to prevent a `removeChild` memory leak in IE < 9 */ - var trash = doc && doc.createElement('div'); + /** Used to assign each benchmark an incrimented id */ + var counter = 0; - /** Used to integrity check compiled tests */ - var uid = 'uid' + (+new Date); + /** Used to detect primitive types */ + var rePrimitive = /^(?:boolean|number|string|undefined)$/; - /** Used to avoid infinite recursion when methods call each other */ - var calledBy = {}; + /** Used to assign default `context` object properties */ + var contextProps = [ + 'Array', 'Date', 'Function', 'Math', 'Object', 'RegExp', 'String', '_', + 'clearTimeout', 'chrome', 'chromium', 'document', 'java', 'navigator', + 'performance', 'platform', 'process', 'runtime', 'setTimeout' + ]; /** Used to avoid hz of Infinity */ var divisors = { @@ -128,3833 +95,2769 @@ '30': [13, 23, 33, 43, 54, 65, 76, 87, 98, 109, 120, 131, 143, 154, 166, 177, 189, 200, 212, 223, 235, 247, 258, 270, 282, 293, 305, 317] }; + /*--------------------------------------------------------------------------*/ + /** - * An object used to flag environments/features. + * Create a new `Benchmark` function using the given `context` object. * * @static * @memberOf Benchmark - * @type Object - */ - var support = {}; + * @param {Object} [context=window] The context object. + * @returns {Function} Returns the `Benchmark` function. + */ + function runInContext(context) { + // exit early if unable to acquire lodash + var _ = context && context._ || req('lodash') || window._; + if (!_) { + Benchmark.runInContext = runInContext; + return Benchmark; + } + // Avoid issues with some ES3 environments that attempt to use values, named + // after built-in constructors like `Object`, for the creation of literals. + // ES5 clears this up by stating that literals must use built-in constructors. + // See http://es5.github.com/#x11.1.5. + context = context ? _.defaults(window.Object(), context, _.pick(window, contextProps)) : window; + + /** Native constructor references */ + var Array = context.Array, + Date = context.Date, + Function = context.Function, + Math = context.Math, + Object = context.Object, + RegExp = context.RegExp, + String = context.String; + + /** Used for `Array` and `Object` method references */ + var arrayRef = Array(), + objectRef = Object(); + + /** Native method shortcuts */ + var abs = Math.abs, + clearTimeout = context.clearTimeout, + floor = Math.floor, + log = Math.log, + max = Math.max, + min = Math.min, + pow = Math.pow, + setTimeout = context.setTimeout, + shift = arrayRef.shift, + slice = arrayRef.slice, + sqrt = Math.sqrt, + toString = objectRef.toString; + + /** Detect DOM document object */ + var doc = isHostType(context, 'document') && context.document; + + /** Used to access Wade Simmons' Node microtime module */ + var microtimeObject = req('microtime'); + + /** Used to access the browser's high resolution timer */ + var perfObject = isHostType(context, 'performance') && context.performance; + + /** Used to call the browser's high resolution timer */ + var perfName = perfObject && ( + perfObject.now && 'now' || + perfObject.webkitNow && 'webkitNow' + ); + + /** Used to access Node's high resolution timer */ + var processObject = isHostType(context, 'process') && context.process; + + /** Used to prevent a `removeChild` memory leak in IE < 9 */ + var trash = doc && doc.createElement('div'); + + /** Used to integrity check compiled tests */ + var uid = 'uid' + (+new Date); + + /** Used to avoid infinite recursion when methods call each other */ + var calledBy = {}; + + /** + * An object used to flag environments/features. + * + * @static + * @memberOf Benchmark + * @type Object + */ + var support = {}; + + (function() { + + /** + * Detect Adobe AIR. + * + * @memberOf Benchmark.support + * @type Boolean + */ + support.air = isClassOf(context.runtime, 'ScriptBridgingProxyObject'); + + /** + * Detect if in a browser environment. + * + * @memberOf Benchmark.support + * @type Boolean + */ + support.browser = doc && isHostType(context, 'navigator') && !isHostType(context, 'phantom'); + + /** + * Detect if Java is enabled/exposed. + * + * @memberOf Benchmark.support + * @type Boolean + */ + support.java = isClassOf(context.java, 'JavaPackage'); + + /** + * Detect if the Timers API exists. + * + * @memberOf Benchmark.support + * @type Boolean + */ + support.timeout = isHostType(context, 'setTimeout') && isHostType(context, 'clearTimeout'); - (function() { + /** + * Detect if functions support decompilation. + * + * @name decompilation + * @memberOf Benchmark.support + * @type Boolean + */ + try { + // Safari 2.x removes commas in object literals + // from Function#toString results + // http://webk.it/11609 + // Firefox 3.6 and Opera 9.25 strip grouping + // parentheses from Function#toString results + // http://bugzil.la/559438 + support.decompilation = Function( + 'return (' + (function(x) { return { 'x': '' + (1 + x) + '', 'y': 0 }; }) + ')' + )()(0).x === '1'; + } catch(e) { + support.decompilation = false; + } + }()); /** - * Detect Adobe AIR. + * Timer object used by `clock()` and `Deferred#resolve`. * - * @memberOf Benchmark.support - * @type Boolean + * @private + * @type Object */ - support.air = isClassOf(window.runtime, 'ScriptBridgingProxyObject'); + var timer = { + + /** + * The timer namespace object or constructor. + * + * @private + * @memberOf timer + * @type Function|Object + */ + 'ns': Date, + + /** + * Starts the deferred timer. + * + * @private + * @memberOf timer + * @param {Object} deferred The deferred instance. + */ + 'start': null, // lazy defined in `clock()` + + /** + * Stops the deferred timer. + * + * @private + * @memberOf timer + * @param {Object} deferred The deferred instance. + */ + 'stop': null // lazy defined in `clock()` + }; + + /*------------------------------------------------------------------------*/ /** - * Detect if `arguments` objects have the correct internal [[Class]] value. + * The Benchmark constructor. + * + * @constructor + * @param {String} name A name to identify the benchmark. + * @param {Function|String} fn The test to benchmark. + * @param {Object} [options={}] Options object. + * @example * - * @memberOf Benchmark.support - * @type Boolean + * // basic usage (the `new` operator is optional) + * var bench = new Benchmark(fn); + * + * // or using a name first + * var bench = new Benchmark('foo', fn); + * + * // or with options + * var bench = new Benchmark('foo', fn, { + * + * // displayed by Benchmark#toString if `name` is not available + * 'id': 'xyz', + * + * // called when the benchmark starts running + * 'onStart': onStart, + * + * // called after each run cycle + * 'onCycle': onCycle, + * + * // called when aborted + * 'onAbort': onAbort, + * + * // called when a test errors + * 'onError': onError, + * + * // called when reset + * 'onReset': onReset, + * + * // called when the benchmark completes running + * 'onComplete': onComplete, + * + * // compiled/called before the test loop + * 'setup': setup, + * + * // compiled/called after the test loop + * 'teardown': teardown + * }); + * + * // or name and options + * var bench = new Benchmark('foo', { + * + * // a flag to indicate the benchmark is deferred + * 'defer': true, + * + * // benchmark test function + * 'fn': function(deferred) { + * // call resolve() when the deferred test is finished + * deferred.resolve(); + * } + * }); + * + * // or options only + * var bench = new Benchmark({ + * + * // benchmark name + * 'name': 'foo', + * + * // benchmark test as a string + * 'fn': '[1,2,3,4].sort()' + * }); + * + * // a test's `this` binding is set to the benchmark instance + * var bench = new Benchmark('foo', function() { + * 'My name is '.concat(this.name); // My name is foo + * }); */ - support.argumentsClass = isClassOf(arguments, 'Arguments'); + function Benchmark(name, fn, options) { + var bench = this; + + // allow instance creation without the `new` operator + if (bench == null || bench.constructor != Benchmark) { + return new Benchmark(name, fn, options); + } + // juggle arguments + if (_.isPlainObject(name)) { + // 1 argument (options) + options = name; + } + else if (_.isFunction(name)) { + // 2 arguments (fn, options) + options = fn; + fn = name; + } + else if (_.isPlainObject(fn)) { + // 2 arguments (name, options) + options = fn; + fn = null; + bench.name = name; + } + else { + // 3 arguments (name, fn [, options]) + bench.name = name; + } + setOptions(bench, options); + + bench.id || (bench.id = ++counter); + bench.fn == null && (bench.fn = fn); + + bench.stats = cloneDeep(bench.stats); + bench.times = cloneDeep(bench.times); + } /** - * Detect if in a browser environment. + * The Deferred constructor. * - * @memberOf Benchmark.support - * @type Boolean + * @constructor + * @memberOf Benchmark + * @param {Object} clone The cloned benchmark instance. */ - support.browser = doc && isHostType(window, 'navigator'); + function Deferred(clone) { + var deferred = this; + if (deferred == null || deferred.constructor != Deferred) { + return new Deferred(clone); + } + deferred.benchmark = clone; + clock(deferred); + } /** - * Detect if strings support accessing characters by index. + * The Event constructor. * - * @memberOf Benchmark.support - * @type Boolean + * @constructor + * @memberOf Benchmark + * @param {String|Object} type The event type. */ - support.charByIndex = - // IE 8 supports indexes on string literals but not string objects - ('x'[0] + Object('x')[0]) == 'xx'; + function Event(type) { + var event = this; + return (event == null || event.constructor != Event) + ? new Event(type) + : (type instanceof Event + ? type + : _.extend(event, { 'timeStamp': +new Date }, typeof type == 'string' ? { 'type': type } : type) + ); + } /** - * Detect if strings have indexes as own properties. + * The Suite constructor. + * + * @constructor + * @memberOf Benchmark + * @param {String} name A name to identify the suite. + * @param {Object} [options={}] Options object. + * @example + * + * // basic usage (the `new` operator is optional) + * var suite = new Benchmark.Suite; + * + * // or using a name first + * var suite = new Benchmark.Suite('foo'); + * + * // or with options + * var suite = new Benchmark.Suite('foo', { + * + * // called when the suite starts running + * 'onStart': onStart, + * + * // called between running benchmarks + * 'onCycle': onCycle, + * + * // called when aborted + * 'onAbort': onAbort, + * + * // called when a test errors + * 'onError': onError, * - * @memberOf Benchmark.support - * @type Boolean + * // called when reset + * 'onReset': onReset, + * + * // called when the suite completes running + * 'onComplete': onComplete + * }); */ - support.charByOwnIndex = - // Narwhal, Rhino, RingoJS, IE 8, and Opera < 10.52 support indexes on - // strings but don't detect them as own properties - support.charByIndex && hasKey('x', '0'); + function Suite(name, options) { + var suite = this; + + // allow instance creation without the `new` operator + if (suite == null || suite.constructor != Suite) { + return new Suite(name, options); + } + // juggle arguments + if (isClassOf(name, 'Object')) { + // 1 argument (options) + options = name; + } else { + // 2 arguments (name [, options]) + suite.name = name; + } + setOptions(suite, options); + } + + /*------------------------------------------------------------------------*/ /** - * Detect if Java is enabled/exposed. + * A deep clone utility. * - * @memberOf Benchmark.support - * @type Boolean - */ - support.java = isClassOf(window.java, 'JavaPackage'); + * @private + * @param {Mixed} value The value to clone. + * @returns {Mixed} The cloned value. + */ + var cloneDeep = _.partialRight(_.cloneDeep, function(value) { + // do not clone non-Object objects + return (typeof value == 'object' && !_.isArray(value) && !_.isPlainObject(value)) + ? value + : undefined; + }); /** - * Detect if the Timers API exists. + * Creates a function from the given arguments string and body. * - * @memberOf Benchmark.support - * @type Boolean - */ - support.timeout = isHostType(window, 'setTimeout') && isHostType(window, 'clearTimeout'); + * @private + * @param {String} args The comma separated function arguments. + * @param {String} body The function body. + * @returns {Function} The new function. + */ + function createFunction() { + // lazy define + createFunction = function(args, body) { + var result, + anchor = freeDefine ? freeDefine.amd : Benchmark, + prop = uid + 'createFunction'; + + runScript((freeDefine ? 'define.amd.' : 'Benchmark.') + prop + '=function(' + args + '){' + body + '}'); + result = anchor[prop]; + delete anchor[prop]; + return result; + }; + // fix JaegerMonkey bug + // http://bugzil.la/639720 + createFunction = support.browser && (createFunction('', 'return"' + uid + '"') || noop)() == uid ? createFunction : Function; + return createFunction.apply(null, arguments); + } /** - * Detect if functions support decompilation. + * Delay the execution of a function based on the benchmark's `delay` property. * - * @name decompilation - * @memberOf Benchmark.support - * @type Boolean + * @private + * @param {Object} bench The benchmark instance. + * @param {Object} fn The function to execute. */ - try { - // Safari 2.x removes commas in object literals - // from Function#toString results - // http://webk.it/11609 - // Firefox 3.6 and Opera 9.25 strip grouping - // parentheses from Function#toString results - // http://bugzil.la/559438 - support.decompilation = Function( - 'return (' + (function(x) { return { 'x': '' + (1 + x) + '', 'y': 0 }; }) + ')' - )()(0).x === '1'; - } catch(e) { - support.decompilation = false; + function delay(bench, fn) { + bench._timerId = _.delay(fn, bench.delay * 1e3); } /** - * Detect ES5+ property descriptor API. + * Destroys the given element. * - * @name descriptors - * @memberOf Benchmark.support - * @type Boolean + * @private + * @param {Element} element The element to destroy. */ - try { - var o = {}; - support.descriptors = (setDescriptor(o, o, o), 'value' in getDescriptor(o, o)); - } catch(e) { - support.descriptors = false; + function destroyElement(element) { + trash.appendChild(element); + trash.innerHTML = ''; } /** - * Detect ES5+ Object.getOwnPropertyNames(). + * Gets the name of the first argument from a function's source. * - * @name getAllKeys - * @memberOf Benchmark.support - * @type Boolean + * @private + * @param {Function} fn The function. + * @returns {String} The argument name. */ - try { - support.getAllKeys = /\bvalueOf\b/.test(getAllKeys(Object.prototype)); - } catch(e) { - support.getAllKeys = false; + function getFirstArgument(fn) { + return (!_.has(fn, 'toString') && + (/^[\s(]*function[^(]*\(([^\s,)]+)/.exec(fn) || 0)[1]) || ''; } /** - * Detect if own properties are iterated before inherited properties (all but IE < 9). + * Computes the geometric mean (log-average) of a sample. + * See http://en.wikipedia.org/wiki/Geometric_mean#Relationship_with_arithmetic_mean_of_logarithms. * - * @name iteratesOwnLast - * @memberOf Benchmark.support - * @type Boolean + * @private + * @param {Array} sample The sample. + * @returns {Number} The geometric mean. */ - support.iteratesOwnFirst = (function() { - var props = []; - function ctor() { this.x = 1; } - ctor.prototype = { 'y': 1 }; - for (var prop in new ctor) { props.push(prop); } - return props[0] == 'x'; - }()); + function getGeometricMean(sample) { + return pow(Math.E, _.reduce(sample, function(sum, x) { + return sum + log(x); + }) / sample.length) || 0; + } /** - * Detect if a node's [[Class]] is resolvable (all but IE < 9) - * and that the JS engine errors when attempting to coerce an object to a - * string without a `toString` property value of `typeof` "function". + * Computes the arithmetic mean of a sample. * - * @name nodeClass - * @memberOf Benchmark.support - * @type Boolean + * @private + * @param {Array} sample The sample. + * @returns {Number} The mean. */ - try { - support.nodeClass = ({ 'toString': 0 } + '', toString.call(doc || 0) != '[object Object]'); - } catch(e) { - support.nodeClass = true; + function getMean(sample) { + return (_.reduce(sample, function(sum, x) { + return sum + x; + }) / sample.length) || 0; } - }()); - - /** - * Timer object used by `clock()` and `Deferred#resolve`. - * - * @private - * @type Object - */ - var timer = { - - /** - * The timer namespace object or constructor. - * - * @private - * @memberOf timer - * @type Function|Object - */ - 'ns': Date, - - /** - * Starts the deferred timer. - * - * @private - * @memberOf timer - * @param {Object} deferred The deferred instance. - */ - 'start': null, // lazy defined in `clock()` - - /** - * Stops the deferred timer. - * - * @private - * @memberOf timer - * @param {Object} deferred The deferred instance. - */ - 'stop': null // lazy defined in `clock()` - }; - - /** Shortcut for inverse results */ - var noArgumentsClass = !support.argumentsClass, - noCharByIndex = !support.charByIndex, - noCharByOwnIndex = !support.charByOwnIndex; - - /** Math shortcuts */ - var abs = Math.abs, - floor = Math.floor, - log = Math.log, - max = Math.max, - min = Math.min, - pow = Math.pow, - sqrt = Math.sqrt; - /*--------------------------------------------------------------------------*/ - - /** - * The Benchmark constructor. - * - * @constructor - * @param {String} name A name to identify the benchmark. - * @param {Function|String} fn The test to benchmark. - * @param {Object} [options={}] Options object. - * @example - * - * // basic usage (the `new` operator is optional) - * var bench = new Benchmark(fn); - * - * // or using a name first - * var bench = new Benchmark('foo', fn); - * - * // or with options - * var bench = new Benchmark('foo', fn, { - * - * // displayed by Benchmark#toString if `name` is not available - * 'id': 'xyz', - * - * // called when the benchmark starts running - * 'onStart': onStart, - * - * // called after each run cycle - * 'onCycle': onCycle, - * - * // called when aborted - * 'onAbort': onAbort, - * - * // called when a test errors - * 'onError': onError, - * - * // called when reset - * 'onReset': onReset, - * - * // called when the benchmark completes running - * 'onComplete': onComplete, - * - * // compiled/called before the test loop - * 'setup': setup, - * - * // compiled/called after the test loop - * 'teardown': teardown - * }); - * - * // or name and options - * var bench = new Benchmark('foo', { - * - * // a flag to indicate the benchmark is deferred - * 'defer': true, - * - * // benchmark test function - * 'fn': function(deferred) { - * // call resolve() when the deferred test is finished - * deferred.resolve(); - * } - * }); - * - * // or options only - * var bench = new Benchmark({ - * - * // benchmark name - * 'name': 'foo', - * - * // benchmark test as a string - * 'fn': '[1,2,3,4].sort()' - * }); - * - * // a test's `this` binding is set to the benchmark instance - * var bench = new Benchmark('foo', function() { - * 'My name is '.concat(this.name); // My name is foo - * }); - */ - function Benchmark(name, fn, options) { - var me = this; + /** + * Gets the source code of a function. + * + * @private + * @param {Function} fn The function. + * @param {String} altSource A string used when a function's source code is unretrievable. + * @returns {String} The function's source code. + */ + function getSource(fn, altSource) { + var result = altSource; + if (isStringable(fn)) { + result = String(fn); + } else if (support.decompilation) { + // escape the `{` for Firefox 1 + result = (/^[^{]+\{([\s\S]*)\}\s*$/.exec(fn) || 0)[1]; + } + // trim string + result = (result || '').replace(/^\s+|\s+$/g, ''); - // allow instance creation without the `new` operator - if (me == null || me.constructor != Benchmark) { - return new Benchmark(name, fn, options); - } - // juggle arguments - if (isClassOf(name, 'Object')) { - // 1 argument (options) - options = name; - } - else if (isClassOf(name, 'Function')) { - // 2 arguments (fn, options) - options = fn; - fn = name; - } - else if (isClassOf(fn, 'Object')) { - // 2 arguments (name, options) - options = fn; - fn = null; - me.name = name; - } - else { - // 3 arguments (name, fn [, options]) - me.name = name; + // detect strings containing only the "use strict" directive + return /^(?:\/\*+[\w|\W]*?\*\/|\/\/.*?[\n\r\u2028\u2029]|\s)*(["'])use strict\1;?$/.test(result) + ? '' + : result; } - setOptions(me, options); - me.id || (me.id = ++counter); - me.fn == null && (me.fn = fn); - me.stats = deepClone(me.stats); - me.times = deepClone(me.times); - } - /** - * The Deferred constructor. - * - * @constructor - * @memberOf Benchmark - * @param {Object} clone The cloned benchmark instance. - */ - function Deferred(clone) { - var me = this; - if (me == null || me.constructor != Deferred) { - return new Deferred(clone); + /** + * Checks if an object is of the specified class. + * + * @private + * @param {Mixed} value The value to check. + * @param {String} name The name of the class. + * @returns {Boolean} Returns `true` if the value is of the specified class, else `false`. + */ + function isClassOf(value, name) { + return value != null && toString.call(value) == '[object ' + name + ']'; } - me.benchmark = clone; - clock(me); - } - - /** - * The Event constructor. - * - * @constructor - * @memberOf Benchmark - * @param {String|Object} type The event type. - */ - function Event(type) { - var me = this; - return (me == null || me.constructor != Event) - ? new Event(type) - : (type instanceof Event) - ? type - : extend(me, { 'timeStamp': +new Date }, typeof type == 'string' ? { 'type': type } : type); - } - /** - * The Suite constructor. - * - * @constructor - * @memberOf Benchmark - * @param {String} name A name to identify the suite. - * @param {Object} [options={}] Options object. - * @example - * - * // basic usage (the `new` operator is optional) - * var suite = new Benchmark.Suite; - * - * // or using a name first - * var suite = new Benchmark.Suite('foo'); - * - * // or with options - * var suite = new Benchmark.Suite('foo', { - * - * // called when the suite starts running - * 'onStart': onStart, - * - * // called between running benchmarks - * 'onCycle': onCycle, - * - * // called when aborted - * 'onAbort': onAbort, - * - * // called when a test errors - * 'onError': onError, - * - * // called when reset - * 'onReset': onReset, - * - * // called when the suite completes running - * 'onComplete': onComplete - * }); - */ - function Suite(name, options) { - var me = this; + /** + * Host objects can return type values that are different from their actual + * data type. The objects we are concerned with usually return non-primitive + * types of "object", "function", or "unknown". + * + * @private + * @param {Mixed} object The owner of the property. + * @param {String} property The property to check. + * @returns {Boolean} Returns `true` if the property value is a non-primitive, else `false`. + */ + function isHostType(object, property) { + if (object == null) { + return false; + } + var type = typeof object[property]; + return !rePrimitive.test(type) && (type != 'object' || !!object[property]); + } - // allow instance creation without the `new` operator - if (me == null || me.constructor != Suite) { - return new Suite(name, options); + /** + * Checks if a value can be safely coerced to a string. + * + * @private + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the value can be coerced, else `false`. + */ + function isStringable(value) { + return _.has(value, 'toString') || isClassOf(value, 'String'); } - // juggle arguments - if (isClassOf(name, 'Object')) { - // 1 argument (options) - options = name; - } else { - // 2 arguments (name [, options]) - me.name = name; + + /** + * A no-operation function. + * + * @private + */ + function noop() { + // no operation performed } - setOptions(me, options); - } - /*--------------------------------------------------------------------------*/ + /** + * A wrapper around require() to suppress `module missing` errors. + * + * @private + * @param {String} id The module id. + * @returns {Mixed} The exported module or `null`. + */ + function req(id) { + try { + var result = freeExports && freeRequire(id); + } catch(e) { } + return result || null; + } - /** - * Note: Some array methods have been implemented in plain JavaScript to avoid - * bugs in IE, Opera, Rhino, and Mobile Safari. - * - * IE compatibility mode and IE < 9 have buggy Array `shift()` and `splice()` - * functions that fail to remove the last element, `object[0]`, of - * array-like-objects even though the `length` property is set to `0`. - * The `shift()` method is buggy in IE 8 compatibility mode, while `splice()` - * is buggy regardless of mode in IE < 9 and buggy in compatibility mode in IE 9. - * - * In Opera < 9.50 and some older/beta Mobile Safari versions using `unshift()` - * generically to augment the `arguments` object will pave the value at index 0 - * without incrimenting the other values's indexes. - * https://github.com/documentcloud/underscore/issues/9 - * - * Rhino and environments it powers, like Narwhal and RingoJS, may have - * buggy Array `concat()`, `reverse()`, `shift()`, `slice()`, `splice()` and - * `unshift()` functions that make sparse arrays non-sparse by assigning the - * undefined indexes a value of undefined. - * https://github.com/mozilla/rhino/commit/702abfed3f8ca043b2636efd31c14ba7552603dd - */ + /** + * Runs a snippet of JavaScript via script injection. + * + * @private + * @param {String} code The code to run. + */ + function runScript(code) { + var anchor = freeDefine ? define.amd : Benchmark, + script = doc.createElement('script'), + sibling = doc.getElementsByTagName('script')[0], + parent = sibling.parentNode, + prop = uid + 'runScript', + prefix = '(' + (freeDefine ? 'define.amd.' : 'Benchmark.') + prop + '||function(){})();'; + + // Firefox 2.0.0.2 cannot use script injection as intended because it executes + // asynchronously, but that's OK because script injection is only used to avoid + // the previously commented JaegerMonkey bug. + try { + // remove the inserted script *before* running the code to avoid differences + // in the expected script element count/order of the document. + script.appendChild(doc.createTextNode(prefix + code)); + anchor[prop] = function() { destroyElement(script); }; + } catch(e) { + parent = parent.cloneNode(false); + sibling = null; + script.text = code; + } + parent.insertBefore(script, sibling); + delete anchor[prop]; + } - /** - * Creates an array containing the elements of the host array followed by the - * elements of each argument in order. - * - * @memberOf Benchmark.Suite - * @returns {Array} The new array. - */ - function concat() { - var value, - j = -1, - length = arguments.length, - result = slice.call(this), - index = result.length; - - while (++j < length) { - value = arguments[j]; - if (isClassOf(value, 'Array')) { - for (var k = 0, l = value.length; k < l; k++, index++) { - if (k in value) { - result[index] = value[k]; + /** + * A helper function for setting options/event handlers. + * + * @private + * @param {Object} bench The benchmark instance. + * @param {Object} [options={}] Options object. + */ + function setOptions(bench, options) { + options = _.extend({}, bench.constructor.options, options); + bench.options = _.forOwn(options, function(value, key) { + if (value != null) { + // add event listeners + if (/^on[A-Z]/.test(key)) { + _.each(key.split(' '), function(key) { + bench.on(key.slice(2).toLowerCase(), value); + }); + } else if (!_.has(bench, key)) { + bench[key] = cloneDeep(value); } } - } else { - result[index++] = value; - } + }); } - return result; - } - /** - * Utility function used by `shift()`, `splice()`, and `unshift()`. - * - * @private - * @param {Number} start The index to start inserting elements. - * @param {Number} deleteCount The number of elements to delete from the insert point. - * @param {Array} elements The elements to insert. - * @returns {Array} An array of deleted elements. - */ - function insert(start, deleteCount, elements) { - // `result` should have its length set to the `deleteCount` - // see https://bugs.ecmascript.org/show_bug.cgi?id=332 - var deleteEnd = start + deleteCount, - elementCount = elements ? elements.length : 0, - index = start - 1, - length = start + elementCount, - object = this, - result = Array(deleteCount), - tail = slice.call(object, deleteEnd); - - // delete elements from the array - while (++index < deleteEnd) { - if (index in object) { - result[index - start] = object[index]; - delete object[index]; + /*------------------------------------------------------------------------*/ + + /** + * Handles cycling/completing the deferred benchmark. + * + * @memberOf Benchmark.Deferred + */ + function resolve() { + var deferred = this, + clone = deferred.benchmark, + bench = clone._original; + + if (bench.aborted) { + // cycle() -> clone cycle/complete event -> compute()'s invoked bench.run() cycle/complete + deferred.teardown(); + clone.running = false; + cycle(deferred); } - } - // insert elements - index = start - 1; - while (++index < length) { - object[index] = elements[index - start]; - } - // append tail elements - start = index--; - length = max(0, (object.length >>> 0) - deleteCount + elementCount); - while (++index < length) { - if ((index - start) in tail) { - object[index] = tail[index - start]; - } else if (index in object) { - delete object[index]; + else if (++deferred.cycles < clone.count) { + // continue the test loop + if (support.timeout) { + // use setTimeout to avoid a call stack overflow if called recursively + setTimeout(function() { clone.compiled.call(deferred, context, timer); }, 0); + } else { + clone.compiled.call(deferred, context, timer); + } } - } - // delete excess elements - deleteCount = deleteCount > elementCount ? deleteCount - elementCount : 0; - while (deleteCount--) { - index = length + deleteCount; - if (index in object) { - delete object[index]; + else { + timer.stop(deferred); + deferred.teardown(); + delay(clone, function() { cycle(deferred); }); } } - object.length = length; - return result; - } - /** - * Rearrange the host array's elements in reverse order. - * - * @memberOf Benchmark.Suite - * @returns {Array} The reversed array. - */ - function reverse() { - var upperIndex, - value, - index = -1, - object = Object(this), - length = object.length >>> 0, - middle = floor(length / 2); - - if (length > 1) { - while (++index < middle) { - upperIndex = length - index - 1; - value = upperIndex in object ? object[upperIndex] : uid; - if (index in object) { - object[upperIndex] = object[index]; - } else { - delete object[upperIndex]; - } - if (value != uid) { - object[index] = value; - } else { - delete object[index]; - } - } - } - return object; - } - - /** - * Removes the first element of the host array and returns it. - * - * @memberOf Benchmark.Suite - * @returns {Mixed} The first element of the array. - */ - function shift() { - return insert.call(this, 0, 1)[0]; - } - - /** - * Creates an array of the host array's elements from the start index up to, - * but not including, the end index. - * - * @memberOf Benchmark.Suite - * @param {Number} start The starting index. - * @param {Number} end The end index. - * @returns {Array} The new array. - */ - function slice(start, end) { - var index = -1, - object = Object(this), - length = object.length >>> 0, - result = []; - - start = toInteger(start); - start = start < 0 ? max(length + start, 0) : min(start, length); - start--; - end = end == null ? length : toInteger(end); - end = end < 0 ? max(length + end, 0) : min(end, length); - - while ((++index, ++start) < end) { - if (start in object) { - result[index] = object[start]; - } - } - return result; - } - - /** - * Allows removing a range of elements and/or inserting elements into the - * host array. - * - * @memberOf Benchmark.Suite - * @param {Number} start The start index. - * @param {Number} deleteCount The number of elements to delete. - * @param {Mixed} [val1, val2, ...] values to insert at the `start` index. - * @returns {Array} An array of removed elements. - */ - function splice(start, deleteCount) { - var object = Object(this), - length = object.length >>> 0; - - start = toInteger(start); - start = start < 0 ? max(length + start, 0) : min(start, length); - - // support the de-facto SpiderMonkey extension - // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/splice#Parameters - // https://bugs.ecmascript.org/show_bug.cgi?id=429 - deleteCount = arguments.length == 1 - ? length - start - : min(max(toInteger(deleteCount), 0), length - start); - - return insert.call(object, start, deleteCount, slice.call(arguments, 2)); - } - - /** - * Converts the specified `value` to an integer. - * - * @private - * @param {Mixed} value The value to convert. - * @returns {Number} The resulting integer. - */ - function toInteger(value) { - value = +value; - return value === 0 || !isFinite(value) ? value || 0 : value - (value % 1); - } - - /** - * Appends arguments to the host array. - * - * @memberOf Benchmark.Suite - * @returns {Number} The new length. - */ - function unshift() { - var object = Object(this); - insert.call(object, 0, 0, arguments); - return object.length; - } - - /*--------------------------------------------------------------------------*/ - - /** - * A generic `Function#bind` like method. - * - * @private - * @param {Function} fn The function to be bound to `thisArg`. - * @param {Mixed} thisArg The `this` binding for the given function. - * @returns {Function} The bound function. - */ - function bind(fn, thisArg) { - return function() { fn.apply(thisArg, arguments); }; - } - - /** - * Creates a function from the given arguments string and body. - * - * @private - * @param {String} args The comma separated function arguments. - * @param {String} body The function body. - * @returns {Function} The new function. - */ - function createFunction() { - // lazy define - createFunction = function(args, body) { - var result, - anchor = freeDefine ? define.amd : Benchmark, - prop = uid + 'createFunction'; - - runScript((freeDefine ? 'define.amd.' : 'Benchmark.') + prop + '=function(' + args + '){' + body + '}'); - result = anchor[prop]; - delete anchor[prop]; - return result; - }; - // fix JaegerMonkey bug - // http://bugzil.la/639720 - createFunction = support.browser && (createFunction('', 'return"' + uid + '"') || noop)() == uid ? createFunction : Function; - return createFunction.apply(null, arguments); - } - - /** - * Delay the execution of a function based on the benchmark's `delay` property. - * - * @private - * @param {Object} bench The benchmark instance. - * @param {Object} fn The function to execute. - */ - function delay(bench, fn) { - bench._timerId = setTimeout(fn, bench.delay * 1e3); - } - - /** - * Destroys the given element. - * - * @private - * @param {Element} element The element to destroy. - */ - function destroyElement(element) { - trash.appendChild(element); - trash.innerHTML = ''; - } - - /** - * Iterates over an object's properties, executing the `callback` for each. - * Callbacks may terminate the loop by explicitly returning `false`. - * - * @private - * @param {Object} object The object to iterate over. - * @param {Function} callback The function executed per own property. - * @param {Object} options The options object. - * @returns {Object} Returns the object iterated over. - */ - function forProps() { - var forShadowed, - skipSeen, - forArgs = true, - shadowed = ['constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', 'toLocaleString', 'toString', 'valueOf']; - - (function(enumFlag, key) { - // must use a non-native constructor to catch the Safari 2 issue - function Klass() { this.valueOf = 0; }; - Klass.prototype.valueOf = 0; - // check various for-in bugs - for (key in new Klass) { - enumFlag += key == 'valueOf' ? 1 : 0; - } - // check if `arguments` objects have non-enumerable indexes - for (key in arguments) { - key == '0' && (forArgs = false); - } - // Safari 2 iterates over shadowed properties twice - // http://replay.waybackmachine.org/20090428222941/http://tobielangel.com/2007/1/29/for-in-loop-broken-in-safari/ - skipSeen = enumFlag == 2; - // IE < 9 incorrectly makes an object's properties non-enumerable if they have - // the same name as other non-enumerable properties in its prototype chain. - forShadowed = !enumFlag; - }(0)); - - // lazy define - forProps = function(object, callback, options) { - options || (options = {}); - - var result = object; - object = Object(object); - - var ctor, - key, - keys, - skipCtor, - done = !result, - which = options.which, - allFlag = which == 'all', - index = -1, - iteratee = object, - length = object.length, - ownFlag = allFlag || which == 'own', - seen = {}, - skipProto = isClassOf(object, 'Function'), - thisArg = options.bind; - - if (thisArg !== undefined) { - callback = bind(callback, thisArg); - } - // iterate all properties - if (allFlag && support.getAllKeys) { - for (index = 0, keys = getAllKeys(object), length = keys.length; index < length; index++) { - key = keys[index]; - if (callback(object[key], key, object) === false) { - break; - } - } - } - // else iterate only enumerable properties - else { - for (key in object) { - // Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1 - // (if the prototype or a property on the prototype has been set) - // incorrectly set a function's `prototype` property [[Enumerable]] value - // to `true`. Because of this we standardize on skipping the `prototype` - // property of functions regardless of their [[Enumerable]] value. - if ((done = - !(skipProto && key == 'prototype') && - !(skipSeen && (hasKey(seen, key) || !(seen[key] = true))) && - (!ownFlag || ownFlag && hasKey(object, key)) && - callback(object[key], key, object) === false)) { - break; - } - } - // in IE < 9 strings don't support accessing characters by index - if (!done && (forArgs && isArguments(object) || - ((noCharByIndex || noCharByOwnIndex) && isClassOf(object, 'String') && - (iteratee = noCharByIndex ? object.split('') : object)))) { - while (++index < length) { - if ((done = - callback(iteratee[index], String(index), object) === false)) { - break; - } - } - } - if (!done && forShadowed) { - // Because IE < 9 can't set the `[[Enumerable]]` attribute of an existing - // property and the `constructor` property of a prototype defaults to - // non-enumerable, we manually skip the `constructor` property when we - // think we are iterating over a `prototype` object. - ctor = object.constructor; - skipCtor = ctor && ctor.prototype && ctor.prototype.constructor === ctor; - for (index = 0; index < 7; index++) { - key = shadowed[index]; - if (!(skipCtor && key == 'constructor') && - hasKey(object, key) && - callback(object[key], key, object) === false) { - break; - } - } - } - } - return result; - }; - return forProps.apply(null, arguments); - } - - /** - * Gets the name of the first argument from a function's source. - * - * @private - * @param {Function} fn The function. - * @returns {String} The argument name. - */ - function getFirstArgument(fn) { - return (!hasKey(fn, 'toString') && - (/^[\s(]*function[^(]*\(([^\s,)]+)/.exec(fn) || 0)[1]) || ''; - } - - /** - * Computes the geometric mean (log-average) of a sample. - * See http://en.wikipedia.org/wiki/Geometric_mean#Relationship_with_arithmetic_mean_of_logarithms. - * - * @private - * @param {Array} sample The sample. - * @returns {Number} The geometric mean. - */ - function getGeometricMean(sample) { - return pow(Math.E, reduce(sample, function(sum, x) { - return sum + log(x); - }) / sample.length) || 0; - } - - /** - * Computes the arithmetic mean of a sample. - * - * @private - * @param {Array} sample The sample. - * @returns {Number} The mean. - */ - function getMean(sample) { - return (reduce(sample, function(sum, x) { - return sum + x; - }) / sample.length) || 0; - } - - /** - * Gets the source code of a function. - * - * @private - * @param {Function} fn The function. - * @param {String} altSource A string used when a function's source code is unretrievable. - * @returns {String} The function's source code. - */ - function getSource(fn, altSource) { - var result = altSource; - if (isStringable(fn)) { - result = String(fn); - } else if (support.decompilation) { - // escape the `{` for Firefox 1 - result = (/^[^{]+\{([\s\S]*)\}\s*$/.exec(fn) || 0)[1]; - } - // trim string - result = (result || '').replace(/^\s+|\s+$/g, ''); - - // detect strings containing only the "use strict" directive - return /^(?:\/\*+[\w|\W]*?\*\/|\/\/.*?[\n\r\u2028\u2029]|\s)*(["'])use strict\1;?$/.test(result) - ? '' - : result; - } - - /** - * Checks if a value is an `arguments` object. - * - * @private - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the value is an `arguments` object, else `false`. - */ - function isArguments() { - // lazy define - isArguments = function(value) { - return toString.call(value) == '[object Arguments]'; - }; - if (noArgumentsClass) { - isArguments = function(value) { - return hasKey(value, 'callee') && - !(propertyIsEnumerable && propertyIsEnumerable.call(value, 'callee')); - }; - } - return isArguments(arguments[0]); - } - - /** - * Checks if an object is of the specified class. - * - * @private - * @param {Mixed} value The value to check. - * @param {String} name The name of the class. - * @returns {Boolean} Returns `true` if the value is of the specified class, else `false`. - */ - function isClassOf(value, name) { - return value != null && toString.call(value) == '[object ' + name + ']'; - } - - /** - * Host objects can return type values that are different from their actual - * data type. The objects we are concerned with usually return non-primitive - * types of object, function, or unknown. - * - * @private - * @param {Mixed} object The owner of the property. - * @param {String} property The property to check. - * @returns {Boolean} Returns `true` if the property value is a non-primitive, else `false`. - */ - function isHostType(object, property) { - var type = object != null ? typeof object[property] : 'number'; - return !/^(?:boolean|number|string|undefined)$/.test(type) && - (type == 'object' ? !!object[property] : true); - } - - /** - * Checks if a given `value` is an object created by the `Object` constructor - * assuming objects created by the `Object` constructor have no inherited - * enumerable properties and that there are no `Object.prototype` extensions. - * - * @private - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is a plain `Object` object, else `false`. - */ - function isPlainObject(value) { - // avoid non-objects and false positives for `arguments` objects in IE < 9 - var result = false; - if (!(value && typeof value == 'object') || isArguments(value)) { - return result; - } - // IE < 9 presents DOM nodes as `Object` objects except they have `toString` - // methods that are `typeof` "string" and still can coerce nodes to strings. - // Also check that the constructor is `Object` (i.e. `Object instanceof Object`) - var ctor = value.constructor; - if ((support.nodeClass || !(typeof value.toString != 'function' && typeof (value + '') == 'string')) && - (!isClassOf(ctor, 'Function') || ctor instanceof ctor)) { - // In most environments an object's own properties are iterated before - // its inherited properties. If the last iterated property is an object's - // own property then there are no inherited enumerable properties. - if (support.iteratesOwnFirst) { - forProps(value, function(subValue, subKey) { - result = subKey; - }); - return result === false || hasKey(value, result); - } - // IE < 9 iterates inherited properties before own properties. If the first - // iterated property is an object's own property then there are no inherited - // enumerable properties. - forProps(value, function(subValue, subKey) { - result = !hasKey(value, subKey); - return false; - }); - return result === false; - } - return result; - } - - /** - * Checks if a value can be safely coerced to a string. - * - * @private - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the value can be coerced, else `false`. - */ - function isStringable(value) { - return hasKey(value, 'toString') || isClassOf(value, 'String'); - } - - /** - * Wraps a function and passes `this` to the original function as the - * first argument. - * - * @private - * @param {Function} fn The function to be wrapped. - * @returns {Function} The new function. - */ - function methodize(fn) { - return function() { - var args = [this]; - args.push.apply(args, arguments); - return fn.apply(null, args); - }; - } - - /** - * A no-operation function. - * - * @private - */ - function noop() { - // no operation performed - } - - /** - * A wrapper around require() to suppress `module missing` errors. - * - * @private - * @param {String} id The module id. - * @returns {Mixed} The exported module or `null`. - */ - function req(id) { - try { - var result = freeExports && freeRequire(id); - } catch(e) { } - return result || null; - } - - /** - * Runs a snippet of JavaScript via script injection. - * - * @private - * @param {String} code The code to run. - */ - function runScript(code) { - var anchor = freeDefine ? define.amd : Benchmark, - script = doc.createElement('script'), - sibling = doc.getElementsByTagName('script')[0], - parent = sibling.parentNode, - prop = uid + 'runScript', - prefix = '(' + (freeDefine ? 'define.amd.' : 'Benchmark.') + prop + '||function(){})();'; - - // Firefox 2.0.0.2 cannot use script injection as intended because it executes - // asynchronously, but that's OK because script injection is only used to avoid - // the previously commented JaegerMonkey bug. - try { - // remove the inserted script *before* running the code to avoid differences - // in the expected script element count/order of the document. - script.appendChild(doc.createTextNode(prefix + code)); - anchor[prop] = function() { destroyElement(script); }; - } catch(e) { - parent = parent.cloneNode(false); - sibling = null; - script.text = code; - } - parent.insertBefore(script, sibling); - delete anchor[prop]; - } - - /** - * A helper function for setting options/event handlers. - * - * @private - * @param {Object} bench The benchmark instance. - * @param {Object} [options={}] Options object. - */ - function setOptions(bench, options) { - options = extend({}, bench.constructor.options, options); - bench.options = forOwn(options, function(value, key) { - if (value != null) { - // add event listeners - if (/^on[A-Z]/.test(key)) { - forEach(key.split(' '), function(key) { - bench.on(key.slice(2).toLowerCase(), value); - }); - } else if (!hasKey(bench, key)) { - bench[key] = deepClone(value); - } - } - }); - } - - /*--------------------------------------------------------------------------*/ - - /** - * Handles cycling/completing the deferred benchmark. - * - * @memberOf Benchmark.Deferred - */ - function resolve() { - var me = this, - clone = me.benchmark, - bench = clone._original; - - if (bench.aborted) { - // cycle() -> clone cycle/complete event -> compute()'s invoked bench.run() cycle/complete - me.teardown(); - clone.running = false; - cycle(me); - } - else if (++me.cycles < clone.count) { - // continue the test loop - if (support.timeout) { - // use setTimeout to avoid a call stack overflow if called recursively - setTimeout(function() { clone.compiled.call(me, timer); }, 0); - } else { - clone.compiled.call(me, timer); - } - } - else { - timer.stop(me); - me.teardown(); - delay(clone, function() { cycle(me); }); - } - } - - /*--------------------------------------------------------------------------*/ - - /** - * A deep clone utility. - * - * @static - * @memberOf Benchmark - * @param {Mixed} value The value to clone. - * @returns {Mixed} The cloned value. - */ - function deepClone(value) { - var accessor, - circular, - clone, - ctor, - descriptor, - extensible, - key, - length, - markerKey, - parent, - result, - source, - subIndex, - data = { 'value': value }, - index = 0, - marked = [], - queue = { 'length': 0 }, - unmarked = []; - - /** - * An easily detectable decorator for cloned values. - */ - function Marker(object) { - this.raw = object; - } - - /** - * The callback used by `forProps()`. - */ - function forPropsCallback(subValue, subKey) { - // exit early to avoid cloning the marker - if (subValue && subValue.constructor == Marker) { - return; - } - // add objects to the queue - if (subValue === Object(subValue)) { - queue[queue.length++] = { 'key': subKey, 'parent': clone, 'source': value }; - } - // assign non-objects - else { - try { - // will throw an error in strict mode if the property is read-only - clone[subKey] = subValue; - } catch(e) { } - } - } - - /** - * Gets an available marker key for the given object. - */ - function getMarkerKey(object) { - // avoid collisions with existing keys - var result = uid; - while (object[result] && object[result].constructor != Marker) { - result += 1; - } - return result; - } - - do { - key = data.key; - parent = data.parent; - source = data.source; - clone = value = source ? source[key] : data.value; - accessor = circular = descriptor = false; - - // create a basic clone to filter out functions, DOM elements, and - // other non `Object` objects - if (value === Object(value)) { - // use custom deep clone function if available - if (isClassOf(value.deepClone, 'Function')) { - clone = value.deepClone(); - } else { - ctor = value.constructor; - switch (toString.call(value)) { - case '[object Array]': - clone = new ctor(value.length); - break; - - case '[object Boolean]': - clone = new ctor(value == true); - break; - - case '[object Date]': - clone = new ctor(+value); - break; - - case '[object Object]': - isPlainObject(value) && (clone = {}); - break; - - case '[object Number]': - case '[object String]': - clone = new ctor(value); - break; - - case '[object RegExp]': - clone = ctor(value.source, - (value.global ? 'g' : '') + - (value.ignoreCase ? 'i' : '') + - (value.multiline ? 'm' : '')); - } - } - // continue clone if `value` doesn't have an accessor descriptor - // http://es5.github.com/#x8.10.1 - if (clone && clone != value && - !(descriptor = source && support.descriptors && getDescriptor(source, key), - accessor = descriptor && (descriptor.get || descriptor.set))) { - // use an existing clone (circular reference) - if ((extensible = isExtensible(value))) { - markerKey = getMarkerKey(value); - if (value[markerKey]) { - circular = clone = value[markerKey].raw; - } - } else { - // for frozen/sealed objects - for (subIndex = 0, length = unmarked.length; subIndex < length; subIndex++) { - data = unmarked[subIndex]; - if (data.object === value) { - circular = clone = data.clone; - break; - } - } - } - if (!circular) { - // mark object to allow quickly detecting circular references and tie it to its clone - if (extensible) { - value[markerKey] = new Marker(clone); - marked.push({ 'key': markerKey, 'object': value }); - } else { - // for frozen/sealed objects - unmarked.push({ 'clone': clone, 'object': value }); - } - // iterate over object properties - forProps(value, forPropsCallback, { 'which': 'all' }); - } - } - } - if (parent) { - // for custom property descriptors - if (accessor || (descriptor && !(descriptor.configurable && descriptor.enumerable && descriptor.writable))) { - if ('value' in descriptor) { - descriptor.value = clone; - } - setDescriptor(parent, key, descriptor); - } - // for default property descriptors - else { - parent[key] = clone; - } - } else { - result = clone; - } - } while ((data = queue[index++])); - - // remove markers - for (index = 0, length = marked.length; index < length; index++) { - data = marked[index]; - delete data.object[data.key]; - } - return result; - } - - /** - * An iteration utility for arrays and objects. - * Callbacks may terminate the loop by explicitly returning `false`. - * - * @static - * @memberOf Benchmark - * @param {Array|Object} object The object to iterate over. - * @param {Function} callback The function called per iteration. - * @param {Mixed} thisArg The `this` binding for the callback. - * @returns {Array|Object} Returns the object iterated over. - */ - function each(object, callback, thisArg) { - var result = object; - object = Object(object); - - var fn = callback, - index = -1, - length = object.length, - isSnapshot = !!(object.snapshotItem && (length = object.snapshotLength)), - isSplittable = (noCharByIndex || noCharByOwnIndex) && isClassOf(object, 'String'), - isConvertable = isSnapshot || isSplittable || 'item' in object, - origObject = object; - - // in Opera < 10.5 `hasKey(object, 'length')` returns `false` for NodeLists - if (length === length >>> 0) { - if (isConvertable) { - // the third argument of the callback is the original non-array object - callback = function(value, index) { - return fn.call(this, value, index, origObject); - }; - // in IE < 9 strings don't support accessing characters by index - if (isSplittable) { - object = object.split(''); - } else { - object = []; - while (++index < length) { - // in Safari 2 `index in object` is always `false` for NodeLists - object[index] = isSnapshot ? result.snapshotItem(index) : result[index]; - } - } - } - forEach(object, callback, thisArg); - } else { - forOwn(object, callback, thisArg); - } - return result; - } - - /** - * Copies enumerable properties from the source(s) object to the destination object. - * - * @static - * @memberOf Benchmark - * @param {Object} destination The destination object. - * @param {Object} [source={}] The source object. - * @returns {Object} The destination object. - */ - function extend(destination, source) { - // Chrome < 14 incorrectly sets `destination` to `undefined` when we `delete arguments[0]` - // http://code.google.com/p/v8/issues/detail?id=839 - var result = destination; - delete arguments[0]; - - forEach(arguments, function(source) { - forProps(source, function(value, key) { - result[key] = value; - }); - }); - return result; - } - - /** - * A generic `Array#filter` like method. - * - * @static - * @memberOf Benchmark - * @param {Array} array The array to iterate over. - * @param {Function|String} callback The function/alias called per iteration. - * @param {Mixed} thisArg The `this` binding for the callback. - * @returns {Array} A new array of values that passed callback filter. - * @example - * - * // get odd numbers - * Benchmark.filter([1, 2, 3, 4, 5], function(n) { - * return n % 2; - * }); // -> [1, 3, 5]; - * - * // get fastest benchmarks - * Benchmark.filter(benches, 'fastest'); - * - * // get slowest benchmarks - * Benchmark.filter(benches, 'slowest'); - * - * // get benchmarks that completed without erroring - * Benchmark.filter(benches, 'successful'); - */ - function filter(array, callback, thisArg) { - var result; - - if (callback == 'successful') { - // callback to exclude those that are errored, unrun, or have hz of Infinity - callback = function(bench) { return bench.cycles && isFinite(bench.hz); }; - } - else if (callback == 'fastest' || callback == 'slowest') { - // get successful, sort by period + margin of error, and filter fastest/slowest - result = filter(array, 'successful').sort(function(a, b) { - a = a.stats; b = b.stats; - return (a.mean + a.moe > b.mean + b.moe ? 1 : -1) * (callback == 'fastest' ? 1 : -1); - }); - result = filter(result, function(bench) { - return result[0].compare(bench) == 0; - }); - } - return result || reduce(array, function(result, value, index) { - return callback.call(thisArg, value, index, array) ? (result.push(value), result) : result; - }, []); - } - - /** - * A generic `Array#forEach` like method. - * Callbacks may terminate the loop by explicitly returning `false`. - * - * @static - * @memberOf Benchmark - * @param {Array} array The array to iterate over. - * @param {Function} callback The function called per iteration. - * @param {Mixed} thisArg The `this` binding for the callback. - * @returns {Array} Returns the array iterated over. - */ - function forEach(array, callback, thisArg) { - var index = -1, - length = (array = Object(array)).length >>> 0; - - if (thisArg !== undefined) { - callback = bind(callback, thisArg); - } - while (++index < length) { - if (index in array && - callback(array[index], index, array) === false) { - break; - } - } - return array; - } - - /** - * Iterates over an object's own properties, executing the `callback` for each. - * Callbacks may terminate the loop by explicitly returning `false`. - * - * @static - * @memberOf Benchmark - * @param {Object} object The object to iterate over. - * @param {Function} callback The function executed per own property. - * @param {Mixed} thisArg The `this` binding for the callback. - * @returns {Object} Returns the object iterated over. - */ - function forOwn(object, callback, thisArg) { - return forProps(object, callback, { 'bind': thisArg, 'which': 'own' }); - } - - /** - * Converts a number to a more readable comma-separated string representation. - * - * @static - * @memberOf Benchmark - * @param {Number} number The number to convert. - * @returns {String} The more readable string representation. - */ - function formatNumber(number) { - number = String(number).split('.'); - return number[0].replace(/(?=(?:\d{3})+$)(?!\b)/g, ',') + - (number[1] ? '.' + number[1] : ''); - } - - /** - * Checks if an object has the specified key as a direct property. - * - * @static - * @memberOf Benchmark - * @param {Object} object The object to check. - * @param {String} key The key to check for. - * @returns {Boolean} Returns `true` if key is a direct property, else `false`. - */ - function hasKey() { - // lazy define for worst case fallback (not as accurate) - hasKey = function(object, key) { - var parent = object != null && (object.constructor || Object).prototype; - return !!parent && key in Object(object) && !(key in parent && object[key] === parent[key]); - }; - // for modern browsers - if (isClassOf(hasOwnProperty, 'Function')) { - hasKey = function(object, key) { - return object != null && hasOwnProperty.call(object, key); - }; - } - // for Safari 2 - else if ({}.__proto__ == Object.prototype) { - hasKey = function(object, key) { - var result = false; - if (object != null) { - object = Object(object); - object.__proto__ = [object.__proto__, object.__proto__ = null, result = key in object][0]; - } - return result; - }; - } - return hasKey.apply(this, arguments); - } - - /** - * A generic `Array#indexOf` like method. - * - * @static - * @memberOf Benchmark - * @param {Array} array The array to iterate over. - * @param {Mixed} value The value to search for. - * @param {Number} [fromIndex=0] The index to start searching from. - * @returns {Number} The index of the matched value or `-1`. - */ - function indexOf(array, value, fromIndex) { - var index = toInteger(fromIndex), - length = (array = Object(array)).length >>> 0; - - index = (index < 0 ? max(0, length + index) : index) - 1; - while (++index < length) { - if (index in array && value === array[index]) { - return index; - } - } - return -1; - } - - /** - * Modify a string by replacing named tokens with matching object property values. - * - * @static - * @memberOf Benchmark - * @param {String} string The string to modify. - * @param {Object} object The template object. - * @returns {String} The modified string. - */ - function interpolate(string, object) { - forOwn(object, function(value, key) { - // escape regexp special characters in `key` - string = string.replace( - RegExp('#\\{' + key.replace(/([.*+?^${}()|[\]\\])/g, '\\$1') + '\\}', 'g'), - value.replace(/\$/g, '$$$$') - ); - }); - return string; - } - - /** - * Invokes a method on all items in an array. - * - * @static - * @memberOf Benchmark - * @param {Array} benches Array of benchmarks to iterate over. - * @param {String|Object} name The name of the method to invoke OR options object. - * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the method with. - * @returns {Array} A new array of values returned from each method invoked. - * @example - * - * // invoke `reset` on all benchmarks - * Benchmark.invoke(benches, 'reset'); - * - * // invoke `emit` with arguments - * Benchmark.invoke(benches, 'emit', 'complete', listener); - * - * // invoke `run(true)`, treat benchmarks as a queue, and register invoke callbacks - * Benchmark.invoke(benches, { - * - * // invoke the `run` method - * 'name': 'run', - * - * // pass a single argument - * 'args': true, - * - * // treat as queue, removing benchmarks from front of `benches` until empty - * 'queued': true, - * - * // called before any benchmarks have been invoked. - * 'onStart': onStart, - * - * // called between invoking benchmarks - * 'onCycle': onCycle, - * - * // called after all benchmarks have been invoked. - * 'onComplete': onComplete - * }); - */ - function invoke(benches, name) { - var args, - bench, - queued, - index = -1, - eventProps = { 'currentTarget': benches }, - options = { 'onStart': noop, 'onCycle': noop, 'onComplete': noop }, - result = map(benches, function(bench) { return bench; }); - - /** - * Invokes the method of the current object and if synchronous, fetches the next. - */ - function execute() { - var listeners, - async = isAsync(bench); - - if (async) { - // use `getNext` as the first listener - bench.on('complete', getNext); - listeners = bench.events.complete; - listeners.splice(0, 0, listeners.pop()); - } - // execute method - result[index] = isClassOf(bench && bench[name], 'Function') ? bench[name].apply(bench, args) : undefined; - // if synchronous return true until finished - return !async && getNext(); - } - - /** - * Fetches the next bench or executes `onComplete` callback. - */ - function getNext(event) { - var cycleEvent, - last = bench, - async = isAsync(last); - - if (async) { - last.off('complete', getNext); - last.emit('complete'); - } - // emit "cycle" event - eventProps.type = 'cycle'; - eventProps.target = last; - cycleEvent = Event(eventProps); - options.onCycle.call(benches, cycleEvent); - - // choose next benchmark if not exiting early - if (!cycleEvent.aborted && raiseIndex() !== false) { - bench = queued ? benches[0] : result[index]; - if (isAsync(bench)) { - delay(bench, execute); - } - else if (async) { - // resume execution if previously asynchronous but now synchronous - while (execute()) { } - } - else { - // continue synchronous execution - return true; - } - } else { - // emit "complete" event - eventProps.type = 'complete'; - options.onComplete.call(benches, Event(eventProps)); - } - // When used as a listener `event.aborted = true` will cancel the rest of - // the "complete" listeners because they were already called above and when - // used as part of `getNext` the `return false` will exit the execution while-loop. - if (event) { - event.aborted = true; - } else { - return false; - } - } - - /** - * Checks if invoking `Benchmark#run` with asynchronous cycles. - */ - function isAsync(object) { - // avoid using `instanceof` here because of IE memory leak issues with host objects - var async = args[0] && args[0].async; - return Object(object).constructor == Benchmark && name == 'run' && - ((async == null ? object.options.async : async) && support.timeout || object.defer); - } - - /** - * Raises `index` to the next defined index or returns `false`. - */ - function raiseIndex() { - var length = result.length; - if (queued) { - // if queued remove the previous bench and subsequent skipped non-entries - do { - ++index > 0 && shift.call(benches); - } while ((length = benches.length) && !('0' in benches)); - } - else { - while (++index < length && !(index in result)) { } - } - // if we reached the last index then return `false` - return (queued ? length : index < length) ? index : (index = false); - } - - // juggle arguments - if (isClassOf(name, 'String')) { - // 2 arguments (array, name) - args = slice.call(arguments, 2); - } else { - // 2 arguments (array, options) - options = extend(options, name); - name = options.name; - args = isClassOf(args = 'args' in options ? options.args : [], 'Array') ? args : [args]; - queued = options.queued; - } - - // start iterating over the array - if (raiseIndex() !== false) { - // emit "start" event - bench = result[index]; - eventProps.type = 'start'; - eventProps.target = bench; - options.onStart.call(benches, Event(eventProps)); - - // end early if the suite was aborted in an "onStart" listener - if (benches.aborted && benches.constructor == Suite && name == 'run') { - // emit "cycle" event - eventProps.type = 'cycle'; - options.onCycle.call(benches, Event(eventProps)); - // emit "complete" event - eventProps.type = 'complete'; - options.onComplete.call(benches, Event(eventProps)); - } - // else start - else { - if (isAsync(bench)) { - delay(bench, execute); - } else { - while (execute()) { } - } - } - } - return result; - } - - /** - * Creates a string of joined array values or object key-value pairs. - * - * @static - * @memberOf Benchmark - * @param {Array|Object} object The object to operate on. - * @param {String} [separator1=','] The separator used between key-value pairs. - * @param {String} [separator2=': '] The separator used between keys and values. - * @returns {String} The joined result. - */ - function join(object, separator1, separator2) { - var result = [], - length = (object = Object(object)).length, - arrayLike = length === length >>> 0; - - separator2 || (separator2 = ': '); - each(object, function(value, key) { - result.push(arrayLike ? value : key + separator2 + value); - }); - return result.join(separator1 || ','); - } - - /** - * A generic `Array#map` like method. - * - * @static - * @memberOf Benchmark - * @param {Array} array The array to iterate over. - * @param {Function} callback The function called per iteration. - * @param {Mixed} thisArg The `this` binding for the callback. - * @returns {Array} A new array of values returned by the callback. - */ - function map(array, callback, thisArg) { - return reduce(array, function(result, value, index) { - result[index] = callback.call(thisArg, value, index, array); - return result; - }, Array(Object(array).length >>> 0)); - } - - /** - * Retrieves the value of a specified property from all items in an array. - * - * @static - * @memberOf Benchmark - * @param {Array} array The array to iterate over. - * @param {String} property The property to pluck. - * @returns {Array} A new array of property values. - */ - function pluck(array, property) { - return map(array, function(object) { - return object == null ? undefined : object[property]; - }); - } - - /** - * A generic `Array#reduce` like method. - * - * @static - * @memberOf Benchmark - * @param {Array} array The array to iterate over. - * @param {Function} callback The function called per iteration. - * @param {Mixed} accumulator Initial value of the accumulator. - * @returns {Mixed} The accumulator. - */ - function reduce(array, callback, accumulator) { - var noaccum = arguments.length < 3; - forEach(array, function(value, index) { - accumulator = noaccum ? (noaccum = false, value) : callback(accumulator, value, index, array); - }); - return accumulator; - } + /*------------------------------------------------------------------------*/ - /*--------------------------------------------------------------------------*/ + /** + * A generic `Array#filter` like method. + * + * @static + * @memberOf Benchmark + * @param {Array} array The array to iterate over. + * @param {Function|String} callback The function/alias called per iteration. + * @param {Mixed} thisArg The `this` binding for the callback. + * @returns {Array} A new array of values that passed callback filter. + * @example + * + * // get odd numbers + * Benchmark.filter([1, 2, 3, 4, 5], function(n) { + * return n % 2; + * }); // -> [1, 3, 5]; + * + * // get fastest benchmarks + * Benchmark.filter(benches, 'fastest'); + * + * // get slowest benchmarks + * Benchmark.filter(benches, 'slowest'); + * + * // get benchmarks that completed without erroring + * Benchmark.filter(benches, 'successful'); + */ + function filter(array, callback, thisArg) { + if (callback === 'successful') { + // callback to exclude those that are errored, unrun, or have hz of Infinity + callback = function(bench) { + return bench.cycles && _.isFinite(bench.hz); + }; + } + else if (callback === 'fastest' || callback === 'slowest') { + // get successful, sort by period + margin of error, and filter fastest/slowest + var result = filter(array, 'successful').sort(function(a, b) { + a = a.stats; b = b.stats; + return (a.mean + a.moe > b.mean + b.moe ? 1 : -1) * (callback === 'fastest' ? 1 : -1); + }); - /** - * Aborts all benchmarks in the suite. - * - * @name abort - * @memberOf Benchmark.Suite - * @returns {Object} The suite instance. - */ - function abortSuite() { - var event, - me = this, - resetting = calledBy.resetSuite; - - if (me.running) { - event = Event('abort'); - me.emit(event); - if (!event.cancelled || resetting) { - // avoid infinite recursion - calledBy.abortSuite = true; - me.reset(); - delete calledBy.abortSuite; - - if (!resetting) { - me.aborted = true; - invoke(me, 'abort'); - } + return _.filter(result, function(bench) { + return result[0].compare(bench) == 0; + }); } + return _.filter(array, callback, thisArg); } - return me; - } - - /** - * Adds a test to the benchmark suite. - * - * @memberOf Benchmark.Suite - * @param {String} name A name to identify the benchmark. - * @param {Function|String} fn The test to benchmark. - * @param {Object} [options={}] Options object. - * @returns {Object} The benchmark instance. - * @example - * - * // basic usage - * suite.add(fn); - * - * // or using a name first - * suite.add('foo', fn); - * - * // or with options - * suite.add('foo', fn, { - * 'onCycle': onCycle, - * 'onComplete': onComplete - * }); - * - * // or name and options - * suite.add('foo', { - * 'fn': fn, - * 'onCycle': onCycle, - * 'onComplete': onComplete - * }); - * - * // or options only - * suite.add({ - * 'name': 'foo', - * 'fn': fn, - * 'onCycle': onCycle, - * 'onComplete': onComplete - * }); - */ - function add(name, fn, options) { - var me = this, - bench = Benchmark(name, fn, options), - event = Event({ 'type': 'add', 'target': bench }); - if (me.emit(event), !event.cancelled) { - me.push(bench); + /** + * Converts a number to a more readable comma-separated string representation. + * + * @static + * @memberOf Benchmark + * @param {Number} number The number to convert. + * @returns {String} The more readable string representation. + */ + function formatNumber(number) { + number = String(number).split('.'); + return number[0].replace(/(?=(?:\d{3})+$)(?!\b)/g, ',') + + (number[1] ? '.' + number[1] : ''); } - return me; - } - - /** - * Creates a new suite with cloned benchmarks. - * - * @name clone - * @memberOf Benchmark.Suite - * @param {Object} options Options object to overwrite cloned options. - * @returns {Object} The new suite instance. - */ - function cloneSuite(options) { - var me = this, - result = new me.constructor(extend({}, me.options, options)); - - // copy own properties - forOwn(me, function(value, key) { - if (!hasKey(result, key)) { - result[key] = value && isClassOf(value.clone, 'Function') - ? value.clone() - : deepClone(value); - } - }); - return result; - } - /** - * An `Array#filter` like method. - * - * @name filter - * @memberOf Benchmark.Suite - * @param {Function|String} callback The function/alias called per iteration. - * @returns {Object} A new suite of benchmarks that passed callback filter. - */ - function filterSuite(callback) { - var me = this, - result = new me.constructor; - - result.push.apply(result, filter(me, callback)); - return result; - } - - /** - * Resets all benchmarks in the suite. - * - * @name reset - * @memberOf Benchmark.Suite - * @returns {Object} The suite instance. - */ - function resetSuite() { - var event, - me = this, - aborting = calledBy.abortSuite; - - if (me.running && !aborting) { - // no worries, `resetSuite()` is called within `abortSuite()` - calledBy.resetSuite = true; - me.abort(); - delete calledBy.resetSuite; - } - // reset if the state has changed - else if ((me.aborted || me.running) && - (me.emit(event = Event('reset')), !event.cancelled)) { - me.running = false; - if (!aborting) { - invoke(me, 'reset'); - } - } - return me; - } + /** + * Invokes a method on all items in an array. + * + * @static + * @memberOf Benchmark + * @param {Array} benches Array of benchmarks to iterate over. + * @param {String|Object} name The name of the method to invoke OR options object. + * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the method with. + * @returns {Array} A new array of values returned from each method invoked. + * @example + * + * // invoke `reset` on all benchmarks + * Benchmark.invoke(benches, 'reset'); + * + * // invoke `emit` with arguments + * Benchmark.invoke(benches, 'emit', 'complete', listener); + * + * // invoke `run(true)`, treat benchmarks as a queue, and register invoke callbacks + * Benchmark.invoke(benches, { + * + * // invoke the `run` method + * 'name': 'run', + * + * // pass a single argument + * 'args': true, + * + * // treat as queue, removing benchmarks from front of `benches` until empty + * 'queued': true, + * + * // called before any benchmarks have been invoked. + * 'onStart': onStart, + * + * // called between invoking benchmarks + * 'onCycle': onCycle, + * + * // called after all benchmarks have been invoked. + * 'onComplete': onComplete + * }); + */ + function invoke(benches, name) { + var args, + bench, + queued, + index = -1, + eventProps = { 'currentTarget': benches }, + options = { 'onStart': noop, 'onCycle': noop, 'onComplete': noop }, + result = _.toArray(benches); - /** - * Runs the suite. - * - * @name run - * @memberOf Benchmark.Suite - * @param {Object} [options={}] Options object. - * @returns {Object} The suite instance. - * @example - * - * // basic usage - * suite.run(); - * - * // or with options - * suite.run({ 'async': true, 'queued': true }); - */ - function runSuite(options) { - var me = this; - - me.reset(); - me.running = true; - options || (options = {}); - - invoke(me, { - 'name': 'run', - 'args': options, - 'queued': options.queued, - 'onStart': function(event) { - me.emit(event); - }, - 'onCycle': function(event) { - var bench = event.target; - if (bench.error) { - me.emit({ 'type': 'error', 'target': bench }); + /** + * Invokes the method of the current object and if synchronous, fetches the next. + */ + function execute() { + var listeners, + async = isAsync(bench); + + if (async) { + // use `getNext` as the first listener + bench.on('complete', getNext); + listeners = bench.events.complete; + listeners.splice(0, 0, listeners.pop()); } - me.emit(event); - event.aborted = me.aborted; - }, - 'onComplete': function(event) { - me.score = getGeometricMean(map(me, function(bench) { - return bench.reference / (bench.times.period * 1e6); - })) || 0; - - me.running = false; - me.emit(event); + // execute method + result[index] = _.isFunction(bench && bench[name]) ? bench[name].apply(bench, args) : undefined; + // if synchronous return true until finished + return !async && getNext(); } - }); - return me; - } - - /*--------------------------------------------------------------------------*/ - /** - * Executes all registered listeners of the specified event type. - * - * @memberOf Benchmark, Benchmark.Suite - * @param {String|Object} type The event type or object. - * @returns {Mixed} Returns the return value of the last listener executed. - */ - function emit(type) { - var listeners, - me = this, - event = Event(type), - events = me.events, - args = (arguments[0] = event, arguments); - - event.currentTarget || (event.currentTarget = me); - event.target || (event.target = me); - delete event.result; - - if (events && (listeners = hasKey(events, event.type) && events[event.type])) { - forEach(listeners.slice(), function(listener) { - if ((event.result = listener.apply(me, args)) === false) { - event.cancelled = true; + /** + * Fetches the next bench or executes `onComplete` callback. + */ + function getNext(event) { + var cycleEvent, + last = bench, + async = isAsync(last); + + if (async) { + last.off('complete', getNext); + last.emit('complete'); } - return !event.aborted; - }); - } - return event.result; - } - - /** - * Returns an array of event listeners for a given type that can be manipulated - * to add or remove listeners. - * - * @memberOf Benchmark, Benchmark.Suite - * @param {String} type The event type. - * @returns {Array} The listeners array. - */ - function listeners(type) { - var me = this, - events = me.events || (me.events = {}); - - return hasKey(events, type) ? events[type] : (events[type] = []); - } - - /** - * Unregisters a listener for the specified event type(s), - * or unregisters all listeners for the specified event type(s), - * or unregisters all listeners for all event types. - * - * @memberOf Benchmark, Benchmark.Suite - * @param {String} [type] The event type. - * @param {Function} [listener] The function to unregister. - * @returns {Object} The benchmark instance. - * @example - * - * // unregister a listener for an event type - * bench.off('cycle', listener); - * - * // unregister a listener for multiple event types - * bench.off('start cycle', listener); - * - * // unregister all listeners for an event type - * bench.off('cycle'); - * - * // unregister all listeners for multiple event types - * bench.off('start cycle complete'); - * - * // unregister all listeners for all event types - * bench.off(); - */ - function off(type, listener) { - var me = this, - events = me.events; - - events && each(type ? type.split(' ') : events, function(listeners, type) { - var index; - if (typeof listeners == 'string') { - type = listeners; - listeners = hasKey(events, type) && events[type]; - } - if (listeners) { - if (listener) { - index = indexOf(listeners, listener); - if (index > -1) { - listeners.splice(index, 1); + // emit "cycle" event + eventProps.type = 'cycle'; + eventProps.target = last; + cycleEvent = Event(eventProps); + options.onCycle.call(benches, cycleEvent); + + // choose next benchmark if not exiting early + if (!cycleEvent.aborted && raiseIndex() !== false) { + bench = queued ? benches[0] : result[index]; + if (isAsync(bench)) { + delay(bench, execute); + } + else if (async) { + // resume execution if previously asynchronous but now synchronous + while (execute()) { } + } + else { + // continue synchronous execution + return true; } } else { - listeners.length = 0; - } - } - }); - return me; - } - - /** - * Registers a listener for the specified event type(s). - * - * @memberOf Benchmark, Benchmark.Suite - * @param {String} type The event type. - * @param {Function} listener The function to register. - * @returns {Object} The benchmark instance. - * @example - * - * // register a listener for an event type - * bench.on('cycle', listener); - * - * // register a listener for multiple event types - * bench.on('start cycle', listener); - */ - function on(type, listener) { - var me = this, - events = me.events || (me.events = {}); - - forEach(type.split(' '), function(type) { - (hasKey(events, type) - ? events[type] - : (events[type] = []) - ).push(listener); - }); - return me; - } - - /*--------------------------------------------------------------------------*/ - - /** - * Aborts the benchmark without recording times. - * - * @memberOf Benchmark - * @returns {Object} The benchmark instance. - */ - function abort() { - var event, - me = this, - resetting = calledBy.reset; - - if (me.running) { - event = Event('abort'); - me.emit(event); - if (!event.cancelled || resetting) { - // avoid infinite recursion - calledBy.abort = true; - me.reset(); - delete calledBy.abort; - - if (support.timeout) { - clearTimeout(me._timerId); - delete me._timerId; + // emit "complete" event + eventProps.type = 'complete'; + options.onComplete.call(benches, Event(eventProps)); } - if (!resetting) { - me.aborted = true; - me.running = false; + // When used as a listener `event.aborted = true` will cancel the rest of + // the "complete" listeners because they were already called above and when + // used as part of `getNext` the `return false` will exit the execution while-loop. + if (event) { + event.aborted = true; + } else { + return false; } } - } - return me; - } - - /** - * Creates a new benchmark using the same test and options. - * - * @memberOf Benchmark - * @param {Object} options Options object to overwrite cloned options. - * @returns {Object} The new benchmark instance. - * @example - * - * var bizarro = bench.clone({ - * 'name': 'doppelganger' - * }); - */ - function clone(options) { - var me = this, - result = new me.constructor(extend({}, me, options)); - - // correct the `options` object - result.options = extend({}, me.options, options); - - // copy own custom properties - forOwn(me, function(value, key) { - if (!hasKey(result, key)) { - result[key] = deepClone(value); - } - }); - return result; - } - - /** - * Determines if a benchmark is faster than another. - * - * @memberOf Benchmark - * @param {Object} other The benchmark to compare. - * @returns {Number} Returns `-1` if slower, `1` if faster, and `0` if indeterminate. - */ - function compare(other) { - var critical, - zStat, - me = this, - sample1 = me.stats.sample, - sample2 = other.stats.sample, - size1 = sample1.length, - size2 = sample2.length, - maxSize = max(size1, size2), - minSize = min(size1, size2), - u1 = getU(sample1, sample2), - u2 = getU(sample2, sample1), - u = min(u1, u2); - - function getScore(xA, sampleB) { - return reduce(sampleB, function(total, xB) { - return total + (xB > xA ? 0 : xB < xA ? 1 : 0.5); - }, 0); - } - - function getU(sampleA, sampleB) { - return reduce(sampleA, function(total, xA) { - return total + getScore(xA, sampleB); - }, 0); - } - - function getZ(u) { - return (u - ((size1 * size2) / 2)) / sqrt((size1 * size2 * (size1 + size2 + 1)) / 12); - } - - // exit early if comparing the same benchmark - if (me == other) { - return 0; - } - // reject the null hyphothesis the two samples come from the - // same population (i.e. have the same median) if... - if (size1 + size2 > 30) { - // ...the z-stat is greater than 1.96 or less than -1.96 - // http://www.statisticslectures.com/topics/mannwhitneyu/ - zStat = getZ(u); - return abs(zStat) > 1.96 ? (zStat > 0 ? -1 : 1) : 0; - } - // ...the U value is less than or equal the critical U value - // http://www.geoib.com/mann-whitney-u-test.html - critical = maxSize < 5 || minSize < 3 ? 0 : uTable[maxSize][minSize - 3]; - return u <= critical ? (u == u1 ? 1 : -1) : 0; - } - - /** - * Reset properties and abort if running. - * - * @memberOf Benchmark - * @returns {Object} The benchmark instance. - */ - function reset() { - var data, - event, - me = this, - index = 0, - changes = { 'length': 0 }, - queue = { 'length': 0 }; - - if (me.running && !calledBy.abort) { - // no worries, `reset()` is called within `abort()` - calledBy.reset = true; - me.abort(); - delete calledBy.reset; - } - else { - // a non-recursive solution to check if properties have changed - // http://www.jslab.dk/articles/non.recursive.preorder.traversal.part4 - data = { 'destination': me, 'source': extend({}, me.constructor.prototype, me.options) }; - do { - forOwn(data.source, function(value, key) { - var changed, - destination = data.destination, - currValue = destination[key]; - - if (value && typeof value == 'object') { - if (isClassOf(value, 'Array')) { - // check if an array value has changed to a non-array value - if (!isClassOf(currValue, 'Array')) { - changed = currValue = []; - } - // or has changed its length - if (currValue.length != value.length) { - changed = currValue = currValue.slice(0, value.length); - currValue.length = value.length; - } - } - // check if an object has changed to a non-object value - else if (!currValue || typeof currValue != 'object') { - changed = currValue = {}; - } - // register a changed object - if (changed) { - changes[changes.length++] = { 'destination': destination, 'key': key, 'value': currValue }; - } - queue[queue.length++] = { 'destination': currValue, 'source': value }; - } - // register a changed primitive - else if (value !== currValue && !(value == null || isClassOf(value, 'Function'))) { - changes[changes.length++] = { 'destination': destination, 'key': key, 'value': value }; - } - }); - } - while ((data = queue[index++])); - - // if changed emit the `reset` event and if it isn't cancelled reset the benchmark - if (changes.length && (me.emit(event = Event('reset')), !event.cancelled)) { - forEach(changes, function(data) { - data.destination[data.key] = data.value; - }); - } - } - return me; - } - - /** - * Displays relevant benchmark information when coerced to a string. - * - * @name toString - * @memberOf Benchmark - * @returns {String} A string representation of the benchmark instance. - */ - function toStringBench() { - var me = this, - error = me.error, - hz = me.hz, - id = me.id, - stats = me.stats, - size = stats.sample.length, - pm = support.java ? '+/-' : '\xb1', - result = me.name || (isNaN(id) ? id : ''); - - if (error) { - result += ': ' + join(error); - } else { - result += ' x ' + formatNumber(hz.toFixed(hz < 100 ? 2 : 0)) + ' ops/sec ' + pm + - stats.rme.toFixed(2) + '% (' + size + ' run' + (size == 1 ? '' : 's') + ' sampled)'; - } - return result; - } - - /*--------------------------------------------------------------------------*/ - - /** - * Clocks the time taken to execute a test per cycle (secs). - * - * @private - * @param {Object} bench The benchmark instance. - * @returns {Number} The time taken. - */ - function clock() { - var applet, - options = Benchmark.options, - template = { 'begin': 's$=new n$', 'end': 'r$=(new n$-s$)/1e3', 'uid': uid }, - timers = [{ 'ns': timer.ns, 'res': max(0.0015, getRes('ms')), 'unit': 'ms' }]; - - // lazy define for hi-res timers - clock = function(clone) { - var deferred; - if (clone instanceof Deferred) { - deferred = clone; - clone = deferred.benchmark; - } - - var bench = clone._original, - fn = bench.fn, - fnArg = deferred ? getFirstArgument(fn) || 'deferred' : '', - stringable = isStringable(fn); - - var source = { - 'setup': getSource(bench.setup, preprocess('m$.setup()')), - 'fn': getSource(fn, preprocess('m$.fn(' + fnArg + ')')), - 'fnArg': fnArg, - 'teardown': getSource(bench.teardown, preprocess('m$.teardown()')) - }; - - var count = bench.count = clone.count, - decompilable = support.decompilation || stringable, - id = bench.id, - isEmpty = !(source.fn || stringable), - name = bench.name || (typeof id == 'number' ? '' : id), - ns = timer.ns, - result = 0; - - // init `minTime` if needed - clone.minTime = bench.minTime || (bench.minTime = bench.options.minTime = options.minTime); - // repair nanosecond timer - // (some Chrome builds erase the `ns` variable after millions of executions) - if (applet) { - try { - ns.nanoTime(); - } catch(e) { - // use non-element to avoid issues with libs that augment them - ns = timer.ns = new applet.Packages.nano; - } + /** + * Checks if invoking `Benchmark#run` with asynchronous cycles. + */ + function isAsync(object) { + // avoid using `instanceof` here because of IE memory leak issues with host objects + var async = args[0] && args[0].async; + return Object(object).constructor == Benchmark && name == 'run' && + ((async == null ? object.options.async : async) && support.timeout || object.defer); } - // Compile in setup/teardown functions and the test loop. - // Create a new compiled test, instead of using the cached `bench.compiled`, - // to avoid potential engine optimizations enabled over the life of the test. - var compiled = bench.compiled = createFunction(preprocess('t$'), interpolate( - preprocess(deferred - ? 'var d$=this,#{fnArg}=d$,m$=d$.benchmark._original,f$=m$.fn,su$=m$.setup,td$=m$.teardown;' + - // when `deferred.cycles` is `0` then... - 'if(!d$.cycles){' + - // set `deferred.fn` - 'd$.fn=function(){var #{fnArg}=d$;if(typeof f$=="function"){try{#{fn}\n}catch(e$){f$(d$)}}else{#{fn}\n}};' + - // set `deferred.teardown` - 'd$.teardown=function(){d$.cycles=0;if(typeof td$=="function"){try{#{teardown}\n}catch(e$){td$()}}else{#{teardown}\n}};' + - // execute the benchmark's `setup` - 'if(typeof su$=="function"){try{#{setup}\n}catch(e$){su$()}}else{#{setup}\n};' + - // start timer - 't$.start(d$);' + - // execute `deferred.fn` and return a dummy object - '}d$.fn();return{}' - - : 'var r$,s$,m$=this,f$=m$.fn,i$=m$.count,n$=t$.ns;#{setup}\n#{begin};' + - 'while(i$--){#{fn}\n}#{end};#{teardown}\nreturn{elapsed:r$,uid:"#{uid}"}'), - source - )); - - try { - if (isEmpty) { - // Firefox may remove dead code from Function#toString results - // http://bugzil.la/536085 - throw new Error('The test "' + name + '" is empty. This may be the result of dead code removal.'); - } - else if (!deferred) { - // pretest to determine if compiled code is exits early, usually by a - // rogue `return` statement, by checking for a return object with the uid - bench.count = 1; - compiled = (compiled.call(bench, timer) || {}).uid == uid && compiled; - bench.count = count; - } - } catch(e) { - compiled = null; - clone.error = e || new Error(String(e)); - bench.count = count; - } - // fallback when a test exits early or errors during pretest - if (decompilable && !compiled && !deferred && !isEmpty) { - compiled = createFunction(preprocess('t$'), interpolate( - preprocess( - (clone.error && !stringable - ? 'var r$,s$,m$=this,f$=m$.fn,i$=m$.count' - : 'function f$(){#{fn}\n}var r$,s$,m$=this,i$=m$.count' - ) + - ',n$=t$.ns;#{setup}\n#{begin};m$.f$=f$;while(i$--){m$.f$()}#{end};' + - 'delete m$.f$;#{teardown}\nreturn{elapsed:r$}' - ), - source - )); + /** + * Raises `index` to the next defined index or returns `false`. + */ + function raiseIndex() { + index++; - try { - // pretest one more time to check for errors - bench.count = 1; - compiled.call(bench, timer); - bench.compiled = compiled; - bench.count = count; - delete clone.error; - } - catch(e) { - bench.count = count; - if (clone.error) { - compiled = null; - } else { - bench.compiled = compiled; - clone.error = e || new Error(String(e)); - } + // if queued remove the previous bench + if (queued && index > 0) { + shift.call(benches); } + // if we reached the last index then return `false` + return (queued ? benches.length : index < result.length) + ? index + : (index = false); } - // assign `compiled` to `clone` before calling in case a deferred benchmark - // immediately calls `deferred.resolve()` - clone.compiled = compiled; - // if no errors run the full test loop - if (!clone.error) { - result = compiled.call(deferred || bench, timer).elapsed; - } - return result; - }; - - /*------------------------------------------------------------------------*/ - /** - * Gets the current timer's minimum resolution (secs). - */ - function getRes(unit) { - var measured, - begin, - count = 30, - divisor = 1e3, - ns = timer.ns, - sample = []; - - // get average smallest measurable time - while (count--) { - if (unit == 'us') { - divisor = 1e6; - if (ns.stop) { - ns.start(); - while (!(measured = ns.microseconds())) { } - } else if (ns[perfName]) { - divisor = 1e3; - measured = Function('n', 'var r,s=n.' + perfName + '();while(!(r=n.' + perfName + '()-s)){};return r')(ns); - } else { - begin = ns(); - while (!(measured = ns() - begin)) { } - } + // juggle arguments + if (_.isString(name)) { + // 2 arguments (array, name) + args = slice.call(arguments, 2); + } else { + // 2 arguments (array, options) + options = _.extend(options, name); + name = options.name; + args = _.isArray(args = 'args' in options ? options.args : []) ? args : [args]; + queued = options.queued; + } + + // start iterating over the array + if (raiseIndex() !== false) { + // emit "start" event + bench = result[index]; + eventProps.type = 'start'; + eventProps.target = bench; + options.onStart.call(benches, Event(eventProps)); + + // end early if the suite was aborted in an "onStart" listener + if (benches.aborted && benches.constructor == Suite && name == 'run') { + // emit "cycle" event + eventProps.type = 'cycle'; + options.onCycle.call(benches, Event(eventProps)); + // emit "complete" event + eventProps.type = 'complete'; + options.onComplete.call(benches, Event(eventProps)); } - else if (unit == 'ns') { - divisor = 1e9; - if (ns.nanoTime) { - begin = ns.nanoTime(); - while (!(measured = ns.nanoTime() - begin)) { } + // else start + else { + if (isAsync(bench)) { + delay(bench, execute); } else { - begin = (begin = ns())[0] + (begin[1] / divisor); - while (!(measured = ((measured = ns())[0] + (measured[1] / divisor)) - begin)) { } - divisor = 1; + while (execute()) { } } } - else { - begin = new ns; - while (!(measured = new ns - begin)) { } - } - // check for broken timers (nanoTime may have issues) - // http://alivebutsleepy.srnet.cz/unreliable-system-nanotime/ - if (measured > 0) { - sample.push(measured); - } else { - sample.push(Infinity); - break; - } } - // convert to seconds - return getMean(sample) / divisor; + return result; } /** - * Replaces all occurrences of `$` with a unique number and - * template tokens with content. - */ - function preprocess(code) { - return interpolate(code, template).replace(/\$/g, /\d+/.exec(uid)); + * Creates a string of joined array values or object key-value pairs. + * + * @static + * @memberOf Benchmark + * @param {Array|Object} object The object to operate on. + * @param {String} [separator1=','] The separator used between key-value pairs. + * @param {String} [separator2=': '] The separator used between keys and values. + * @returns {String} The joined result. + */ + function join(object, separator1, separator2) { + var result = [], + length = (object = Object(object)).length, + arrayLike = length === length >>> 0; + + separator2 || (separator2 = ': '); + _.each(object, function(value, key) { + result.push(arrayLike ? value : key + separator2 + value); + }); + return result.join(separator1 || ','); } /*------------------------------------------------------------------------*/ - // detect nanosecond support from a Java applet - each(doc && doc.applets || [], function(element) { - return !(timer.ns = applet = 'nanoTime' in element && element); - }); - - // check type in case Safari returns an object instead of a number - try { - if (typeof timer.ns.nanoTime() == 'number') { - timers.push({ 'ns': timer.ns, 'res': getRes('ns'), 'unit': 'ns' }); - } - } catch(e) { } - - // detect Chrome's microsecond timer: - // enable benchmarking via the --enable-benchmarking command - // line switch in at least Chrome 7 to use chrome.Interval - try { - if ((timer.ns = new (window.chrome || window.chromium).Interval)) { - timers.push({ 'ns': timer.ns, 'res': getRes('us'), 'unit': 'us' }); + /** + * Aborts all benchmarks in the suite. + * + * @name abort + * @memberOf Benchmark.Suite + * @returns {Object} The suite instance. + */ + function abortSuite() { + var event, + suite = this, + resetting = calledBy.resetSuite; + + if (suite.running) { + event = Event('abort'); + suite.emit(event); + if (!event.cancelled || resetting) { + // avoid infinite recursion + calledBy.abortSuite = true; + suite.reset(); + delete calledBy.abortSuite; + + if (!resetting) { + suite.aborted = true; + invoke(suite, 'abort'); + } + } } - } catch(e) { } - - // detect `performance.now` microsecond resolution timer - if ((timer.ns = perfName && perfObject)) { - timers.push({ 'ns': timer.ns, 'res': getRes('us'), 'unit': 'us' }); + return suite; } - // detect Node's nanosecond resolution timer available in Node >= 0.8 - if (processObject && typeof (timer.ns = processObject.hrtime) == 'function') { - timers.push({ 'ns': timer.ns, 'res': getRes('ns'), 'unit': 'ns' }); + /** + * Adds a test to the benchmark suite. + * + * @memberOf Benchmark.Suite + * @param {String} name A name to identify the benchmark. + * @param {Function|String} fn The test to benchmark. + * @param {Object} [options={}] Options object. + * @returns {Object} The benchmark instance. + * @example + * + * // basic usage + * suite.add(fn); + * + * // or using a name first + * suite.add('foo', fn); + * + * // or with options + * suite.add('foo', fn, { + * 'onCycle': onCycle, + * 'onComplete': onComplete + * }); + * + * // or name and options + * suite.add('foo', { + * 'fn': fn, + * 'onCycle': onCycle, + * 'onComplete': onComplete + * }); + * + * // or options only + * suite.add({ + * 'name': 'foo', + * 'fn': fn, + * 'onCycle': onCycle, + * 'onComplete': onComplete + * }); + */ + function add(name, fn, options) { + var suite = this, + bench = new Benchmark(name, fn, options), + event = Event({ 'type': 'add', 'target': bench }); + + if (suite.emit(event), !event.cancelled) { + suite.push(bench); + } + return suite; } - // detect Wade Simmons' Node microtime module - if (microtimeObject && typeof (timer.ns = microtimeObject.now) == 'function') { - timers.push({ 'ns': timer.ns, 'res': getRes('us'), 'unit': 'us' }); + /** + * Creates a new suite with cloned benchmarks. + * + * @name clone + * @memberOf Benchmark.Suite + * @param {Object} options Options object to overwrite cloned options. + * @returns {Object} The new suite instance. + */ + function cloneSuite(options) { + var suite = this, + result = new suite.constructor(_.extend({}, suite.options, options)); + + // copy own properties + _.forOwn(suite, function(value, key) { + if (!_.has(result, key)) { + result[key] = value && _.isFunction(value.clone) + ? value.clone() + : cloneDeep(value); + } + }); + return result; } - // pick timer with highest resolution - timer = reduce(timers, function(timer, other) { - return other.res < timer.res ? other : timer; - }); + /** + * An `Array#filter` like method. + * + * @name filter + * @memberOf Benchmark.Suite + * @param {Function|String} callback The function/alias called per iteration. + * @returns {Object} A new suite of benchmarks that passed callback filter. + */ + function filterSuite(callback) { + var suite = this, + result = new suite.constructor; - // remove unused applet - if (timer.unit != 'ns' && applet) { - applet = destroyElement(applet); - } - // error if there are no working timers - if (timer.res == Infinity) { - throw new Error('Benchmark.js was unable to find a working timer.'); + result.push.apply(result, filter(suite, callback)); + return result; } - // use API of chosen timer - if (timer.unit == 'ns') { - if (timer.ns.nanoTime) { - extend(template, { - 'begin': 's$=n$.nanoTime()', - 'end': 'r$=(n$.nanoTime()-s$)/1e9' - }); - } else { - extend(template, { - 'begin': 's$=n$()', - 'end': 'r$=n$(s$);r$=r$[0]+(r$[1]/1e9)' - }); + + /** + * Resets all benchmarks in the suite. + * + * @name reset + * @memberOf Benchmark.Suite + * @returns {Object} The suite instance. + */ + function resetSuite() { + var event, + suite = this, + aborting = calledBy.abortSuite; + + if (suite.running && !aborting) { + // no worries, `resetSuite()` is called within `abortSuite()` + calledBy.resetSuite = true; + suite.abort(); + delete calledBy.resetSuite; } - } - else if (timer.unit == 'us') { - if (timer.ns.stop) { - extend(template, { - 'begin': 's$=n$.start()', - 'end': 'r$=n$.microseconds()/1e6' - }); - } else if (perfName) { - extend(template, { - 'begin': 's$=n$.' + perfName + '()', - 'end': 'r$=(n$.' + perfName + '()-s$)/1e3' - }); - } else { - extend(template, { - 'begin': 's$=n$()', - 'end': 'r$=(n$()-s$)/1e6' - }); + // reset if the state has changed + else if ((suite.aborted || suite.running) && + (suite.emit(event = Event('reset')), !event.cancelled)) { + suite.running = false; + if (!aborting) { + invoke(suite, 'reset'); + } } + return suite; } - // define `timer` methods - timer.start = createFunction(preprocess('o$'), - preprocess('var n$=this.ns,#{begin};o$.elapsed=0;o$.timeStamp=s$')); - - timer.stop = createFunction(preprocess('o$'), - preprocess('var n$=this.ns,s$=o$.timeStamp,#{end};o$.elapsed=r$')); + /** + * Runs the suite. + * + * @name run + * @memberOf Benchmark.Suite + * @param {Object} [options={}] Options object. + * @returns {Object} The suite instance. + * @example + * + * // basic usage + * suite.run(); + * + * // or with options + * suite.run({ 'async': true, 'queued': true }); + */ + function runSuite(options) { + var suite = this; - // resolve time span required to achieve a percent uncertainty of at most 1% - // http://spiff.rit.edu/classes/phys273/uncert/uncert.html - options.minTime || (options.minTime = max(timer.res / 2 / 0.01, 0.05)); - return clock.apply(null, arguments); - } + suite.reset(); + suite.running = true; + options || (options = {}); - /*--------------------------------------------------------------------------*/ + invoke(suite, { + 'name': 'run', + 'args': options, + 'queued': options.queued, + 'onStart': function(event) { + suite.emit(event); + }, + 'onCycle': function(event) { + var bench = event.target; + if (bench.error) { + suite.emit({ 'type': 'error', 'target': bench }); + } + suite.emit(event); + event.aborted = suite.aborted; + }, + 'onComplete': function(event) { + suite.running = false; + suite.emit(event); + } + }); + return suite; + } - /** - * Computes stats on benchmark results. - * - * @private - * @param {Object} bench The benchmark instance. - * @param {Object} options The options object. - */ - function compute(bench, options) { - options || (options = {}); + /*------------------------------------------------------------------------*/ - var async = options.async, - elapsed = 0, - initCount = bench.initCount, - minSamples = bench.minSamples, - queue = [], - sample = bench.stats.sample; + /** + * Executes all registered listeners of the specified event type. + * + * @memberOf Benchmark, Benchmark.Suite + * @param {String|Object} type The event type or object. + * @returns {Mixed} Returns the return value of the last listener executed. + */ + function emit(type) { + var listeners, + object = this, + event = Event(type), + events = object.events, + args = (arguments[0] = event, arguments); + + event.currentTarget || (event.currentTarget = object); + event.target || (event.target = object); + delete event.result; + + if (events && (listeners = _.has(events, event.type) && events[event.type])) { + _.each(listeners.slice(), function(listener) { + if ((event.result = listener.apply(object, args)) === false) { + event.cancelled = true; + } + return !event.aborted; + }); + } + return event.result; + } /** - * Adds a clone to the queue. + * Returns an array of event listeners for a given type that can be manipulated + * to add or remove listeners. + * + * @memberOf Benchmark, Benchmark.Suite + * @param {String} type The event type. + * @returns {Array} The listeners array. */ - function enqueue() { - queue.push(bench.clone({ - '_original': bench, - 'events': { - 'abort': [update], - 'cycle': [update], - 'error': [update], - 'start': [update] - } - })); + function listeners(type) { + var object = this, + events = object.events || (object.events = {}); + + return _.has(events, type) ? events[type] : (events[type] = []); } /** - * Updates the clone/original benchmarks to keep their data in sync. + * Unregisters a listener for the specified event type(s), + * or unregisters all listeners for the specified event type(s), + * or unregisters all listeners for all event types. + * + * @memberOf Benchmark, Benchmark.Suite + * @param {String} [type] The event type. + * @param {Function} [listener] The function to unregister. + * @returns {Object} The benchmark instance. + * @example + * + * // unregister a listener for an event type + * bench.off('cycle', listener); + * + * // unregister a listener for multiple event types + * bench.off('start cycle', listener); + * + * // unregister all listeners for an event type + * bench.off('cycle'); + * + * // unregister all listeners for multiple event types + * bench.off('start cycle complete'); + * + * // unregister all listeners for all event types + * bench.off(); */ - function update(event) { - var clone = this, - type = event.type; + function off(type, listener) { + var object = this, + events = object.events; - if (bench.running) { - if (type == 'start') { - // Note: `clone.minTime` prop is inited in `clock()` - clone.count = bench.initCount; + if (!events) { + return object; + } + _.each(type ? type.split(' ') : events, function(listeners, type) { + var index; + if (typeof listeners == 'string') { + type = listeners; + listeners = _.has(events, type) && events[type]; } - else { - if (type == 'error') { - bench.error = clone.error; - } - if (type == 'abort') { - bench.abort(); - bench.emit('cycle'); + if (listeners) { + if (listener) { + index = _.indexOf(listeners, listener); + if (index > -1) { + listeners.splice(index, 1); + } } else { - event.currentTarget = event.target = bench; - bench.emit(event); + listeners.length = 0; } } - } else if (bench.aborted) { - // clear abort listeners to avoid triggering bench's abort/cycle again - clone.events.abort.length = 0; - clone.abort(); - } + }); + return object; } /** - * Determines if more clones should be queued or if cycling should stop. - */ - function evaluate(event) { - var critical, - df, - mean, - moe, - rme, - sd, - sem, - variance, - clone = event.target, - done = bench.aborted, - now = +new Date, - size = sample.push(clone.times.period), - maxedOut = size >= minSamples && (elapsed += now - clone.times.timeStamp) / 1e3 > bench.maxTime, - times = bench.times, - varOf = function(sum, x) { return sum + pow(x - mean, 2); }; - - // exit early for aborted or unclockable tests - if (done || clone.hz == Infinity) { - maxedOut = !(size = sample.length = queue.length = 0); - } + * Registers a listener for the specified event type(s). + * + * @memberOf Benchmark, Benchmark.Suite + * @param {String} type The event type. + * @param {Function} listener The function to register. + * @returns {Object} The benchmark instance. + * @example + * + * // register a listener for an event type + * bench.on('cycle', listener); + * + * // register a listener for multiple event types + * bench.on('start cycle', listener); + */ + function on(type, listener) { + var object = this, + events = object.events || (object.events = {}); + + _.each(type.split(' '), function(type) { + (_.has(events, type) + ? events[type] + : (events[type] = []) + ).push(listener); + }); + return object; + } - if (!done) { - // sample mean (estimate of the population mean) - mean = getMean(sample); - // sample variance (estimate of the population variance) - variance = reduce(sample, varOf, 0) / (size - 1) || 0; - // sample standard deviation (estimate of the population standard deviation) - sd = sqrt(variance); - // standard error of the mean (a.k.a. the standard deviation of the sampling distribution of the sample mean) - sem = sd / sqrt(size); - // degrees of freedom - df = size - 1; - // critical value - critical = tTable[Math.round(df) || 1] || tTable.infinity; - // margin of error - moe = sem * critical; - // relative margin of error - rme = (moe / mean) * 100 || 0; - - extend(bench.stats, { - 'deviation': sd, - 'mean': mean, - 'moe': moe, - 'rme': rme, - 'sem': sem, - 'variance': variance - }); + /*------------------------------------------------------------------------*/ - // Abort the cycle loop when the minimum sample size has been collected - // and the elapsed time exceeds the maximum time allowed per benchmark. - // We don't count cycle delays toward the max time because delays may be - // increased by browsers that clamp timeouts for inactive tabs. - // https://developer.mozilla.org/en/window.setTimeout#Inactive_tabs - if (maxedOut) { - // reset the `initCount` in case the benchmark is rerun - bench.initCount = initCount; - bench.running = false; - done = true; - times.elapsed = (now - times.timeStamp) / 1e3; - } - if (bench.hz != Infinity) { - bench.hz = 1 / mean; - times.cycle = mean * bench.count; - times.period = mean; + /** + * Aborts the benchmark without recording times. + * + * @memberOf Benchmark + * @returns {Object} The benchmark instance. + */ + function abort() { + var event, + bench = this, + resetting = calledBy.reset; + + if (bench.running) { + event = Event('abort'); + bench.emit(event); + if (!event.cancelled || resetting) { + // avoid infinite recursion + calledBy.abort = true; + bench.reset(); + delete calledBy.abort; + + if (support.timeout) { + clearTimeout(bench._timerId); + delete bench._timerId; + } + if (!resetting) { + bench.aborted = true; + bench.running = false; + } } } - // if time permits, increase sample size to reduce the margin of error - if (queue.length < 2 && !maxedOut) { - enqueue(); - } - // abort the invoke cycle when done - event.aborted = done; + return bench; } - // init queue and begin - enqueue(); - invoke(queue, { - 'name': 'run', - 'args': { 'async': async }, - 'queued': true, - 'onCycle': evaluate, - 'onComplete': function() { bench.emit('complete'); } - }); - } + /** + * Creates a new benchmark using the same test and options. + * + * @memberOf Benchmark + * @param {Object} options Options object to overwrite cloned options. + * @returns {Object} The new benchmark instance. + * @example + * + * var bizarro = bench.clone({ + * 'name': 'doppelganger' + * }); + */ + function clone(options) { + var bench = this, + sample = bench.stats.sample, + result = new bench.constructor(_.extend({}, bench, options)); - /*--------------------------------------------------------------------------*/ + // correct the `options` object + result.options = _.extend({}, bench.options, options); - /** - * Cycles a benchmark until a run `count` can be established. - * - * @private - * @param {Object} clone The cloned benchmark instance. - * @param {Object} options The options object. - */ - function cycle(clone, options) { - options || (options = {}); + // copy own custom properties + _.forOwn(bench, function(value, key) { + if (!_.has(result, key)) { + result[key] = cloneDeep(value); + } + }); - var deferred; - if (clone instanceof Deferred) { - deferred = clone; - clone = clone.benchmark; + return result; } - var clocked, - cycles, - divisor, - event, - minTime, - period, - async = options.async, - bench = clone._original, - count = clone.count, - times = clone.times; - - // continue, if not aborted between cycles - if (clone.running) { - // `minTime` is set to `Benchmark.options.minTime` in `clock()` - cycles = ++clone.cycles; - clocked = deferred ? deferred.elapsed : clock(clone); - minTime = clone.minTime; - - if (cycles > bench.cycles) { - bench.cycles = cycles; - } - if (clone.error) { - event = Event('error'); - event.message = clone.error; - clone.emit(event); - if (!event.cancelled) { - clone.abort(); - } + /** + * Determines if a benchmark is faster than another. + * + * @memberOf Benchmark + * @param {Object} other The benchmark to compare. + * @returns {Number} Returns `-1` if slower, `1` if faster, and `0` if indeterminate. + */ + function compare(other) { + var critical, + zStat, + bench = this, + sample1 = bench.stats.sample, + sample2 = other.stats.sample, + size1 = sample1.length, + size2 = sample2.length, + maxSize = max(size1, size2), + minSize = min(size1, size2), + u1 = getU(sample1, sample2), + u2 = getU(sample2, sample1), + u = min(u1, u2); + + function getScore(xA, sampleB) { + return _.reduce(sampleB, function(total, xB) { + return total + (xB > xA ? 0 : xB < xA ? 1 : 0.5); + }, 0); } - } - // continue, if not errored - if (clone.running) { - // time taken to complete last test cycle - bench.times.cycle = times.cycle = clocked; - // seconds per operation - period = bench.times.period = times.period = clocked / count; - // ops per second - bench.hz = clone.hz = 1 / period; - // avoid working our way up to this next time - bench.initCount = clone.initCount = count; - // do we need to do another cycle? - clone.running = clocked < minTime; - - if (clone.running) { - // tests may clock at `0` when `initCount` is a small number, - // to avoid that we set its count to something a bit higher - if (!clocked && (divisor = divisors[clone.cycles]) != null) { - count = floor(4e6 / divisor); - } - // calculate how many more iterations it will take to achive the `minTime` - if (count <= clone.count) { - count += Math.ceil((minTime - clocked) / period); - } - clone.running = count != Infinity; - } - } - // should we exit early? - event = Event('cycle'); - clone.emit(event); - if (event.aborted) { - clone.abort(); - } - // figure out what to do next - if (clone.running) { - // start a new cycle - clone.count = count; - if (deferred) { - clone.compiled.call(deferred, timer); - } else if (async) { - delay(clone, function() { cycle(clone, options); }); - } else { - cycle(clone); + function getU(sampleA, sampleB) { + return _.reduce(sampleA, function(total, xA) { + return total + getScore(xA, sampleB); + }, 0); } - } - else { - // fix TraceMonkey bug associated with clock fallbacks - // http://bugzil.la/509069 - if (support.browser) { - runScript(uid + '=1;delete ' + uid); - } - // done - clone.emit('complete'); - } - } - /*--------------------------------------------------------------------------*/ + function getZ(u) { + return (u - ((size1 * size2) / 2)) / sqrt((size1 * size2 * (size1 + size2 + 1)) / 12); + } - /** - * Runs the benchmark. - * - * @memberOf Benchmark - * @param {Object} [options={}] Options object. - * @returns {Object} The benchmark instance. - * @example - * - * // basic usage - * bench.run(); - * - * // or with options - * bench.run({ 'async': true }); - */ - function run(options) { - var me = this, - event = Event('start'); - - // set `running` to `false` so `reset()` won't call `abort()` - me.running = false; - me.reset(); - me.running = true; - - me.count = me.initCount; - me.times.timeStamp = +new Date; - me.emit(event); - - if (!event.cancelled) { - options = { 'async': ((options = options && options.async) == null ? me.async : options) && support.timeout }; - - // for clones created within `compute()` - if (me._original) { - if (me.defer) { - Deferred(me); - } else { - cycle(me, options); - } + // exit early if comparing the same benchmark + if (bench == other) { + return 0; } - // for original benchmarks - else { - compute(me, options); + // reject the null hyphothesis the two samples come from the + // same population (i.e. have the same median) if... + if (size1 + size2 > 30) { + // ...the z-stat is greater than 1.96 or less than -1.96 + // http://www.statisticslectures.com/topics/mannwhitneyu/ + zStat = getZ(u); + return abs(zStat) > 1.96 ? (zStat > 0 ? -1 : 1) : 0; } + // ...the U value is less than or equal the critical U value + // http://www.geoib.com/mann-whitney-u-test.html + critical = maxSize < 5 || minSize < 3 ? 0 : uTable[maxSize][minSize - 3]; + return u <= critical ? (u == u1 ? 1 : -1) : 0; } - return me; - } - - /*--------------------------------------------------------------------------*/ - - // Firefox 1 erroneously defines variable and argument names of functions on - // the function itself as non-configurable properties with `undefined` values. - // The bugginess continues as the `Benchmark` constructor has an argument - // named `options` and Firefox 1 will not assign a value to `Benchmark.options`, - // making it non-writable in the process, unless it is the first property - // assigned by for-in loop of `extend()`. - extend(Benchmark, { /** - * The default options copied by benchmark instances. + * Reset properties and abort if running. * - * @static * @memberOf Benchmark - * @type Object - */ - 'options': { - - /** - * A flag to indicate that benchmark cycles will execute asynchronously - * by default. - * - * @memberOf Benchmark.options - * @type Boolean - */ - 'async': false, - - /** - * A flag to indicate that the benchmark clock is deferred. - * - * @memberOf Benchmark.options - * @type Boolean - */ - 'defer': false, - - /** - * The delay between test cycles (secs). - * @memberOf Benchmark.options - * @type Number - */ - 'delay': 0.005, - - /** - * Displayed by Benchmark#toString when a `name` is not available - * (auto-generated if absent). - * - * @memberOf Benchmark.options - * @type String - */ - 'id': undefined, - - /** - * The default number of times to execute a test on a benchmark's first cycle. - * - * @memberOf Benchmark.options - * @type Number - */ - 'initCount': 1, - - /** - * The maximum time a benchmark is allowed to run before finishing (secs). - * - * Note: Cycle delays aren't counted toward the maximum time. - * - * @memberOf Benchmark.options - * @type Number - */ - 'maxTime': 5, - - /** - * The minimum sample size required to perform statistical analysis. - * - * @memberOf Benchmark.options - * @type Number - */ - 'minSamples': 5, - - /** - * The time needed to reduce the percent uncertainty of measurement to 1% (secs). - * - * @memberOf Benchmark.options - * @type Number - */ - 'minTime': 0, - - /** - * The name of the benchmark. - * - * @memberOf Benchmark.options - * @type String - */ - 'name': undefined, - - /** - * An event listener called when the benchmark is aborted. - * - * @memberOf Benchmark.options - * @type Function - */ - 'onAbort': undefined, - - /** - * An event listener called when the benchmark completes running. - * - * @memberOf Benchmark.options - * @type Function - */ - 'onComplete': undefined, - - /** - * An event listener called after each run cycle. - * - * @memberOf Benchmark.options - * @type Function - */ - 'onCycle': undefined, + * @returns {Object} The benchmark instance. + */ + function reset() { + var data, + event, + bench = this, + index = 0, + changes = { 'length': 0 }, + queue = { 'length': 0 }; + + if (bench.running && !calledBy.abort) { + // no worries, `reset()` is called within `abort()` + calledBy.reset = true; + bench.abort(); + delete calledBy.reset; + } + else { + // a non-recursive solution to check if properties have changed + // http://www.jslab.dk/articles/non.recursive.preorder.traversal.part4 + data = { 'destination': bench, 'source': _.extend({}, bench.constructor.prototype, bench.options) }; + do { + _.forOwn(data.source, function(value, key) { + var changed, + destination = data.destination, + currValue = destination[key]; + + if (value && typeof value == 'object') { + if (_.isArray(value)) { + // check if an array value has changed to a non-array value + if (!_.isArray(currValue)) { + changed = currValue = []; + } + // or has changed its length + if (currValue.length != value.length) { + changed = currValue = currValue.slice(0, value.length); + currValue.length = value.length; + } + } + // check if an object has changed to a non-object value + else if (!currValue || typeof currValue != 'object') { + changed = currValue = {}; + } + // register a changed object + if (changed) { + changes[changes.length++] = { 'destination': destination, 'key': key, 'value': currValue }; + } + queue[queue.length++] = { 'destination': currValue, 'source': value }; + } + // register a changed primitive + else if (value !== currValue && !(value == null || _.isFunction(value))) { + changes[changes.length++] = { 'destination': destination, 'key': key, 'value': value }; + } + }); + } + while ((data = queue[index++])); - /** - * An event listener called when a test errors. - * - * @memberOf Benchmark.options - * @type Function - */ - 'onError': undefined, + // if changed emit the `reset` event and if it isn't cancelled reset the benchmark + if (changes.length && (bench.emit(event = Event('reset')), !event.cancelled)) { + _.each(changes, function(data) { + data.destination[data.key] = data.value; + }); + } + } + return bench; + } - /** - * An event listener called when the benchmark is reset. - * - * @memberOf Benchmark.options - * @type Function - */ - 'onReset': undefined, + /** + * Displays relevant benchmark information when coerced to a string. + * + * @name toString + * @memberOf Benchmark + * @returns {String} A string representation of the benchmark instance. + */ + function toStringBench() { + var bench = this, + error = bench.error, + hz = bench.hz, + id = bench.id, + stats = bench.stats, + size = stats.sample.length, + pm = support.java ? '+/-' : '\xb1', + result = bench.name || (_.isNaN(id) ? id : ''); - /** - * An event listener called when the benchmark starts running. - * - * @memberOf Benchmark.options - * @type Function - */ - 'onStart': undefined, + if (error) { + result += ': ' + join(error); + } else { + result += ' x ' + formatNumber(hz.toFixed(hz < 100 ? 2 : 0)) + ' ops/sec ' + pm + + stats.rme.toFixed(2) + '% (' + size + ' run' + (size == 1 ? '' : 's') + ' sampled)'; + } + return result; + } - /** - * The reference time taken to execute the test once (usecs). - * - * @memberOf Benchmark.options - * @type Number - */ - 'reference': 0 - }, + /*------------------------------------------------------------------------*/ /** - * Platform object with properties describing things like browser name, - * version, and operating system. + * Clocks the time taken to execute a test per cycle (secs). * - * @static - * @memberOf Benchmark - * @type Object - */ - 'platform': req('platform') || window.platform || { + * @private + * @param {Object} bench The benchmark instance. + * @returns {Number} The time taken. + */ + function clock() { + var applet, + options = Benchmark.options, + timers = [{ 'ns': timer.ns, 'res': max(0.0015, getRes('ms')), 'unit': 'ms' }]; + + var templateData = { + 'begin': interpolate('s#=new n#'), + 'end': interpolate('r#=(new n#-s#)/1e3'), + 'uid': uid + }; - /** - * The platform description. - * - * @memberOf Benchmark.platform - * @type String - */ - 'description': window.navigator && navigator.userAgent || null, + // lazy define for hi-res timers + clock = function(clone) { + var deferred; + if (clone instanceof Deferred) { + deferred = clone; + clone = deferred.benchmark; + } - /** - * The name of the browser layout engine. - * - * @memberOf Benchmark.platform - * @type String|Null - */ - 'layout': null, + var bench = clone._original, + fn = bench.fn, + fnArg = deferred ? getFirstArgument(fn) || 'deferred' : '', + stringable = isStringable(fn); - /** - * The name of the product hosting the browser. - * - * @memberOf Benchmark.platform - * @type String|Null - */ - 'product': null, + _.extend(templateData, { + 'setup': getSource(bench.setup, interpolate('m#.setup()')), + 'fn': getSource(fn, interpolate('m#.fn(' + fnArg + ')')), + 'fnArg': fnArg, + 'teardown': getSource(bench.teardown, interpolate('m#.teardown()')) + }); - /** - * The name of the browser/environment. - * - * @memberOf Benchmark.platform - * @type String|Null - */ - 'name': null, + var count = bench.count = clone.count, + decompilable = support.decompilation || stringable, + id = bench.id, + isEmpty = !(templateData.fn || stringable), + name = bench.name || (typeof id == 'number' ? '' : id), + ns = timer.ns, + result = 0; + + // init `minTime` if needed + clone.minTime = bench.minTime || (bench.minTime = bench.options.minTime = options.minTime); + + // repair nanosecond timer + // (some Chrome builds erase the `ns` variable after millions of executions) + if (applet) { + try { + ns.nanoTime(); + } catch(e) { + // use non-element to avoid issues with libs that augment them + ns = timer.ns = new applet.Packages.nano; + } + } - /** - * The name of the product's manufacturer. - * - * @memberOf Benchmark.platform - * @type String|Null - */ - 'manufacturer': null, + // Compile in setup/teardown functions and the test loop. + // Create a new compiled test, instead of using the cached `bench.compiled`, + // to avoid potential engine optimizations enabled over the life of the test. + var compiled = bench.compiled = createCompiled( + deferred + ? 'var d#=this,${fnArg}=d#,m#=d#.benchmark._original,f#=m#.fn,su#=m#.setup,td#=m#.teardown;' + + // when `deferred.cycles` is `0` then... + 'if(!d#.cycles){' + + // set `deferred.fn` + 'd#.fn=function(){var ${fnArg}=d#;if(typeof f#=="function"){try{${fn}\n}catch(e#){f#(d#)}}else{${fn}\n}};' + + // set `deferred.teardown` + 'd#.teardown=function(){d#.cycles=0;if(typeof td#=="function"){try{${teardown}\n}catch(e#){td#()}}else{${teardown}\n}};' + + // execute the benchmark's `setup` + 'if(typeof su#=="function"){try{${setup}\n}catch(e#){su#()}}else{${setup}\n};' + + // start timer + 't#.start(d#);' + + // execute `deferred.fn` and return a dummy object + '}d#.fn();return{}' + + : 'var r#,s#,m#=this,f#=m#.fn,i#=m#.count,n#=t#.ns;${setup}\n${begin};' + + 'while(i#--){${fn}\n}${end};${teardown}\nreturn{elapsed:r#,uid:"${uid}"}' + ); - /** - * The name of the operating system. - * - * @memberOf Benchmark.platform - * @type String|Null - */ - 'os': null, + try { + if (isEmpty) { + // Firefox may remove dead code from Function#toString results + // http://bugzil.la/536085 + throw new Error('The test "' + name + '" is empty. This may be the result of dead code removal.'); + } + else if (!deferred) { + // pretest to determine if compiled code is exits early, usually by a + // rogue `return` statement, by checking for a return object with the uid + bench.count = 1; + compiled = (compiled.call(bench, context, timer) || {}).uid == uid && compiled; + bench.count = count; + } + } catch(e) { + compiled = null; + clone.error = e || new Error(String(e)); + bench.count = count; + } + // fallback when a test exits early or errors during pretest + if (decompilable && !compiled && !deferred && !isEmpty) { + compiled = createCompiled( + (clone.error && !stringable + ? 'var r#,s#,m#=this,f#=m#.fn,i#=m#.count' + : 'function f#(){${fn}\n}var r#,s#,m#=this,i#=m#.count' + ) + + ',n#=t#.ns;${setup}\n${begin};m#.f#=f#;while(i#--){m#.f#()}${end};' + + 'delete m#.f#;${teardown}\nreturn{elapsed:r#}' + ); + + try { + // pretest one more time to check for errors + bench.count = 1; + compiled.call(bench, context, timer); + bench.compiled = compiled; + bench.count = count; + delete clone.error; + } + catch(e) { + bench.count = count; + if (clone.error) { + compiled = null; + } else { + bench.compiled = compiled; + clone.error = e || new Error(String(e)); + } + } + } + // assign `compiled` to `clone` before calling in case a deferred benchmark + // immediately calls `deferred.resolve()` + clone.compiled = compiled; + // if no errors run the full test loop + if (!clone.error) { + result = compiled.call(deferred || bench, context, timer).elapsed; + } + return result; + }; + + /*----------------------------------------------------------------------*/ /** - * The alpha/beta release indicator. - * - * @memberOf Benchmark.platform - * @type String|Null + * Creates a compiled function from the given function `body`. */ - 'prerelease': null, + function createCompiled(body) { + return createFunction( + interpolate('window,t#'), + 'var global = window, clearTimeout = global.clearTimeout, setTimeout = global.setTimeout;\n' + + interpolate(body) + ); + } /** - * The browser/environment version. - * - * @memberOf Benchmark.platform - * @type String|Null + * Gets the current timer's minimum resolution (secs). */ - 'version': null, + function getRes(unit) { + var measured, + begin, + count = 30, + divisor = 1e3, + ns = timer.ns, + sample = []; + + // get average smallest measurable time + while (count--) { + if (unit == 'us') { + divisor = 1e6; + if (ns.stop) { + ns.start(); + while (!(measured = ns.microseconds())) { } + } else if (ns[perfName]) { + divisor = 1e3; + measured = Function('n', 'var r,s=n.' + perfName + '();while(!(r=n.' + perfName + '()-s)){};return r')(ns); + } else { + begin = ns(); + while (!(measured = ns() - begin)) { } + } + } + else if (unit == 'ns') { + divisor = 1e9; + if (ns.nanoTime) { + begin = ns.nanoTime(); + while (!(measured = ns.nanoTime() - begin)) { } + } else { + begin = (begin = ns())[0] + (begin[1] / divisor); + while (!(measured = ((measured = ns())[0] + (measured[1] / divisor)) - begin)) { } + divisor = 1; + } + } + else { + begin = new ns; + while (!(measured = new ns - begin)) { } + } + // check for broken timers (nanoTime may have issues) + // http://alivebutsleepy.srnet.cz/unreliable-system-nanotime/ + if (measured > 0) { + sample.push(measured); + } else { + sample.push(Infinity); + break; + } + } + // convert to seconds + return getMean(sample) / divisor; + } /** - * Return platform description when the platform object is coerced to a string. - * - * @memberOf Benchmark.platform - * @type Function - * @returns {String} The platform description. + * Interpolates a given template string. */ - 'toString': function() { - return this.description || ''; + function interpolate(string) { + // replaces all occurrences of `#` with a unique number and template tokens with content + return _.template(string.replace(/\#/g, /\d+/.exec(uid)), templateData || {}); } - }, - /** - * The semantic version number. - * - * @static - * @memberOf Benchmark - * @type String - */ - 'version': '1.0.0', + /*----------------------------------------------------------------------*/ - // an object of environment/feature detection flags - 'support': support, + // detect nanosecond support from a Java applet + _.each(doc && doc.applets || [], function(element) { + return !(timer.ns = applet = 'nanoTime' in element && element); + }); + + // check type in case Safari returns an object instead of a number + try { + if (typeof timer.ns.nanoTime() == 'number') { + timers.push({ 'ns': timer.ns, 'res': getRes('ns'), 'unit': 'ns' }); + } + } catch(e) { } - // clone objects - 'deepClone': deepClone, + // detect Chrome's microsecond timer: + // enable benchmarking via the --enable-benchmarking command + // line switch in at least Chrome 7 to use chrome.Interval + try { + if ((timer.ns = new (context.chrome || context.chromium).Interval)) { + timers.push({ 'ns': timer.ns, 'res': getRes('us'), 'unit': 'us' }); + } + } catch(e) { } - // iteration utility - 'each': each, + // detect `performance.now` microsecond resolution timer + if ((timer.ns = perfName && perfObject)) { + timers.push({ 'ns': timer.ns, 'res': getRes('us'), 'unit': 'us' }); + } - // augment objects - 'extend': extend, + // detect Node's nanosecond resolution timer available in Node >= 0.8 + if (processObject && typeof (timer.ns = processObject.hrtime) == 'function') { + timers.push({ 'ns': timer.ns, 'res': getRes('ns'), 'unit': 'ns' }); + } - // generic Array#filter - 'filter': filter, + // detect Wade Simmons' Node microtime module + if (microtimeObject && typeof (timer.ns = microtimeObject.now) == 'function') { + timers.push({ 'ns': timer.ns, 'res': getRes('us'), 'unit': 'us' }); + } - // generic Array#forEach - 'forEach': forEach, + // pick timer with highest resolution + timer = _.reduce(timers, function(timer, other) { + return other.res < timer.res ? other : timer; + }); - // generic own property iteration utility - 'forOwn': forOwn, + // remove unused applet + if (timer.unit != 'ns' && applet) { + applet = destroyElement(applet); + } + // error if there are no working timers + if (timer.res == Infinity) { + throw new Error('Benchmark.js was unable to find a working timer.'); + } + // use API of chosen timer + if (timer.unit == 'ns') { + if (timer.ns.nanoTime) { + _.extend(templateData, { + 'begin': interpolate('s#=n#.nanoTime()'), + 'end': interpolate('r#=(n#.nanoTime()-s#)/1e9') + }); + } else { + _.extend(templateData, { + 'begin': interpolate('s#=n#()'), + 'end': interpolate('r#=n#(s#);r#=r#[0]+(r#[1]/1e9)') + }); + } + } + else if (timer.unit == 'us') { + if (timer.ns.stop) { + _.extend(templateData, { + 'begin': interpolate('s#=n#.start()'), + 'end': interpolate('r#=n#.microseconds()/1e6') + }); + } else if (perfName) { + _.extend(templateData, { + 'begin': interpolate('s#=n#.' + perfName + '()'), + 'end': interpolate('r#=(n#.' + perfName + '()-s#)/1e3') + }); + } else { + _.extend(templateData, { + 'begin': interpolate('s#=n#()'), + 'end': interpolate('r#=(n#()-s#)/1e6') + }); + } + } - // converts a number to a comma-separated string - 'formatNumber': formatNumber, + // define `timer` methods + timer.start = createFunction( + interpolate('o#'), + interpolate('var n#=this.ns,${begin};o#.elapsed=0;o#.timeStamp=s#') + ); - // generic Object#hasOwnProperty - // (trigger hasKey's lazy define before assigning it to Benchmark) - 'hasKey': (hasKey(Benchmark, ''), hasKey), + timer.stop = createFunction( + interpolate('o#'), + interpolate('var n#=this.ns,s#=o#.timeStamp,${end};o#.elapsed=r#') + ); - // generic Array#indexOf - 'indexOf': indexOf, + // resolve time span required to achieve a percent uncertainty of at most 1% + // http://spiff.rit.edu/classes/phys273/uncert/uncert.html + options.minTime || (options.minTime = max(timer.res / 2 / 0.01, 0.05)); + return clock.apply(null, arguments); + } - // template utility - 'interpolate': interpolate, + /*------------------------------------------------------------------------*/ - // invokes a method on each item in an array - 'invoke': invoke, + /** + * Computes stats on benchmark results. + * + * @private + * @param {Object} bench The benchmark instance. + * @param {Object} options The options object. + */ + function compute(bench, options) { + options || (options = {}); - // generic Array#join for arrays and objects - 'join': join, + var async = options.async, + elapsed = 0, + initCount = bench.initCount, + minSamples = bench.minSamples, + queue = [], + sample = bench.stats.sample; - // generic Array#map - 'map': map, + /** + * Adds a clone to the queue. + */ + function enqueue() { + queue.push(bench.clone({ + '_original': bench, + 'events': { + 'abort': [update], + 'cycle': [update], + 'error': [update], + 'start': [update] + } + })); + } - // retrieves a property value from each item in an array - 'pluck': pluck, + /** + * Updates the clone/original benchmarks to keep their data in sync. + */ + function update(event) { + var clone = this, + type = event.type; + + if (bench.running) { + if (type == 'start') { + // Note: `clone.minTime` prop is inited in `clock()` + clone.count = bench.initCount; + } + else { + if (type == 'error') { + bench.error = clone.error; + } + if (type == 'abort') { + bench.abort(); + bench.emit('cycle'); + } else { + event.currentTarget = event.target = bench; + bench.emit(event); + } + } + } else if (bench.aborted) { + // clear abort listeners to avoid triggering bench's abort/cycle again + clone.events.abort.length = 0; + clone.abort(); + } + } - // generic Array#reduce - 'reduce': reduce - }); + /** + * Determines if more clones should be queued or if cycling should stop. + */ + function evaluate(event) { + var critical, + df, + mean, + moe, + rme, + sd, + sem, + variance, + clone = event.target, + done = bench.aborted, + now = +new Date, + size = sample.push(clone.times.period), + maxedOut = size >= minSamples && (elapsed += now - clone.times.timeStamp) / 1e3 > bench.maxTime, + times = bench.times, + varOf = function(sum, x) { return sum + pow(x - mean, 2); }; + + // exit early for aborted or unclockable tests + if (done || clone.hz == Infinity) { + maxedOut = !(size = sample.length = queue.length = 0); + } - /*--------------------------------------------------------------------------*/ + if (!done) { + // sample mean (estimate of the population mean) + mean = getMean(sample); + // sample variance (estimate of the population variance) + variance = _.reduce(sample, varOf, 0) / (size - 1) || 0; + // sample standard deviation (estimate of the population standard deviation) + sd = sqrt(variance); + // standard error of the mean (a.k.a. the standard deviation of the sampling distribution of the sample mean) + sem = sd / sqrt(size); + // degrees of freedom + df = size - 1; + // critical value + critical = tTable[Math.round(df) || 1] || tTable.infinity; + // margin of error + moe = sem * critical; + // relative margin of error + rme = (moe / mean) * 100 || 0; + + _.extend(bench.stats, { + 'deviation': sd, + 'mean': mean, + 'moe': moe, + 'rme': rme, + 'sem': sem, + 'variance': variance + }); - extend(Benchmark.prototype, { + // Abort the cycle loop when the minimum sample size has been collected + // and the elapsed time exceeds the maximum time allowed per benchmark. + // We don't count cycle delays toward the max time because delays may be + // increased by browsers that clamp timeouts for inactive tabs. + // https://developer.mozilla.org/en/window.setTimeout#Inactive_tabs + if (maxedOut) { + // reset the `initCount` in case the benchmark is rerun + bench.initCount = initCount; + bench.running = false; + done = true; + times.elapsed = (now - times.timeStamp) / 1e3; + } + if (bench.hz != Infinity) { + bench.hz = 1 / mean; + times.cycle = mean * bench.count; + times.period = mean; + } + } + // if time permits, increase sample size to reduce the margin of error + if (queue.length < 2 && !maxedOut) { + enqueue(); + } + // abort the invoke cycle when done + event.aborted = done; + } - /** - * The number of times a test was executed. - * - * @memberOf Benchmark - * @type Number - */ - 'count': 0, + // init queue and begin + enqueue(); + invoke(queue, { + 'name': 'run', + 'args': { 'async': async }, + 'queued': true, + 'onCycle': evaluate, + 'onComplete': function() { bench.emit('complete'); } + }); + } - /** - * The number of cycles performed while benchmarking. - * - * @memberOf Benchmark - * @type Number - */ - 'cycles': 0, + /*------------------------------------------------------------------------*/ /** - * The number of executions per second. + * Cycles a benchmark until a run `count` can be established. * - * @memberOf Benchmark - * @type Number + * @private + * @param {Object} clone The cloned benchmark instance. + * @param {Object} options The options object. */ - 'hz': 0, + function cycle(clone, options) { + options || (options = {}); - /** - * The compiled test function. - * - * @memberOf Benchmark - * @type Function|String - */ - 'compiled': undefined, + var deferred; + if (clone instanceof Deferred) { + deferred = clone; + clone = clone.benchmark; + } - /** - * The error object if the test failed. - * - * @memberOf Benchmark - * @type Object - */ - 'error': undefined, + var clocked, + cycles, + divisor, + event, + minTime, + period, + async = options.async, + bench = clone._original, + count = clone.count, + times = clone.times; + + // continue, if not aborted between cycles + if (clone.running) { + // `minTime` is set to `Benchmark.options.minTime` in `clock()` + cycles = ++clone.cycles; + clocked = deferred ? deferred.elapsed : clock(clone); + minTime = clone.minTime; - /** - * The test to benchmark. - * - * @memberOf Benchmark - * @type Function|String - */ - 'fn': undefined, + if (cycles > bench.cycles) { + bench.cycles = cycles; + } + if (clone.error) { + event = Event('error'); + event.message = clone.error; + clone.emit(event); + if (!event.cancelled) { + clone.abort(); + } + } + } - /** - * A flag to indicate if the benchmark is aborted. - * - * @memberOf Benchmark - * @type Boolean - */ - 'aborted': false, + // continue, if not errored + if (clone.running) { + // time taken to complete last test cycle + bench.times.cycle = times.cycle = clocked; + // seconds per operation + period = bench.times.period = times.period = clocked / count; + // ops per second + bench.hz = clone.hz = 1 / period; + // avoid working our way up to this next time + bench.initCount = clone.initCount = count; + // do we need to do another cycle? + clone.running = clocked < minTime; + + if (clone.running) { + // tests may clock at `0` when `initCount` is a small number, + // to avoid that we set its count to something a bit higher + if (!clocked && (divisor = divisors[clone.cycles]) != null) { + count = floor(4e6 / divisor); + } + // calculate how many more iterations it will take to achive the `minTime` + if (count <= clone.count) { + count += Math.ceil((minTime - clocked) / period); + } + clone.running = count != Infinity; + } + } + // should we exit early? + event = Event('cycle'); + clone.emit(event); + if (event.aborted) { + clone.abort(); + } + // figure out what to do next + if (clone.running) { + // start a new cycle + clone.count = count; + if (deferred) { + clone.compiled.call(deferred, context, timer); + } else if (async) { + delay(clone, function() { cycle(clone, options); }); + } else { + cycle(clone); + } + } + else { + // fix TraceMonkey bug associated with clock fallbacks + // http://bugzil.la/509069 + if (support.browser) { + runScript(uid + '=1;delete ' + uid); + } + // done + clone.emit('complete'); + } + } - /** - * A flag to indicate if the benchmark is running. - * - * @memberOf Benchmark - * @type Boolean - */ - 'running': false, + /*------------------------------------------------------------------------*/ /** - * Compiled into the test and executed immediately **before** the test loop. + * Runs the benchmark. * * @memberOf Benchmark - * @type Function|String + * @param {Object} [options={}] Options object. + * @returns {Object} The benchmark instance. * @example * * // basic usage - * var bench = Benchmark({ - * 'setup': function() { - * var c = this.count, - * element = document.getElementById('container'); - * while (c--) { - * element.appendChild(document.createElement('div')); - * } - * }, - * 'fn': function() { - * element.removeChild(element.lastChild); - * } - * }); - * - * // compiles to something like: - * var c = this.count, - * element = document.getElementById('container'); - * while (c--) { - * element.appendChild(document.createElement('div')); - * } - * var start = new Date; - * while (count--) { - * element.removeChild(element.lastChild); - * } - * var end = new Date - start; - * - * // or using strings - * var bench = Benchmark({ - * 'setup': '\ - * var a = 0;\n\ - * (function() {\n\ - * (function() {\n\ - * (function() {', - * 'fn': 'a += 1;', - * 'teardown': '\ - * }())\n\ - * }())\n\ - * }())' - * }); + * bench.run(); * - * // compiles to something like: - * var a = 0; - * (function() { - * (function() { - * (function() { - * var start = new Date; - * while (count--) { - * a += 1; - * } - * var end = new Date - start; - * }()) - * }()) - * }()) + * // or with options + * bench.run({ 'async': true }); */ - 'setup': noop, + function run(options) { + var bench = this, + event = Event('start'); - /** - * Compiled into the test and executed immediately **after** the test loop. - * - * @memberOf Benchmark - * @type Function|String - */ - 'teardown': noop, + // set `running` to `false` so `reset()` won't call `abort()` + bench.running = false; + bench.reset(); + bench.running = true; - /** - * An object of stats including mean, margin or error, and standard deviation. - * - * @memberOf Benchmark - * @type Object - */ - 'stats': { + bench.count = bench.initCount; + bench.times.timeStamp = +new Date; + bench.emit(event); + + if (!event.cancelled) { + options = { 'async': ((options = options && options.async) == null ? bench.async : options) && support.timeout }; + + // for clones created within `compute()` + if (bench._original) { + if (bench.defer) { + Deferred(bench); + } else { + cycle(bench, options); + } + } + // for original benchmarks + else { + compute(bench, options); + } + } + return bench; + } + + /*------------------------------------------------------------------------*/ + + // Firefox 1 erroneously defines variable and argument names of functions on + // the function itself as non-configurable properties with `undefined` values. + // The bugginess continues as the `Benchmark` constructor has an argument + // named `options` and Firefox 1 will not assign a value to `Benchmark.options`, + // making it non-writable in the process, unless it is the first property + // assigned by for-in loop of `_.extend()`. + _.extend(Benchmark, { + + /** + * The default options copied by benchmark instances. + * + * @static + * @memberOf Benchmark + * @type Object + */ + 'options': { + + /** + * A flag to indicate that benchmark cycles will execute asynchronously + * by default. + * + * @memberOf Benchmark.options + * @type Boolean + */ + 'async': false, + + /** + * A flag to indicate that the benchmark clock is deferred. + * + * @memberOf Benchmark.options + * @type Boolean + */ + 'defer': false, + + /** + * The delay between test cycles (secs). + * @memberOf Benchmark.options + * @type Number + */ + 'delay': 0.005, + + /** + * Displayed by Benchmark#toString when a `name` is not available + * (auto-generated if absent). + * + * @memberOf Benchmark.options + * @type String + */ + 'id': undefined, + + /** + * The default number of times to execute a test on a benchmark's first cycle. + * + * @memberOf Benchmark.options + * @type Number + */ + 'initCount': 1, + + /** + * The maximum time a benchmark is allowed to run before finishing (secs). + * + * Note: Cycle delays aren't counted toward the maximum time. + * + * @memberOf Benchmark.options + * @type Number + */ + 'maxTime': 5, + + /** + * The minimum sample size required to perform statistical analysis. + * + * @memberOf Benchmark.options + * @type Number + */ + 'minSamples': 5, + + /** + * The time needed to reduce the percent uncertainty of measurement to 1% (secs). + * + * @memberOf Benchmark.options + * @type Number + */ + 'minTime': 0, + + /** + * The name of the benchmark. + * + * @memberOf Benchmark.options + * @type String + */ + 'name': undefined, + + /** + * An event listener called when the benchmark is aborted. + * + * @memberOf Benchmark.options + * @type Function + */ + 'onAbort': undefined, + + /** + * An event listener called when the benchmark completes running. + * + * @memberOf Benchmark.options + * @type Function + */ + 'onComplete': undefined, + + /** + * An event listener called after each run cycle. + * + * @memberOf Benchmark.options + * @type Function + */ + 'onCycle': undefined, + + /** + * An event listener called when a test errors. + * + * @memberOf Benchmark.options + * @type Function + */ + 'onError': undefined, + + /** + * An event listener called when the benchmark is reset. + * + * @memberOf Benchmark.options + * @type Function + */ + 'onReset': undefined, + + /** + * An event listener called when the benchmark starts running. + * + * @memberOf Benchmark.options + * @type Function + */ + 'onStart': undefined + }, + + /** + * Platform object with properties describing things like browser name, + * version, and operating system. + * + * @static + * @memberOf Benchmark + * @type Object + */ + 'platform': context.platform || req('platform') || ({ + 'description': context.navigator && context.navigator.userAgent || null, + 'layout': null, + 'product': null, + 'name': null, + 'manufacturer': null, + 'os': null, + 'prerelease': null, + 'version': null, + 'toString': function() { + return this.description || ''; + } + }), + + /** + * The semantic version number. + * + * @static + * @memberOf Benchmark + * @type String + */ + 'version': '1.0.0' + }); + + _.extend(Benchmark, { + 'filter': filter, + 'formatNumber': formatNumber, + 'invoke': invoke, + 'join': join, + 'runInContext': runInContext, + 'support': support + }); + + /*------------------------------------------------------------------------*/ + + _.extend(Benchmark.prototype, { /** - * The margin of error. + * The number of times a test was executed. * - * @memberOf Benchmark#stats + * @memberOf Benchmark * @type Number */ - 'moe': 0, + 'count': 0, /** - * The relative margin of error (expressed as a percentage of the mean). + * The number of cycles performed while benchmarking. * - * @memberOf Benchmark#stats + * @memberOf Benchmark * @type Number */ - 'rme': 0, + 'cycles': 0, /** - * The standard error of the mean. + * The number of executions per second. * - * @memberOf Benchmark#stats + * @memberOf Benchmark * @type Number */ - 'sem': 0, + 'hz': 0, /** - * The sample standard deviation. + * The compiled test function. * - * @memberOf Benchmark#stats - * @type Number + * @memberOf Benchmark + * @type Function|String */ - 'deviation': 0, + 'compiled': undefined, /** - * The sample arithmetic mean. + * The error object if the test failed. * - * @memberOf Benchmark#stats - * @type Number + * @memberOf Benchmark + * @type Object */ - 'mean': 0, + 'error': undefined, /** - * The array of sampled periods. + * The test to benchmark. * - * @memberOf Benchmark#stats - * @type Array + * @memberOf Benchmark + * @type Function|String */ - 'sample': [], + 'fn': undefined, /** - * The sample variance. + * A flag to indicate if the benchmark is aborted. * - * @memberOf Benchmark#stats - * @type Number + * @memberOf Benchmark + * @type Boolean */ - 'variance': 0 - }, - - /** - * An object of timing data including cycle, elapsed, period, start, and stop. - * - * @memberOf Benchmark - * @type Object - */ - 'times': { + 'aborted': false, /** - * The time taken to complete the last cycle (secs). + * A flag to indicate if the benchmark is running. * - * @memberOf Benchmark#times - * @type Number + * @memberOf Benchmark + * @type Boolean */ - 'cycle': 0, + 'running': false, /** - * The time taken to complete the benchmark (secs). + * Compiled into the test and executed immediately **before** the test loop. * - * @memberOf Benchmark#times - * @type Number + * @memberOf Benchmark + * @type Function|String + * @example + * + * // basic usage + * var bench = Benchmark({ + * 'setup': function() { + * var c = this.count, + * element = document.getElementById('container'); + * while (c--) { + * element.appendChild(document.createElement('div')); + * } + * }, + * 'fn': function() { + * element.removeChild(element.lastChild); + * } + * }); + * + * // compiles to something like: + * var c = this.count, + * element = document.getElementById('container'); + * while (c--) { + * element.appendChild(document.createElement('div')); + * } + * var start = new Date; + * while (count--) { + * element.removeChild(element.lastChild); + * } + * var end = new Date - start; + * + * // or using strings + * var bench = Benchmark({ + * 'setup': '\ + * var a = 0;\n\ + * (function() {\n\ + * (function() {\n\ + * (function() {', + * 'fn': 'a += 1;', + * 'teardown': '\ + * }())\n\ + * }())\n\ + * }())' + * }); + * + * // compiles to something like: + * var a = 0; + * (function() { + * (function() { + * (function() { + * var start = new Date; + * while (count--) { + * a += 1; + * } + * var end = new Date - start; + * }()) + * }()) + * }()) */ - 'elapsed': 0, + 'setup': noop, /** - * The time taken to execute the test once (secs). + * Compiled into the test and executed immediately **after** the test loop. * - * @memberOf Benchmark#times - * @type Number + * @memberOf Benchmark + * @type Function|String */ - 'period': 0, + 'teardown': noop, /** - * A timestamp of when the benchmark started (ms). + * An object of stats including mean, margin or error, and standard deviation. * - * @memberOf Benchmark#times - * @type Number + * @memberOf Benchmark + * @type Object */ - 'timeStamp': 0 - }, - - // aborts benchmark (does not record times) - 'abort': abort, - - // creates a new benchmark using the same test and options - 'clone': clone, - - // compares benchmark's hertz with another - 'compare': compare, - - // executes listeners - 'emit': emit, - - // get listeners - 'listeners': listeners, - - // unregister listeners - 'off': off, - - // register listeners - 'on': on, - - // reset benchmark properties - 'reset': reset, - - // runs the benchmark - 'run': run, - - // pretty print benchmark info - 'toString': toStringBench - }); - - /*--------------------------------------------------------------------------*/ - - extend(Deferred.prototype, { - - /** - * The deferred benchmark instance. - * - * @memberOf Benchmark.Deferred - * @type Object - */ - 'benchmark': null, - - /** - * The number of deferred cycles performed while benchmarking. - * - * @memberOf Benchmark.Deferred - * @type Number - */ - 'cycles': 0, - - /** - * The time taken to complete the deferred benchmark (secs). - * - * @memberOf Benchmark.Deferred - * @type Number - */ - 'elapsed': 0, - - /** - * A timestamp of when the deferred benchmark started (ms). - * - * @memberOf Benchmark.Deferred - * @type Number - */ - 'timeStamp': 0, - - // cycles/completes the deferred benchmark - 'resolve': resolve - }); - - /*--------------------------------------------------------------------------*/ - - extend(Event.prototype, { - - /** - * A flag to indicate if the emitters listener iteration is aborted. - * - * @memberOf Benchmark.Event - * @type Boolean - */ - 'aborted': false, - - /** - * A flag to indicate if the default action is cancelled. - * - * @memberOf Benchmark.Event - * @type Boolean - */ - 'cancelled': false, - - /** - * The object whose listeners are currently being processed. - * - * @memberOf Benchmark.Event - * @type Object - */ - 'currentTarget': undefined, - - /** - * The return value of the last executed listener. - * - * @memberOf Benchmark.Event - * @type Mixed - */ - 'result': undefined, - - /** - * The object to which the event was originally emitted. - * - * @memberOf Benchmark.Event - * @type Object - */ - 'target': undefined, - - /** - * A timestamp of when the event was created (ms). - * - * @memberOf Benchmark.Event - * @type Number - */ - 'timeStamp': 0, + 'stats': { + + /** + * The margin of error. + * + * @memberOf Benchmark#stats + * @type Number + */ + 'moe': 0, + + /** + * The relative margin of error (expressed as a percentage of the mean). + * + * @memberOf Benchmark#stats + * @type Number + */ + 'rme': 0, + + /** + * The standard error of the mean. + * + * @memberOf Benchmark#stats + * @type Number + */ + 'sem': 0, + + /** + * The sample standard deviation. + * + * @memberOf Benchmark#stats + * @type Number + */ + 'deviation': 0, + + /** + * The sample arithmetic mean (secs). + * + * @memberOf Benchmark#stats + * @type Number + */ + 'mean': 0, + + /** + * The array of sampled periods. + * + * @memberOf Benchmark#stats + * @type Array + */ + 'sample': [], + + /** + * The sample variance. + * + * @memberOf Benchmark#stats + * @type Number + */ + 'variance': 0 + }, - /** - * The event type. - * - * @memberOf Benchmark.Event - * @type String - */ - 'type': '' - }); + /** + * An object of timing data including cycle, elapsed, period, start, and stop. + * + * @memberOf Benchmark + * @type Object + */ + 'times': { + + /** + * The time taken to complete the last cycle (secs). + * + * @memberOf Benchmark#times + * @type Number + */ + 'cycle': 0, + + /** + * The time taken to complete the benchmark (secs). + * + * @memberOf Benchmark#times + * @type Number + */ + 'elapsed': 0, + + /** + * The time taken to execute the test once (secs). + * + * @memberOf Benchmark#times + * @type Number + */ + 'period': 0, + + /** + * A timestamp of when the benchmark started (ms). + * + * @memberOf Benchmark#times + * @type Number + */ + 'timeStamp': 0 + } + }); - /*--------------------------------------------------------------------------*/ + _.extend(Benchmark.prototype, { + 'abort': abort, + 'clone': clone, + 'compare': compare, + 'emit': emit, + 'listeners': listeners, + 'off': off, + 'on': on, + 'reset': reset, + 'run': run, + 'toString': toStringBench + }); - /** - * The default options copied by suite instances. - * - * @static - * @memberOf Benchmark.Suite - * @type Object - */ - Suite.options = { + /*------------------------------------------------------------------------*/ - /** - * The name of the suite. - * - * @memberOf Benchmark.Suite.options - * @type String - */ - 'name': undefined - }; + _.extend(Deferred.prototype, { - /*--------------------------------------------------------------------------*/ + /** + * The deferred benchmark instance. + * + * @memberOf Benchmark.Deferred + * @type Object + */ + 'benchmark': null, - extend(Suite.prototype, { + /** + * The number of deferred cycles performed while benchmarking. + * + * @memberOf Benchmark.Deferred + * @type Number + */ + 'cycles': 0, - /** - * The number of benchmarks in the suite. - * - * @memberOf Benchmark.Suite - * @type Number - */ - 'length': 0, + /** + * The time taken to complete the deferred benchmark (secs). + * + * @memberOf Benchmark.Deferred + * @type Number + */ + 'elapsed': 0, - /** - * A score computed using the normalized result of each benchmark in the suite. - * - * @memberOf Benchmark.Suite - * @type Number - */ - 'score': 0, + /** + * A timestamp of when the deferred benchmark started (ms). + * + * @memberOf Benchmark.Deferred + * @type Number + */ + 'timeStamp': 0 + }); - /** - * A flag to indicate if the suite is aborted. - * - * @memberOf Benchmark.Suite - * @type Boolean - */ - 'aborted': false, + _.extend(Deferred.prototype, { + 'resolve': resolve + }); - /** - * A flag to indicate if the suite is running. - * - * @memberOf Benchmark.Suite - * @type Boolean - */ - 'running': false, + /*------------------------------------------------------------------------*/ - /** - * An `Array#forEach` like method. - * Callbacks may terminate the loop by explicitly returning `false`. - * - * @memberOf Benchmark.Suite - * @param {Function} callback The function called per iteration. - * @returns {Object} The suite iterated over. - */ - 'forEach': methodize(forEach), + _.extend(Event.prototype, { - /** - * An `Array#indexOf` like method. - * - * @memberOf Benchmark.Suite - * @param {Mixed} value The value to search for. - * @returns {Number} The index of the matched value or `-1`. - */ - 'indexOf': methodize(indexOf), + /** + * A flag to indicate if the emitters listener iteration is aborted. + * + * @memberOf Benchmark.Event + * @type Boolean + */ + 'aborted': false, - /** - * Invokes a method on all benchmarks in the suite. - * - * @memberOf Benchmark.Suite - * @param {String|Object} name The name of the method to invoke OR options object. - * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the method with. - * @returns {Array} A new array of values returned from each method invoked. - */ - 'invoke': methodize(invoke), + /** + * A flag to indicate if the default action is cancelled. + * + * @memberOf Benchmark.Event + * @type Boolean + */ + 'cancelled': false, - /** - * Converts the suite of benchmarks to a string. - * - * @memberOf Benchmark.Suite - * @param {String} [separator=','] A string to separate each element of the array. - * @returns {String} The string. - */ - 'join': [].join, + /** + * The object whose listeners are currently being processed. + * + * @memberOf Benchmark.Event + * @type Object + */ + 'currentTarget': undefined, - /** - * An `Array#map` like method. - * - * @memberOf Benchmark.Suite - * @param {Function} callback The function called per iteration. - * @returns {Array} A new array of values returned by the callback. - */ - 'map': methodize(map), + /** + * The return value of the last executed listener. + * + * @memberOf Benchmark.Event + * @type Mixed + */ + 'result': undefined, - /** - * Retrieves the value of a specified property from all benchmarks in the suite. - * - * @memberOf Benchmark.Suite - * @param {String} property The property to pluck. - * @returns {Array} A new array of property values. - */ - 'pluck': methodize(pluck), + /** + * The object to which the event was originally emitted. + * + * @memberOf Benchmark.Event + * @type Object + */ + 'target': undefined, - /** - * Removes the last benchmark from the suite and returns it. - * - * @memberOf Benchmark.Suite - * @returns {Mixed} The removed benchmark. - */ - 'pop': [].pop, + /** + * A timestamp of when the event was created (ms). + * + * @memberOf Benchmark.Event + * @type Number + */ + 'timeStamp': 0, - /** - * Appends benchmarks to the suite. - * - * @memberOf Benchmark.Suite - * @returns {Number} The suite's new length. - */ - 'push': [].push, + /** + * The event type. + * + * @memberOf Benchmark.Event + * @type String + */ + 'type': '' + }); - /** - * Sorts the benchmarks of the suite. - * - * @memberOf Benchmark.Suite - * @param {Function} [compareFn=null] A function that defines the sort order. - * @returns {Object} The sorted suite. - */ - 'sort': [].sort, + /*------------------------------------------------------------------------*/ /** - * An `Array#reduce` like method. + * The default options copied by suite instances. * + * @static * @memberOf Benchmark.Suite - * @param {Function} callback The function called per iteration. - * @param {Mixed} accumulator Initial value of the accumulator. - * @returns {Mixed} The accumulator. + * @type Object */ - 'reduce': methodize(reduce), + Suite.options = { - // aborts all benchmarks in the suite - 'abort': abortSuite, - - // adds a benchmark to the suite - 'add': add, - - // creates a new suite with cloned benchmarks - 'clone': cloneSuite, - - // executes listeners of a specified type - 'emit': emit, + /** + * The name of the suite. + * + * @memberOf Benchmark.Suite.options + * @type String + */ + 'name': undefined + }; - // creates a new suite of filtered benchmarks - 'filter': filterSuite, + /*------------------------------------------------------------------------*/ - // get listeners - 'listeners': listeners, + _.extend(Suite.prototype, { - // unregister listeners - 'off': off, + /** + * The number of benchmarks in the suite. + * + * @memberOf Benchmark.Suite + * @type Number + */ + 'length': 0, - // register listeners - 'on': on, + /** + * A flag to indicate if the suite is aborted. + * + * @memberOf Benchmark.Suite + * @type Boolean + */ + 'aborted': false, - // resets all benchmarks in the suite - 'reset': resetSuite, + /** + * A flag to indicate if the suite is running. + * + * @memberOf Benchmark.Suite + * @type Boolean + */ + 'running': false + }); - // runs all benchmarks in the suite - 'run': runSuite, + _.extend(Suite.prototype, { + 'abort': abortSuite, + 'add': add, + 'clone': cloneSuite, + 'emit': emit, + 'filter': filterSuite, + 'join': arrayRef.join, + 'listeners': listeners, + 'off': off, + 'on': on, + 'pop': arrayRef.pop, + 'push': arrayRef.push, + 'reset': resetSuite, + 'run': runSuite, + 'reverse': arrayRef.reverse, + 'shift': shift, + 'sort': arrayRef.sort, + 'splice': arrayRef.splice, + 'unshift': arrayRef.unshift + }); - // array methods - 'concat': concat, + /*------------------------------------------------------------------------*/ - 'reverse': reverse, + // expose Deferred, Event, and Suite + _.extend(Benchmark, { + 'Deferred': Deferred, + 'Event': Event, + 'Suite': Suite + }); - 'shift': shift, + /*------------------------------------------------------------------------*/ - 'slice': slice, + // avoid array-like object bugs with `Array#shift` and `Array#splice` + // in Firefox < 10 and IE < 9 + if (!_.support.spliceObjects) { + _.each(['pop', 'shift', 'splice'], function(methodName) { + var func = arrayRef[methodName]; - 'splice': splice, + Suite.prototype[methodName] = function() { + var value = this, + result = func.apply(value, arguments); - 'unshift': unshift - }); + if (value.length === 0) { + delete value[0]; + } + return result; + }; + }); + } + // trigger clock's lazy define early to avoid a security error + if (support.air) { + clock({ '_original': { 'fn': noop, 'count': 1, 'options': {} } }); + } + return Benchmark; + } /*--------------------------------------------------------------------------*/ - // expose Deferred, Event and Suite - extend(Benchmark, { - 'Deferred': Deferred, - 'Event': Event, - 'Suite': Suite - }); - // expose Benchmark // some AMD build optimizers, like r.js, check for specific condition patterns like the following: if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) { // define as an anonymous module so, through path mapping, it can be aliased - define(function() { - return Benchmark; + define(['lodash', 'platform'], function(_, platform) { + return runInContext({ + '_': _, + 'platform': platform + }); }); } - // check for `exports` after `define` in case a build optimizer adds an `exports` object - else if (freeExports) { - // in Node.js or RingoJS v0.8.0+ - if (typeof module == 'object' && module && module.exports == freeExports) { - (module.exports = Benchmark).Benchmark = Benchmark; + else { + var Benchmark = runInContext(); + + // check for `exports` after `define` in case a build optimizer adds an `exports` object + if (freeExports && !freeExports.nodeType) { + // in Node.js or RingoJS v0.8.0+ + if (freeModule) { + (freeModule.exports = Benchmark).Benchmark = Benchmark; + } + // in Narwhal or RingoJS v0.7.0- + else { + freeExports.Benchmark = Benchmark; + } } - // in Narwhal or RingoJS v0.7.0- + // in a browser or Rhino else { - freeExports.Benchmark = Benchmark; + window.Benchmark = Benchmark; } } - // in a browser or Rhino - else { - // use square bracket notation so Closure Compiler won't munge `Benchmark` - // http://code.google.com/closure/compiler/docs/api-tutorial3.html#export - window['Benchmark'] = Benchmark; - } - - // trigger clock's lazy define early to avoid a security error - if (support.air) { - clock({ '_original': { 'fn': noop, 'count': 1, 'options': {} } }); - } }(this)); diff --git a/vendor/platform.js/README.md b/vendor/platform.js/README.md index c2f1cb632f..219c00b2fd 100644 --- a/vendor/platform.js/README.md +++ b/vendor/platform.js/README.md @@ -8,7 +8,7 @@ Platform.js is for informational purposes only and **not** intended as a substit ## BestieJS -Platform.js is part of the BestieJS *"Best in Class"* module collection. This means we promote solid browser/environment support, ES5 precedents, unit testing, and plenty of documentation. +Platform.js is part of the BestieJS *"Best in Class"* module collection. This means we promote solid browser/environment support, ES5+ precedents, unit testing, and plenty of documentation. ## Documentation @@ -18,7 +18,7 @@ For a list of upcoming features, check out our [roadmap](https://github.com/best ## Support -Platform.js has been tested in at least Adobe AIR 3.1, Chrome 5-21, Firefox 1-14, IE 6-9, Opera 9.25-12, Safari 3-6, Node.js 0.8.6, Narwhal 0.3.2, RingoJS 0.8, and Rhino 1.7RC5. +Platform.js has been tested in at least Chrome 5~25, Firefox 1~19, IE 6-10, Opera 9.25-12, Safari 3-6, Node.js 0.4.8-0.10.1, Narwhal 0.3.2, PhantomJS 1.8.1, RingoJS 0.9, and Rhino 1.7RC5. ## Installation and usage diff --git a/vendor/platform.js/platform.js b/vendor/platform.js/platform.js index d643c18833..30ce3e1c37 100644 --- a/vendor/platform.js/platform.js +++ b/vendor/platform.js/platform.js @@ -20,11 +20,14 @@ var reOpera = /Opera/; /** Used to resolve a value's internal [[Class]] */ - var toString = {}.toString; + var toString = Object.prototype.toString; /** Detect Java environment */ var java = /Java/.test(getClassOf(window.java)) && window.java; + /** Detect Rhino */ + var rhino = java && getClassOf(window.environment) == 'Environment'; + /** A character to represent alpha */ var alpha = java ? 'a' : '\u03b1'; @@ -581,10 +584,11 @@ // detect stubborn layout engines if (layout == 'iCab' && parseFloat(version) > 3) { layout = ['WebKit']; - } else if (data = - /Opera/.test(name) && 'Presto' || - /\b(?:Midori|Nook|Safari)\b/i.test(ua) && 'WebKit' || - !layout && /\bMSIE\b/i.test(ua) && (/^Mac/.test(os) ? 'Tasman' : 'Trident')) { + } else if ((data = + /Opera/.test(name) && 'Presto' || + /\b(?:Midori|Nook|Safari)\b/i.test(ua) && 'WebKit' || + !layout && /\bMSIE\b/i.test(ua) && (/^Mac/.test(os) ? 'Tasman' : 'Trident') + )) { layout = [data]; } // leverage environment features @@ -597,7 +601,7 @@ arch = data.getProperty('os.arch'); os = os || data.getProperty('os.name') + ' ' + data.getProperty('os.version'); } - if (typeof exports == 'object' && exports) { + if (freeExports) { // if `thisBinding` is the [ModuleScope] if (thisBinding == oldWin && typeof system == 'object' && (data = [system])[0]) { os || (os = data[0].os || null); @@ -610,23 +614,28 @@ name = 'Narwhal'; } } - } else if (typeof process == 'object' && (data = process)) { + } + else if (typeof process == 'object' && (data = process)) { name = 'Node.js'; arch = data.arch; os = data.platform; version = /[\d.]+/.exec(data.version)[0]; } - } else if (getClassOf(window.environment) == 'Environment') { + else if (rhino) { + name = 'Rhino'; + } + } + else if (rhino) { name = 'Rhino'; } } // detect Adobe AIR - else if (getClassOf(data = window.runtime) == 'ScriptBridgingProxyObject') { + else if (getClassOf((data = window.runtime)) == 'ScriptBridgingProxyObject') { name = 'Adobe AIR'; os = data.flash.system.Capabilities.os; } // detect PhantomJS - else if (getClassOf(data = window.phantom) == 'RuntimeObject') { + else if (getClassOf((data = window.phantom)) == 'RuntimeObject') { name = 'PhantomJS'; version = (data = data.version || null) && (data.major + '.' + data.minor + '.' + data.patch); } @@ -646,9 +655,10 @@ } // detect prerelease phases if (version && (data = - /(?:[ab]|dp|pre|[ab]\d+pre)(?:\d+\+?)?$/i.exec(version) || - /(?:alpha|beta)(?: ?\d)?/i.exec(ua + ';' + (useFeatures && nav.appMinorVersion)) || - /\bMinefield\b/i.test(ua) && 'a')) { + /(?:[ab]|dp|pre|[ab]\d+pre)(?:\d+\+?)?$/i.exec(version) || + /(?:alpha|beta)(?: ?\d)?/i.exec(ua + ';' + (useFeatures && nav.appMinorVersion)) || + /\bMinefield\b/i.test(ua) && 'a' + )) { prerelease = /b/i.test(data) ? 'beta' : 'alpha'; version = version.replace(RegExp(data + '\\+?$'), '') + (prerelease == 'beta' ? beta : alpha) + (/\d+\+?/.exec(data) || ''); @@ -689,8 +699,9 @@ // detect BlackBerry OS version // http://docs.blackberry.com/en/developers/deliverables/18169/HTTP_headers_sent_by_BB_Browser_1234911_11.jsp else if (/BlackBerry/.test(product) && (data = - (RegExp(product.replace(/ +/g, ' *') + '/([.\\d]+)', 'i').exec(ua) || 0)[1] || - version)) { + (RegExp(product.replace(/ +/g, ' *') + '/([.\\d]+)', 'i').exec(ua) || 0)[1] || + version + )) { os = 'Device Software ' + data; version = null; } @@ -705,7 +716,7 @@ /Windows XP/.test(os) && version > 8 || version == 8 && !/Trident/.test(ua) )) - ) && !reOpera.test(data = parse.call(forOwn, ua.replace(reOpera, '') + ';')) && data.name) { + ) && !reOpera.test((data = parse.call(forOwn, ua.replace(reOpera, '') + ';'))) && data.name) { // when "indentifying", the UA contains both Opera and the other browser's name data = 'ing as ' + data.name + ((data = data.version) ? ' ' + data : ''); @@ -793,7 +804,7 @@ } } // strip incorrect OS versions - if (version && version.indexOf(data = /[\d.]+$/.exec(os)) == 0 && + if (version && version.indexOf((data = /[\d.]+$/.exec(os))) == 0 && ua.indexOf('/' + data + '-') > -1) { os = trim(os.replace(data, '')); } @@ -981,7 +992,7 @@ }); } // check for `exports` after `define` in case a build optimizer adds an `exports` object - else if (freeExports) { + else if (freeExports && !freeExports.nodeType) { // in Narwhal, Node.js, or RingoJS forOwn(parse(), function(value, key) { freeExports[key] = value; @@ -989,8 +1000,6 @@ } // in a browser or Rhino else { - // use square bracket notation so Closure Compiler won't munge `platform` - // http://code.google.com/closure/compiler/docs/api-tutorial3.html#export - window['platform'] = parse(); + window.platform = parse(); } }(this)); diff --git a/vendor/qunit-clib/README.md b/vendor/qunit-clib/README.md index 7c2edfa8f5..7b44ea8562 100644 --- a/vendor/qunit-clib/README.md +++ b/vendor/qunit-clib/README.md @@ -1,7 +1,7 @@ -# QUnit CLIB v1.2.0 +# QUnit CLIB v1.3.0 ## command-line interface boilerplate -QUnit CLIB helps extend QUnit's CLI support to many common CLI environments. +QUnit CLIB helps extend QUnit’s CLI support to many common CLI environments. ## Screenshot @@ -9,26 +9,29 @@ QUnit CLIB helps extend QUnit's CLI support to many common CLI environments. ## Support -QUnit CLIB has been tested in at least Node.js 0.4.8-0.8.19, Narwhal v0.3.2, PhantomJS 1.8.1, RingoJS v0.9, and Rhino v1.7RC5. +QUnit CLIB has been tested in at least Node.js 0.4.8-0.10.1, Narwhal 0.3.2, PhantomJS 1.8.1, RingoJS 0.9, and Rhino 1.7RC5. ## Usage ```js -(function(window) { +;(function(window) { + 'use strict'; // use a single "load" function var load = typeof require == 'function' ? require : window.load; // load QUnit and CLIB if needed - var QUnit = - window.QUnit || ( - window.addEventListener || (window.addEventListener = Function.prototype), - window.setTimeout || (window.setTimeout = Function.prototype), - window.QUnit = load('path/to/qunit.js') || window.QUnit, - load('path/to/qunit-clib.js'), - window.addEventListener === Function.prototype && delete window.addEventListener, + var QUnit = (function() { + var noop = Function.prototype; + return window.QUnit || ( + window.addEventListener || (window.addEventListener = noop), + window.setTimeout || (window.setTimeout = noop), + window.QUnit = load('../vendor/qunit/qunit/qunit.js') || window.QUnit, + (load('../vendor/qunit-clib/qunit-clib.js') || { 'runInContext': noop }).runInContext(window), + addEventListener === noop && delete window.addEventListener, window.QUnit ); + }()); // explicitly call `QUnit.module()` instead of `module()` // in case we are in a CLI environment @@ -38,9 +41,8 @@ QUnit CLIB has been tested in at least Node.js 0.4.8-0.8.19, Narwhal v0.3.2, Pha // ... }); - // must call `QUnit.start()` if using QUnit < 1.3.0 with Node.js or any - // version of QUnit with Narwhal, PhantomJS, Rhino, or RingoJS - if (!window.document) { + // call `QUnit.start()` for Narwhal, Node.js, PhantomJS, Rhino, and RingoJS + if (!window.document || window.phantom) { QUnit.start(); } }(typeof global == 'object' && global || this)); @@ -49,7 +51,6 @@ QUnit CLIB has been tested in at least Node.js 0.4.8-0.8.19, Narwhal v0.3.2, Pha ## Footnotes 1. QUnit v1.3.0 does not work with Narwhal or Ringo < v0.8.0 - 2. Rhino v1.7RC4 does not support timeout fallbacks `clearTimeout` and `setTimeout` ## Author diff --git a/vendor/qunit-clib/qunit-clib.js b/vendor/qunit-clib/qunit-clib.js index 7174febe15..eb89ef3c1c 100644 --- a/vendor/qunit-clib/qunit-clib.js +++ b/vendor/qunit-clib/qunit-clib.js @@ -1,5 +1,5 @@ /*! - * QUnit CLI Boilerplate v1.2.0 + * QUnit CLI Boilerplate v1.3.0 * Copyright 2011-2012 John-David Dalton * Based on a gist by Jörn Zaefferer * Available under MIT license @@ -7,12 +7,28 @@ ;(function(window) { 'use strict'; + /** Detect free variable `exports` */ + var freeExports = typeof exports == 'object' && exports; + + /** Detect free variable `global` and use it as `window` */ + var freeGlobal = typeof global == 'object' && global; + if (freeGlobal.global === freeGlobal) { + window = freeGlobal; + } + + /*--------------------------------------------------------------------------*/ + /** - * Timeout fallbacks based on the work of Andrea Giammarchi and Weston C. - * https://github.com/WebReflection/wru/blob/master/src/rhinoTimers.js - * http://stackoverflow.com/questions/2261705/how-to-run-a-javascript-function-asynchronously-without-using-settimeout + * Installs the CLI boilerplate additions on the given `context` object. + * + * @memberOf exports + * @param {Object} context The context object. */ - (function() { + function runInContext(context) { + // exit early if no `context` is provided or if `QUnit` does not exist + if (!context || !context.QUnit) { + return; + } /** * Schedules timer-based callbacks. @@ -29,7 +45,7 @@ // https://bugzilla.mozilla.org/show_bug.cgi?id=775566 var task = ids[++counter] = new JavaAdapter(java.util.TimerTask, { 'run': function() { - fn.apply(window, args); + fn.apply(context, args); } }); // support non-functions @@ -53,7 +69,7 @@ /** * Clears the delay set by `setInterval` or `setTimeout`. * - * @memberOf window + * @memberOf context * @param {Number} id The ID of the timeout to be cleared. */ function clearTimer(id) { @@ -67,7 +83,7 @@ /** * Executes a code snippet or function repeatedly, with a delay between each call. * - * @memberOf window + * @memberOf context * @param {Function|String} fn The function to call or string to evaluate. * @oaram {Number} delay The number of milliseconds to delay each `fn` call. * @param [arg1, arg2, ...] Arguments to invoke `fn` with. @@ -80,7 +96,7 @@ /** * Executes a code snippet or a function after specified delay. * - * @memberOf window + * @memberOf context * @param {Function|String} fn The function to call or string to evaluate. * @oaram {Number} delay The number of milliseconds to delay the `fn` call. * @param [arg1, arg2, ...] Arguments to invoke `fn` with. @@ -90,29 +106,17 @@ return schedule(fn, delay, slice.call(arguments, 2)); } - try { - var counter = 0, - ids = {}, - slice = Array.prototype.slice, - timer = new java.util.Timer; - - window.clearInterval = - window.clearTimeout = clearTimer; - window.setInterval = setInterval; - window.setTimeout = setTimeout; - } catch(e) { } - }()); + /*------------------------------------------------------------------------*/ - /*--------------------------------------------------------------------------*/ + /** Add `console.log()` support for Narwhal, Rhino, and RingoJS */ + var console = context.console || (context.console = { 'log': context.print }); - (function() { + /** Shorten `context.QUnit.QUnit` to `context.QUnit` */ + var QUnit = context.QUnit = context.QUnit.QUnit || context.QUnit; /** Used as a horizontal rule in console output */ var hr = '----------------------------------------'; - /** Shorten `window.QUnit.QUnit` to `window.QUnit` */ - window.QUnit && (QUnit = QUnit.QUnit || QUnit); - /** * A logging callback triggered when all testing is completed. * @@ -134,14 +138,14 @@ console.log(' Finished in ' + details.runtime + ' milliseconds.'); console.log(hr); - // exit out of Rhino + // exit out of Narhwal, Rhino, or Ringo try { quit(); } catch(e) { } // exit out of Node.js or PhantomJS try { - var process = window.process || window.phantom; + var process = context.process || context.phantom; if (details.failed) { console.error('Error: ' + details.failed + ' of ' + details.total + ' tests failed.'); process.exit(1); @@ -247,23 +251,42 @@ */ 'assertions': [] }; - }()); - /*--------------------------------------------------------------------------*/ + /*------------------------------------------------------------------------*/ + + // Timeout fallbacks based on the work of Andrea Giammarchi and Weston C. + // https://github.com/WebReflection/wru/blob/master/src/rhinoTimers.js + // http://stackoverflow.com/questions/2261705/how-to-run-a-javascript-function-asynchronously-without-using-settimeout + try { + var counter = 0, + ids = {}, + slice = Array.prototype.slice, + timer = new java.util.Timer; + + context.clearInterval = + context.clearTimeout = clearTimer; + context.setInterval = setInterval; + context.setTimeout = setTimeout; + } catch(e) { } - // expose shortcuts - // exclude `module` because some environments have it as a built-in object - ('asyncTest deepEqual equal equals expect notDeepEqual notEqual notStrictEqual ' + - 'ok raises same start stop strictEqual test throws').replace(/\S+/g, function(methodName) { - window[methodName] = QUnit[methodName]; - }); + // expose shortcuts + // exclude `module` because some environments have it as a built-in object + ('asyncTest deepEqual equal equals expect notDeepEqual notEqual notStrictEqual ' + + 'ok raises same start stop strictEqual test throws').replace(/\S+/g, function(methodName) { + context[methodName] = QUnit[methodName]; + }); - // add `console.log()` support for Narwhal, Rhino, and RingoJS - if (!window.console && window.print) { - window.console = { 'log': window.print }; + // must call `QUnit.start()` in the test file if using QUnit < 1.3.0 with + // Node.js or any version of QUnit with Narwhal, PhantomJS, Rhino, or RingoJS + QUnit.init(); } - // must call `QUnit.start()` in the test file if using QUnit < 1.3.0 with - // Node.js or any version of QUnit with Narwhal, PhantomJS, Rhino, or RingoJS - QUnit.init(); -}(typeof global == 'object' && global || this)); + /*--------------------------------------------------------------------------*/ + + // expose QUnit CLIB + if (freeExports) { + freeExports.runInContext = runInContext; + } else { + runInContext(window); + } +}(this)); diff --git a/vendor/requirejs/require.js b/vendor/requirejs/require.js index 5b2687543f..062516acd9 100644 --- a/vendor/requirejs/require.js +++ b/vendor/requirejs/require.js @@ -1,5 +1,5 @@ /** vim: et:ts=4:sw=4:sts=4 - * @license RequireJS 2.1.4 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved. + * @license RequireJS 2.1.5 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved. * Available via the MIT or new BSD license. * see: http://github.com/jrburke/requirejs for details */ @@ -12,7 +12,7 @@ var requirejs, require, define; (function (global) { var req, s, head, baseElement, dataMain, src, interactiveScript, currentlyAddingScript, mainScript, subPath, - version = '2.1.4', + version = '2.1.5', commentRegExp = /(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg, cjsRequireRegExp = /[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g, jsSuffixRegExp = /\.js$/, @@ -191,15 +191,21 @@ var requirejs, require, define; var inCheckLoaded, Module, context, handlers, checkLoadedTimeoutId, config = { + //Defaults. Do not set a default for map + //config to speed up normalize(), which + //will run faster if there is no default. waitSeconds: 7, baseUrl: './', paths: {}, pkgs: {}, shim: {}, - map: {}, config: {} }, registry = {}, + //registry of just enabled modules, to speed + //cycle breaking code when lots of modules + //are registered, but not activated. + enabledRegistry = {}, undefEvents = {}, defQueue = [], defined = {}, @@ -295,7 +301,7 @@ var requirejs, require, define; } //Apply map config if available. - if (applyMap && (baseParts || starMap) && map) { + if (applyMap && map && (baseParts || starMap)) { nameParts = name.split('/'); for (i = nameParts.length; i > 0; i -= 1) { @@ -576,6 +582,7 @@ var requirejs, require, define; function cleanRegistry(id) { //Clean up machinery used for waiting modules. delete registry[id]; + delete enabledRegistry[id]; } function breakCycle(mod, traced, processed) { @@ -624,7 +631,7 @@ var requirejs, require, define; inCheckLoaded = true; //Figure out the state of all the modules. - eachProp(registry, function (mod) { + eachProp(enabledRegistry, function (mod) { map = mod.map; modId = map.id; @@ -805,7 +812,7 @@ var requirejs, require, define; }, /** - * Checks is the module is ready to define itself, and if so, + * Checks if the module is ready to define itself, and if so, * define it. */ check: function () { @@ -883,7 +890,7 @@ var requirejs, require, define; } //Clean up - delete registry[id]; + cleanRegistry(id); this.defined = true; } @@ -1049,6 +1056,7 @@ var requirejs, require, define; }, enable: function () { + enabledRegistry[this.map.id] = this; this.enabled = true; //Set flag mentioning that the module is enabling, @@ -1208,6 +1216,7 @@ var requirejs, require, define; Module: Module, makeModuleMap: makeModuleMap, nextTick: req.nextTick, + onError: onError, /** * Set a configuration for the context. @@ -1234,6 +1243,9 @@ var requirejs, require, define; eachProp(cfg, function (value, prop) { if (objs[prop]) { if (prop === 'map') { + if (!config.map) { + config.map = {}; + } mixin(config[prop], value, true, true); } else { mixin(config[prop], value, true); @@ -1345,7 +1357,7 @@ var requirejs, require, define; //Synchronous access to one module. If require.get is //available (as in the Node adapter), prefer that. if (req.get) { - return req.get(context, deps, relMap); + return req.get(context, deps, relMap, localRequire); } //Normalize module name, if it contains . or .. @@ -1396,7 +1408,7 @@ var requirejs, require, define; * plain URLs like nameToUrl. */ toUrl: function (moduleNamePlusExt) { - var ext, url, + var ext, index = moduleNamePlusExt.lastIndexOf('.'), segment = moduleNamePlusExt.split('/')[0], isRelative = segment === '.' || segment === '..'; @@ -1408,9 +1420,8 @@ var requirejs, require, define; moduleNamePlusExt = moduleNamePlusExt.substring(0, index); } - url = context.nameToUrl(normalize(moduleNamePlusExt, - relMap && relMap.id, true), ext || '.fake'); - return ext ? url : url.substring(0, url.length - 5); + return context.nameToUrl(normalize(moduleNamePlusExt, + relMap && relMap.id, true), ext, true); }, defined: function (id) { @@ -1529,7 +1540,7 @@ var requirejs, require, define; * it is assumed to have already been normalized. This is an * internal API, not a public one. Use toUrl for the public API. */ - nameToUrl: function (moduleName, ext) { + nameToUrl: function (moduleName, ext, skipExt) { var paths, pkgs, pkg, pkgPath, syms, i, parentModule, url, parentPath; @@ -1578,7 +1589,7 @@ var requirejs, require, define; //Join the path parts together, then figure out if baseUrl is needed. url = syms.join('/'); - url += (ext || (/\?/.test(url) ? '' : '.js')); + url += (ext || (/\?/.test(url) || skipExt ? '' : '.js')); url = (url.charAt(0) === '/' || url.match(/^[\w\+\.\-]+:/) ? '' : config.baseUrl) + url; } @@ -1817,7 +1828,7 @@ var requirejs, require, define; node.attachEvent('onreadystatechange', context.onScriptLoad); //It would be great to add an error handler here to catch //404s in IE9+. However, onreadystatechange will fire before - //the error handler, so that does not help. If addEvenListener + //the error handler, so that does not help. If addEventListener //is used, then IE will fire error before load, but we cannot //use that pathway given the connect.microsoft.com issue //mentioned above about not doing the 'script execute, @@ -1846,16 +1857,24 @@ var requirejs, require, define; return node; } else if (isWebWorker) { - //In a web worker, use importScripts. This is not a very - //efficient use of importScripts, importScripts will block until - //its script is downloaded and evaluated. However, if web workers - //are in play, the expectation that a build has been done so that - //only one script needs to be loaded anyway. This may need to be - //reevaluated if other use cases become common. - importScripts(url); - - //Account for anonymous modules - context.completeLoad(moduleName); + try { + //In a web worker, use importScripts. This is not a very + //efficient use of importScripts, importScripts will block until + //its script is downloaded and evaluated. However, if web workers + //are in play, the expectation that a build has been done so that + //only one script needs to be loaded anyway. This may need to be + //reevaluated if other use cases become common. + importScripts(url); + + //Account for anonymous modules + context.completeLoad(moduleName); + } catch (e) { + context.onError(makeError('importscripts', + 'importScripts failed for ' + + moduleName + ' at ' + url, + e, + [moduleName])); + } } };