diff --git a/.travis.yml b/.travis.yml index 7cc82bd0ef..2ea46dcbba 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,9 @@ node_js: - "0.10" env: global: - - BIN="node" BUILD=false MAKE=false OPTION="" + - BIN="node" BUILD=false COMPAT=false MAKE=false OPTION="" SAUCE_LABS=false + - SAUCE_USERNAME="jdalton" + - secure: "woILQltl1pI3DgadZ5NrcqntdPvnRmQBwIVNZL91Ht5d9snIhgyAixI6xNAS8F8BzD9RzqzVPHay5sHfn+GhNaojcaiHs1nXAbdyclevMyfP+3MQ1HGfMSU0bv1GdT35LJ+C0u4Y3SuuZSbBlNEeLXRPMngPZahf4xL8RsZz/is=" matrix: - BUILD="compat" - BUILD="modern" @@ -34,25 +36,45 @@ matrix: env: BIN="ringo" BUILD="compat" - node_js: "0.10" env: BIN="ringo" BUILD="legacy" + - node_js: "0.8" + env: SAUCE_LABS=true BUILD="compat" + - node_js: "0.8" + env: SAUCE_LABS=true BUILD="modern" + - node_js: "0.8" + env: SAUCE_LABS=true BUILD="legacy" + - node_js: "0.8" + env: SAUCE_LABS=true BUILD="mobile" + - node_js: "0.8" + env: SAUCE_LABS=true BUILD="underscore" git: - depth: 1 + depth: 10 branches: only: - master before_install: - - "[ $BIN == 'istanbul' ] && npm install -g istanbul || true" + - "([ $BUILD == 'legacy' ] || [ $BUILD == 'mobile' ] || [ $BUILD == 'modern' ]) && MAKE=true || true" + - "([ $BUILD == 'compat' ] || [ $BUILD == 'legacy' ]) && COMPAT=true || true" + - "[ $SAUCE_LABS != false ] && npm i ecstatic@\"~0.4.0\" request@\"~2.27.0\" sauce-tunnel@\"~1.1.0\" || true" + - "[ $BIN == 'istanbul' ] && npm i -g istanbul@\"~0.1.0\" || true" - "[ $BIN == 'narwhal' ] && wget https://github.com/280north/narwhal/archive/v0.3.2.zip && sudo unzip v0.3.2 -d /opt/ && rm v0.3.2.zip || true" - "[ $BIN == 'narwhal' ] && sudo ln -s /opt/narwhal-0.3.2/bin/narwhal /usr/local/bin/narwhal && sudo chmod +x /usr/local/bin/narwhal || true" - "[ $BIN == 'rhino' ] && sudo mkdir /opt/rhino-1.7R5 && sudo wget -O /opt/rhino-1.7R5/js.jar https://oss.sonatype.org/content/repositories/snapshots/org/mozilla/rhino/1.7R5-SNAPSHOT/rhino-1.7R5-20120629.144839-4.jar || true" - "[ $BIN == 'rhino' ] && echo -e '#!/bin/sh\\njava -jar /opt/rhino-1.7R5/js.jar $@' | sudo tee /usr/local/bin/rhino && sudo chmod +x /usr/local/bin/rhino || true" - "[ $BIN == 'ringo' ] && wget http://ringojs.org/downloads/ringojs-0.9.zip && sudo unzip ringojs-0.9 -d /opt && rm ringojs-0.9.zip || true" - "[ $BIN == 'ringo' ] && sudo ln -s /opt/ringojs-0.9/bin/ringo /usr/local/bin/ringo && sudo chmod +x /usr/local/bin/ringo || true" -script: - - "[ $BIN == 'istanbul' ] && $BIN cover ./test/test.js || true" - - "[ $BUILD != false ] && [ $BUILD != 'compat' ] && [ $BUILD != 'modern' ] && MAKE=true || true" - - "[ $MAKE != false ] && git clone --depth=1 --branch=master git://github.com/lodash/lodash-cli.git ./node_modules/lodash-cli || true" + - "[ $MAKE != false ] && git clone --depth=10 --branch=master git://github.com/lodash/lodash-cli.git ./node_modules/lodash-cli || true" - "[ $MAKE != false ] && mkdir ./node_modules/lodash-cli/node_modules && cd ./node_modules/lodash-cli/node_modules/ && ln -s ../../../ ./lodash && cd ../ && npm i . && cd ../../ || true" - "[ $MAKE != false ] && node ./node_modules/lodash-cli/bin/lodash $BUILD -o ./dist/lodash.$BUILD.js || true" - - "[ $BUILD != false ] && cd ./test || true" - - "[ $BUILD != false ] && $BIN $OPTION ./test.js ../dist/lodash.$BUILD.js || true" - - "[ $BUILD != false ] && $BIN $OPTION ./test.js ../dist/lodash.$BUILD.min.js || true" +script: + - "[ $BIN == 'istanbul' ] && $BIN cover ./test/test.js || true" + - "([ $SAUCE_LABS != false ] || [ $BUILD == false ]) && true || cd ./test" + - "([ $SAUCE_LABS != false ] || [ $BUILD == false ]) && true || $BIN $OPTION ./test.js ../dist/lodash.$BUILD.js" + - "([ $SAUCE_LABS != false ] || [ $BUILD == false ]) && true || $BIN $OPTION ./test.js ../dist/lodash.$BUILD.min.js" + - "([ $SAUCE_LABS == false ] || [ $BUILD == 'underscore' ]) && true || node ./test/saucelabs.js \"test/index.html?build=lodash-$BUILD\"" + - "([ $SAUCE_LABS == false ] || [ $BUILD == 'underscore' ]) && true || node ./test/saucelabs.js \"test/index.html?build=../dist/lodash.$BUILD.js\"" + - "[ $SAUCE_LABS == false ] && true || node ./test/saucelabs.js \"test/backbone.html?build=lodash-$BUILD\"" + - "[ $SAUCE_LABS == false ] && true || node ./test/saucelabs.js \"test/backbone.html?build=../dist/lodash.$BUILD.js\"" + - "[ $SAUCE_LABS == false ] && true || node ./test/saucelabs.js \"test/underscore.html?build=lodash-$BUILD\"" + - "[ $SAUCE_LABS == false ] && true || node ./test/saucelabs.js \"test/underscore.html?build=../dist/lodash.$BUILD.js\"" + - "([ $SAUCE_LABS == false ] || [ $COMPAT == false ]) && true || node ./test/saucelabs.js \"test/index.html?build=lodash-$BUILD&compat=7\"" + - "([ $SAUCE_LABS == false ] || [ $COMPAT == false ]) && true || node ./test/saucelabs.js \"test/index.html?build=../dist/lodash.$BUILD.js&compat=7\"" diff --git a/README.md b/README.md index 3a2abc5321..854345f794 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,33 @@ -# Lo-Dash v2.2.1 +# Lo-Dash v2.3.0 A utility library delivering consistency, [customization](http://lodash.com/custom-builds), [performance](http://lodash.com/benchmarks), & [extras](http://lodash.com/#features). ## Download +Check out our [wiki]([https://github.com/lodash/lodash/wiki/build-differences]) for details over the differences between builds. + * Modern builds perfect for newer browsers/environments:
-[Development](https://raw.github.com/lodash/lodash/2.2.1/dist/lodash.js) & -[Production](https://raw.github.com/lodash/lodash/2.2.1/dist/lodash.min.js) +[Development](https://raw.github.com/lodash/lodash/2.3.0/dist/lodash.js) & +[Production](https://raw.github.com/lodash/lodash/2.3.0/dist/lodash.min.js) * Compatibility builds for older environment support too:
-[Development](https://raw.github.com/lodash/lodash/2.2.1/dist/lodash.compat.js) & -[Production](https://raw.github.com/lodash/lodash/2.2.1/dist/lodash.compat.min.js) +[Development](https://raw.github.com/lodash/lodash/2.3.0/dist/lodash.compat.js) & +[Production](https://raw.github.com/lodash/lodash/2.3.0/dist/lodash.compat.min.js) * Underscore builds to use as a drop-in replacement:
-[Development](https://raw.github.com/lodash/lodash/2.2.1/dist/lodash.underscore.js) & -[Production](https://raw.github.com/lodash/lodash/2.2.1/dist/lodash.underscore.min.js) +[Development](https://raw.github.com/lodash/lodash/2.3.0/dist/lodash.underscore.js) & +[Production](https://raw.github.com/lodash/lodash/2.3.0/dist/lodash.underscore.min.js) + +CDN copies are available on [cdnjs](http://cdnjs.com/libraries/lodash.js/) & [jsDelivr](http://www.jsdelivr.com/#!lodash). For smaller file sizes, create [custom builds](http://lodash.com/custom-builds) with only the features needed. -CDN copies are available on [cdnjs](http://cdnjs.com/libraries/lodash.js/) & [jsDelivr](http://www.jsdelivr.com/#!lodash).
-For smaller file sizes, create [custom builds](http://lodash.com/custom-builds) with only the features needed.
-Love modules? We’ve got you covered with [lodash-amd](https://npmjs.org/package/lodash-amd), [lodash-node](https://npmjs.org/package/lodash-node), & [npm packages](https://npmjs.org/browse/keyword/lodash-modularized) per method. +Love modules? We’ve got you covered with [lodash-amd](https://npmjs.org/package/lodash-amd), [lodash-es6](https://github.com/lodash/lodash-es6), [lodash-node](https://npmjs.org/package/lodash-node), & [npm packages](https://npmjs.org/browse/keyword/lodash-modularized) per method. ## Dive in -There’s plenty of [documentation](http://lodash.com/docs), [unit tests](http://lodash.com/tests), & [benchmarks](http://lodash.com/benchmarks).
-For a list of upcoming features, check out our [roadmap](https://github.com/lodash/lodash/wiki/Roadmap).
-The full changelog for this release is available on our [wiki](https://github.com/lodash/lodash/wiki/Changelog). +There’s plenty of **[documentation](http://lodash.com/docs)**, [unit tests](http://lodash.com/tests), & [benchmarks](http://lodash.com/benchmarks).
+Check out DevDocs as a fast, organized, & searchable interface for our documentation. + +The full changelog for this release is available on our [wiki](https://github.com/lodash/lodash/wiki/Changelog).
+A list of upcoming features is available on our [roadmap](https://github.com/lodash/lodash/wiki/Roadmap). ## Features *not* in Underscore @@ -34,6 +38,7 @@ The full changelog for this release is available on our [wiki](https://github.co * [_.clone](http://lodash.com/docs#clone) supports shallow cloning of `Date` & `RegExp` objects * [_.cloneDeep](http://lodash.com/docs#cloneDeep) for deep cloning arrays & objects * [_.contains](http://lodash.com/docs#contains) accepts a `fromIndex` + * [_.create](http://lodash.com/docs#create) for easier object inheritance * [_.createCallback](http://lodash.com/docs#createCallback) for extending callbacks in methods & mixins * [_.curry](http://lodash.com/docs#curry) for creating [curried](http://hughfdjackson.com/javascript/2013/07/06/why-curry-helps/) functions * [_.debounce](http://lodash.com/docs#debounce) & [_.throttle](http://lodash.com/docs#throttle) accept additional `options` for more control @@ -44,6 +49,7 @@ The full changelog for this release is available on our [wiki](https://github.co * [_.isPlainObject](http://lodash.com/docs#isPlainObject) for checking if values are created by `Object` * [_.memoize](http://lodash.com/docs#memoize) exposes the `cache` of memoized functions * [_.merge](http://lodash.com/docs#merge) for a deep [_.extend](http://lodash.com/docs#extend) + * [_.noop](http://lodash.com/docs#noop) for function placeholders * [_.parseInt](http://lodash.com/docs#parseInt) for consistent behavior * [_.partialRight](http://lodash.com/docs#partialRight) for [partial application](http://lodash.com/docs#partial) from the right * [_.pull](http://lodash.com/docs#pull) & [_.remove](http://lodash.com/docs#remove) for mutating arrays @@ -65,6 +71,9 @@ The full changelog for this release is available on our [wiki](https://github.co ## Resources + * Podcasts + - [JavaScript Jabber](http://javascriptjabber.com/079-jsj-lo-dash-with-john-david-dalton/) + * Posts - [Say “Hello” to Lo-Dash](http://kitcambridge.be/blog/say-hello-to-lo-dash/) - [Custom builds in Lo-Dash 2.0](http://kitcambridge.be/blog/custom-builds-in-lo-dash-2-dot-0/) @@ -77,9 +86,14 @@ The full changelog for this release is available on our [wiki](https://github.co - [Testing](https://vimeo.com/45865290) - [CascadiaJS ’12](http://www.youtube.com/watch?v=dpPy4f_SeEk) + A list of other community created podcasts, posts, & videos is available on our [wiki](https://github.com/lodash/lodash/wiki/Resources). + ## Support -Tested in Chrome 5~29, Firefox 2~24, IE 6-10, Opera 9.25~16, Safari 3-6, Node.js 0.6.8-0.10.20, Narwhal 0.3.2, PhantomJS 1.9.2, RingoJS 0.9, & Rhino 1.7RC5. +Tested in Chrome 5~30, Firefox 2~25, IE 6-11, Opera 9.25~17, Safari 3-7, Node.js 0.6.8-0.10.21, Narwhal 0.3.2, PhantomJS 1.9.2, RingoJS 0.9, & Rhino 1.7RC5. + +Special thanks to [Sauce Labs](https://saucelabs.com/) for providing automated browser testing.
+[![Sauce Labs](http://lodash.com/_img/sauce.png)](https://saucelabs.com/ "Sauce Labs: Selenium Testing & More") ## Installation & usage @@ -109,7 +123,6 @@ var _ = require('lodash/dist/lodash.underscore'); **Notes:** * Don’t assign values to [special variable](http://nodejs.org/api/repl.html#repl_repl_features) `_` when in the REPL * If Lo-Dash is installed globally, run [`npm ln lodash`](http://blog.nodejs.org/2011/03/23/npm-1-0-global-vs-local-installation/) in your project’s root directory *before* requiring it - * Node.js 0.10.8-0.10.11 [have](https://github.com/joyent/node/issues/5622) [bugs](https://github.com/joyent/node/issues/5688) preventing minified builds In [Rhino](http://www.mozilla.org/rhino/): @@ -132,12 +145,12 @@ require({ ## Author -| [![twitter/jdalton](http://gravatar.com/avatar/299a3d891ff1920b69c364d061007043?s=70)](http://twitter.com/jdalton "Follow @jdalton on Twitter") | +| [![twitter/jdalton](http://gravatar.com/avatar/299a3d891ff1920b69c364d061007043?s=70)](https://twitter.com/jdalton "Follow @jdalton on Twitter") | |---| | [John-David Dalton](http://allyoucanleet.com/) | ## Contributors -| [![twitter/blainebublitz](http://gravatar.com/avatar/ac1c67fd906c9fecd823ce302283b4c1?s=70)](http://twitter.com/blainebublitz "Follow @BlaineBublitz on Twitter") | [![twitter/kitcambridge](http://gravatar.com/avatar/6662a1d02f351b5ef2f8b4d815804661?s=70)](https://twitter.com/kitcambridge "Follow @kitcambridge on Twitter") | [![twitter/mathias](http://gravatar.com/avatar/24e08a9ea84deb17ae121074d0f17125?s=70)](http://twitter.com/mathias "Follow @mathias on Twitter") | +| [![twitter/blainebublitz](http://gravatar.com/avatar/ac1c67fd906c9fecd823ce302283b4c1?s=70)](https://twitter.com/blainebublitz "Follow @BlaineBublitz on Twitter") | [![twitter/kitcambridge](http://gravatar.com/avatar/6662a1d02f351b5ef2f8b4d815804661?s=70)](https://twitter.com/kitcambridge "Follow @kitcambridge on Twitter") | [![twitter/mathias](http://gravatar.com/avatar/24e08a9ea84deb17ae121074d0f17125?s=70)](https://twitter.com/mathias "Follow @mathias on Twitter") | |---|---|---| -| [Blaine Bublitz](http://iceddev.com/) | [Kit Cambridge](http://kitcambridge.github.io/) | [Mathias Bynens](http://mathiasbynens.be/) | +| [Blaine Bublitz](http://www.iceddev.com/) | [Kit Cambridge](http://kitcambridge.be/) | [Mathias Bynens](http://mathiasbynens.be/) | diff --git a/bower.json b/bower.json index 352320e2cc..57ac7ae2a3 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "lodash", - "version": "2.2.1", + "version": "2.3.0", "main": "dist/lodash.compat.js", "ignore": [ ".*", diff --git a/component.json b/component.json index 5750a62152..8be106fbd6 100644 --- a/component.json +++ b/component.json @@ -1,7 +1,7 @@ { "name": "lodash", "repo": "lodash/lodash", - "version": "2.2.1", + "version": "2.3.0", "description": "A utility library delivering consistency, customization, performance, & extras.", "license": "MIT", "keywords": ["amd", "browser", "client", "functional", "server", "util"], diff --git a/dist/lodash.compat.js b/dist/lodash.compat.js index e48344d4cf..957c070c07 100644 --- a/dist/lodash.compat.js +++ b/dist/lodash.compat.js @@ -1,6 +1,6 @@ /** * @license - * Lo-Dash 2.2.1 (Custom Build) + * Lo-Dash 2.3.0 (Custom Build) * Build: `lodash -o ./dist/lodash.compat.js` * Copyright 2012-2013 The Dojo Foundation * Based on Underscore.js 1.5.2 @@ -58,7 +58,7 @@ var reFlags = /\w*$/; /** Used to detected named functions */ - var reFuncName = /^function[ \n\r\t]+\w/; + var reFuncName = /^\s*function[ \n\r\t]+\w/; /** Used to match "interpolate" template delimiters */ var reInterpolate = /<%=([\s\S]+?)%>/g; @@ -391,15 +391,6 @@ return typeof value.toString != 'function' && typeof (value + '') == 'string'; } - /** - * A no-operation function. - * - * @private - */ - function noop() { - // no operation performed - } - /** * Releases the given array back to the array pool. * @@ -505,11 +496,14 @@ /** Used to restore the original `_` reference in `noConflict` */ var oldDash = context._; + /** Used to resolve the internal [[Class]] of values */ + var toString = objectProto.toString; + /** Used to detect if a method is native */ var reNative = RegExp('^' + - String(objectProto.valueOf) + String(toString) .replace(/[.*+?^${}()|[\]\\]/g, '\\$&') - .replace(/valueOf|for [^\]]+/g, '.+?') + '$' + .replace(/toString| for [^\]]+/g, '.*?') + '$' ); /** Native method shortcuts */ @@ -522,13 +516,16 @@ now = reNative.test(now = Date.now) && now || function() { return +new Date; }, push = arrayRef.push, propertyIsEnumerable = objectProto.propertyIsEnumerable, - setImmediate = context.setImmediate, setTimeout = context.setTimeout, - splice = arrayRef.splice, - toString = objectProto.toString, - unshift = arrayRef.unshift; + splice = arrayRef.splice; + + /** Used to detect `setImmediate` in Node.js */ + var setImmediate = typeof (setImmediate = freeGlobal && moduleExports && freeGlobal.setImmediate) == 'function' && + !reNative.test(setImmediate) && setImmediate; + /** Used to set meta data on functions */ var defineProperty = (function() { + // IE 8 only accepts DOM elements try { var o = {}, func = reNative.test(func = Object.defineProperty) && func, @@ -538,8 +535,7 @@ }()); /* Native method shortcuts for methods with the same name as other `lodash` methods */ - var nativeBind = reNative.test(nativeBind = toString.bind) && nativeBind, - nativeCreate = reNative.test(nativeCreate = Object.create) && nativeCreate, + var nativeCreate = reNative.test(nativeCreate = Object.create) && nativeCreate, nativeIsArray = reNative.test(nativeIsArray = Array.isArray) && nativeIsArray, nativeIsFinite = context.isFinite, nativeIsNaN = context.isNaN, @@ -547,12 +543,7 @@ nativeMax = Math.max, nativeMin = Math.min, nativeParseInt = context.parseInt, - nativeRandom = Math.random, - nativeSlice = arrayRef.slice; - - /** Detect various environments */ - var isIeOpera = reNative.test(context.attachEvent), - isV8 = nativeBind && !/\n|true/.test(nativeBind + isIeOpera); + nativeRandom = Math.random; /** Used to lookup a built-in constructor by [[Class]] */ var ctorByClass = {}; @@ -575,10 +566,10 @@ (function() { var length = shadowedProps.length; while (length--) { - var prop = shadowedProps[length]; + var key = shadowedProps[length]; for (var className in nonEnumProps) { - if (hasOwnProperty.call(nonEnumProps, className) && !hasOwnProperty.call(nonEnumProps[className], prop)) { - nonEnumProps[className][prop] = false; + if (hasOwnProperty.call(nonEnumProps, className) && !hasOwnProperty.call(nonEnumProps[className], key)) { + nonEnumProps[className][key] = false; } } } @@ -599,15 +590,16 @@ * * The chainable wrapper functions are: * `after`, `assign`, `bind`, `bindAll`, `bindKey`, `chain`, `compact`, - * `compose`, `concat`, `countBy`, `createCallback`, `curry`, `debounce`, - * `defaults`, `defer`, `delay`, `difference`, `filter`, `flatten`, `forEach`, - * `forEachRight`, `forIn`, `forInRight`, `forOwn`, `forOwnRight`, `functions`, - * `groupBy`, `indexBy`, `initial`, `intersection`, `invert`, `invoke`, `keys`, - * `map`, `max`, `memoize`, `merge`, `min`, `object`, `omit`, `once`, `pairs`, - * `partial`, `partialRight`, `pick`, `pluck`, `pull`, `push`, `range`, `reject`, - * `remove`, `rest`, `reverse`, `shuffle`, `slice`, `sort`, `sortBy`, `splice`, - * `tap`, `throttle`, `times`, `toArray`, `transform`, `union`, `uniq`, `unshift`, - * `unzip`, `values`, `where`, `without`, `wrap`, and `zip` + * `compose`, `concat`, `countBy`, `create`, `createCallback`, `curry`, + * `debounce`, `defaults`, `defer`, `delay`, `difference`, `filter`, `flatten`, + * `forEach`, `forEachRight`, `forIn`, `forInRight`, `forOwn`, `forOwnRight`, + * `functions`, `groupBy`, `indexBy`, `initial`, `intersection`, `invert`, + * `invoke`, `keys`, `map`, `max`, `memoize`, `merge`, `min`, `object`, `omit`, + * `once`, `pairs`, `partial`, `partialRight`, `pick`, `pluck`, `pull`, `push`, + * `range`, `reject`, `remove`, `rest`, `reverse`, `shuffle`, `slice`, `sort`, + * `sortBy`, `splice`, `tap`, `throttle`, `times`, `toArray`, `transform`, + * `union`, `uniq`, `unshift`, `unzip`, `values`, `where`, `without`, `wrap`, + * and `zip` * * The non-chainable wrapper functions are: * `clone`, `cloneDeep`, `contains`, `escape`, `every`, `find`, `findIndex`, @@ -687,8 +679,8 @@ props = []; ctor.prototype = { 'valueOf': 1, 'y': 1 }; - for (var prop in new ctor) { props.push(prop); } - for (prop in arguments) { } + for (var key in new ctor) { props.push(key); } + for (key in arguments) { } /** * Detect if an `arguments` object's [[Class]] is resolvable (all but Firefox < 4, IE < 9). @@ -728,14 +720,6 @@ */ support.enumPrototypes = propertyIsEnumerable.call(ctor, '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 functions can be decompiled by `Function#toString` * (all but PS3 and older Opera mobile browsers & avoided in Windows 8 apps). @@ -760,7 +744,7 @@ * @memberOf _.support * @type boolean */ - support.nonEnumArgs = prop != 0; + support.nonEnumArgs = key != 0; /** * Detect if properties shadowing those on `Object.prototype` are non-enumerable. @@ -984,19 +968,53 @@ /*--------------------------------------------------------------------------*/ + /** + * The base implementation of `_.bind` that creates the bound function and + * sets its meta data. + * + * @private + * @param {Array} bindData The bind data array. + * @returns {Function} Returns the new bound function. + */ + function baseBind(bindData) { + var func = bindData[0], + partialArgs = bindData[2], + thisArg = bindData[4]; + + function bound() { + // `Function#bind` spec + // http://es5.github.io/#x15.3.4.5 + if (partialArgs) { + var args = partialArgs.slice(); + push.apply(args, arguments); + } + // mimic the constructor's `return` behavior + // http://es5.github.io/#x13.2.2 + if (this instanceof bound) { + // ensure `new bound` is an instance of `func` + var thisBinding = baseCreate(func.prototype), + result = func.apply(thisBinding, args || arguments); + return isObject(result) ? result : thisBinding; + } + return func.apply(thisArg, args || arguments); + } + setBindData(bound, bindData); + return bound; + } + /** * The base implementation of `_.clone` without argument juggling or support * for `thisArg` binding. * * @private * @param {*} value The value to clone. - * @param {boolean} [deep=false] Specify a deep clone. + * @param {boolean} [isDeep=false] Specify a deep clone. * @param {Function} [callback] The function to customize cloning values. * @param {Array} [stackA=[]] Tracks traversed source objects. * @param {Array} [stackB=[]] Associates clones with source counterparts. * @returns {*} Returns the cloned value. */ - function baseClone(value, deep, callback, stackA, stackB) { + function baseClone(value, isDeep, callback, stackA, stackB) { if (callback) { var result = callback(value); if (typeof result != 'undefined') { @@ -1029,7 +1047,7 @@ return value; } var isArr = isArray(value); - if (deep) { + if (isDeep) { // check for circular references and return corresponding clone var initedStack = !stackA; stackA || (stackA = getArray()); @@ -1056,7 +1074,7 @@ } } // exit for shallow clone - if (!deep) { + if (!isDeep) { return result; } // add the source value to the stack of traversed objects @@ -1066,7 +1084,7 @@ // recursively populate clone (susceptible to call stack limits) (isArr ? baseEach : forOwn)(value, function(objValue, key) { - result[key] = baseClone(objValue, deep, callback, stackA, stackB); + result[key] = baseClone(objValue, isDeep, callback, stackA, stackB); }); if (initedStack) { @@ -1076,6 +1094,32 @@ return result; } + /** + * The base implementation of `_.create` without support for assigning + * properties to the created object. + * + * @private + * @param {Object} prototype The object to inherit from. + * @returns {Object} Returns the new object. + */ + function baseCreate(prototype, properties) { + return isObject(prototype) ? nativeCreate(prototype) : {}; + } + // fallback for browsers without `Object.create` + if (!nativeCreate) { + baseCreate = (function() { + function Object() {} + return function(prototype) { + if (isObject(prototype)) { + Object.prototype = prototype; + var result = new Object; + Object.prototype = null; + } + return result || context.Object(); + }; + }()); + } + /** * The base implementation of `_.createCallback` without support for creating * "_.pluck" or "_.where" style callbacks. @@ -1090,24 +1134,30 @@ if (typeof func != 'function') { return identity; } - // exit early if there is no `thisArg` - if (typeof thisArg == 'undefined') { + // exit early for no `thisArg` or already bound by `Function#bind` + if (typeof thisArg == 'undefined' || !('prototype' in func)) { return func; } - var bindData = func.__bindData__ || (support.funcNames && !func.name); + var bindData = func.__bindData__; if (typeof bindData == 'undefined') { - var source = reThis && fnToString.call(func); - if (!support.funcNames && source && !reFuncName.test(source)) { - bindData = true; + if (support.funcNames) { + bindData = !func.name; } - if (support.funcNames || !bindData) { - // checks if `func` references the `this` keyword and stores the result - bindData = !support.funcDecomp || reThis.test(source); - setBindData(func, bindData); + bindData = bindData || !support.funcDecomp; + if (!bindData) { + var source = fnToString.call(func); + if (!support.funcNames) { + bindData = !reFuncName.test(source); + } + if (!bindData) { + // checks if `func` references the `this` keyword and stores the result + bindData = reThis.test(source); + setBindData(func, bindData); + } } } // exit early if there are no `this` references or `func` is bound - if (bindData !== true && (bindData && bindData[1] & 1)) { + if (bindData === false || (bindData !== true && bindData[1] & 1)) { return func; } switch (argCount) { @@ -1127,6 +1177,96 @@ return bind(func, thisArg); } + /** + * The base implementation of `createWrapper` that creates the wrapper and + * sets its meta data. + * + * @private + * @param {Array} bindData The bind data array. + * @returns {Function} Returns the new function. + */ + function baseCreateWrapper(bindData) { + var func = bindData[0], + bitmask = bindData[1], + partialArgs = bindData[2], + partialRightArgs = bindData[3], + thisArg = bindData[4], + arity = bindData[5]; + + var isBind = bitmask & 1, + isBindKey = bitmask & 2, + isCurry = bitmask & 4, + isCurryBound = bitmask & 8, + key = func; + + function bound() { + var thisBinding = isBind ? thisArg : this; + if (partialArgs) { + var args = partialArgs.slice(); + push.apply(args, arguments); + } + if (partialRightArgs || isCurry) { + args || (args = slice(arguments)); + if (partialRightArgs) { + push.apply(args, partialRightArgs); + } + if (isCurry && args.length < arity) { + bitmask |= 16 & ~32; + return baseCreateWrapper([func, (isCurryBound ? bitmask : bitmask & ~3), args, null, thisArg, arity]); + } + } + args || (args = arguments); + if (isBindKey) { + func = thisBinding[key]; + } + if (this instanceof bound) { + thisBinding = baseCreate(func.prototype); + var result = func.apply(thisBinding, args); + return isObject(result) ? result : thisBinding; + } + return func.apply(thisBinding, args); + } + setBindData(bound, bindData); + return bound; + } + + /** + * The base implementation of `_.difference` that accepts a single array + * of values to exclude. + * + * @private + * @param {Array} array The array to process. + * @param {Array} [values] The array of values to exclude. + * @returns {Array} Returns a new array of filtered values. + */ + function baseDifference(array, values) { + var index = -1, + indexOf = getIndexOf(), + length = array ? array.length : 0, + isLarge = length >= largeArraySize && indexOf === baseIndexOf, + result = []; + + if (isLarge) { + var cache = createCache(values); + if (cache) { + indexOf = cacheIndexOf; + values = cache; + } else { + isLarge = false; + } + } + while (++index < length) { + var value = array[index]; + if (indexOf(values, value) < 0) { + result.push(value); + } + } + if (isLarge) { + releaseObject(values); + } + return result; + } + /** * The base implementation of `_.flatten` without support for callback * shorthands or `thisArg` binding. @@ -1134,11 +1274,11 @@ * @private * @param {Array} array The array to flatten. * @param {boolean} [isShallow=false] A flag to restrict flattening to a single level. - * @param {boolean} [isArgArrays=false] A flag to restrict flattening to arrays and `arguments` objects. + * @param {boolean} [isStrict=false] A flag to restrict flattening to arrays and `arguments` objects. * @param {number} [fromIndex=0] The index to start from. * @returns {Array} Returns a new flattened array. */ - function baseFlatten(array, isShallow, isArgArrays, fromIndex) { + function baseFlatten(array, isShallow, isStrict, fromIndex) { var index = (fromIndex || 0) - 1, length = array ? array.length : 0, result = []; @@ -1150,7 +1290,7 @@ && (isArray(value) || isArguments(value))) { // recursively flatten arrays (susceptible to call stack limits) if (!isShallow) { - value = baseFlatten(value, isShallow, isArgArrays); + value = baseFlatten(value, isShallow, isStrict); } var valIndex = -1, valLength = value.length, @@ -1160,7 +1300,7 @@ while (++valIndex < valLength) { result[resIndex++] = value[valIndex]; } - } else if (!isArgArrays) { + } else if (!isStrict) { result.push(value); } } @@ -1243,8 +1383,11 @@ var isArr = className == arrayClass; if (!isArr) { // unwrap any `lodash` wrapped values - if (hasOwnProperty.call(a, '__wrapped__ ') || hasOwnProperty.call(b, '__wrapped__')) { - return baseIsEqual(a.__wrapped__ || a, b.__wrapped__ || b, callback, isWhere, stackA, stackB); + var aWrapped = hasOwnProperty.call(a, '__wrapped__'), + bWrapped = hasOwnProperty.call(b, '__wrapped__'); + + if (aWrapped || bWrapped) { + return baseIsEqual(aWrapped ? a.__wrapped__ : a, bWrapped ? b.__wrapped__ : b, callback, isWhere, stackA, stackB); } // exit for functions and DOM nodes if (className != objectClass || (!support.nodeClass && (isNode(a) || isNode(b)))) { @@ -1255,10 +1398,10 @@ 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 - )) { + if (ctorA != ctorB && + !(isFunction(ctorA) && ctorA instanceof ctorA && isFunction(ctorB) && ctorB instanceof ctorB) && + ('constructor' in a && 'constructor' in b) + ) { return false; } } @@ -1401,6 +1544,19 @@ }); } + /** + * The base implementation of `_.random` without argument juggling or support + * for returning floating-point numbers. + * + * @private + * @param {number} min The minimum possible value. + * @param {number} max The maximum possible value. + * @returns {number} Returns a random number. + */ + function baseRandom(min, max) { + return min + floor(nativeRandom() * (max - min + 1)); + } + /** * The base implementation of `_.uniq` without support for callback shorthands * or `thisArg` binding. @@ -1505,16 +1661,15 @@ * provided to the new function. * @param {*} [thisArg] The `this` binding of `func`. * @param {number} [arity] The arity of `func`. - * @returns {Function} Returns the new bound function. + * @returns {Function} Returns the new function. */ - function createBound(func, bitmask, partialArgs, partialRightArgs, thisArg, arity) { + function createWrapper(func, bitmask, partialArgs, partialRightArgs, thisArg, arity) { var isBind = bitmask & 1, isBindKey = bitmask & 2, isCurry = bitmask & 4, isCurryBound = bitmask & 8, isPartial = bitmask & 16, - isPartialRight = bitmask & 32, - key = func; + isPartialRight = bitmask & 32; if (!isBindKey && !isFunction(func)) { throw new TypeError; @@ -1528,74 +1683,36 @@ isPartialRight = partialRightArgs = false; } var bindData = func && func.__bindData__; - if (bindData) { + if (bindData && bindData !== true) { + bindData = bindData.slice(); + + // set `thisBinding` is not previously bound if (isBind && !(bindData[1] & 1)) { bindData[4] = thisArg; } + // set if previously bound but not currently (subsequent curried functions) if (!isBind && bindData[1] & 1) { bitmask |= 8; } + // set curried arity if not yet set if (isCurry && !(bindData[1] & 4)) { bindData[5] = arity; } + // append partial left arguments if (isPartial) { push.apply(bindData[2] || (bindData[2] = []), partialArgs); } + // append partial right arguments if (isPartialRight) { push.apply(bindData[3] || (bindData[3] = []), partialRightArgs); } + // merge flags bindData[1] |= bitmask; - return createBound.apply(null, bindData); - } - // use `Function#bind` if it exists and is fast - // (in V8 `Function#bind` is slower except when partially applied) - if (isBind && !(isBindKey || isCurry || isPartialRight) && - (support.fastBind || (nativeBind && isPartial))) { - if (isPartial) { - var args = [thisArg]; - push.apply(args, partialArgs); - } - var bound = isPartial - ? nativeBind.apply(func, args) - : nativeBind.call(func, thisArg); - } - else { - bound = function() { - // `Function#bind` spec - // http://es5.github.io/#x15.3.4.5 - var args = arguments, - thisBinding = isBind ? thisArg : this; - - if (isCurry || isPartial || isPartialRight) { - args = nativeSlice.call(args); - if (isPartial) { - unshift.apply(args, partialArgs); - } - if (isPartialRight) { - push.apply(args, partialRightArgs); - } - if (isCurry && args.length < arity) { - bitmask |= 16 & ~32; - return createBound(func, (isCurryBound ? bitmask : bitmask & ~3), args, null, thisArg, arity); - } - } - if (isBindKey) { - func = thisBinding[key]; - } - if (this instanceof bound) { - // ensure `new bound` is an instance of `func` - thisBinding = createObject(func.prototype); - - // mimic the constructor's `return` behavior - // http://es5.github.io/#x13.2.2 - var result = func.apply(thisBinding, args); - return isObject(result) ? result : thisBinding; - } - return func.apply(thisBinding, args); - }; + return createWrapper.apply(null, bindData); } - setBindData(bound, nativeSlice.call(arguments)); - return bound; + // fast path for `_.bind` + var creater = (bitmask == 1 || bitmask === 17) ? baseBind : baseCreateWrapper; + return creater([func, bitmask, partialArgs, partialRightArgs, thisArg, arity]); } /** @@ -1646,28 +1763,6 @@ ); } - /** - * Creates a new object with the specified `prototype`. - * - * @private - * @param {Object} prototype The prototype object. - * @returns {Object} Returns the new object. - */ - function createObject(prototype) { - return isObject(prototype) ? nativeCreate(prototype) : {}; - } - // fallback for browsers without `Object.create` - if (!nativeCreate) { - createObject = function(prototype) { - if (isObject(prototype)) { - noop.prototype = prototype; - var result = new noop; - noop.prototype = null; - } - return result || {}; - }; - } - /** * Used by `escape` to convert characters to HTML entities. * @@ -1697,7 +1792,7 @@ * * @private * @param {Function} func The function to set data on. - * @param {*} value The value to set. + * @param {Array} value The data array to set. */ var setBindData = !defineProperty ? noop : function(func, value) { descriptor.value = value; @@ -1781,7 +1876,7 @@ if (!support.argsClass) { isArguments = function(value) { return value && typeof value == 'object' && typeof value.length == 'number' && - hasOwnProperty.call(value, 'callee') || false; + hasOwnProperty.call(value, 'callee') && !propertyIsEnumerable.call(value, 'callee') || false; }; } @@ -1937,16 +2032,16 @@ * @returns {Object} Returns the destination object. * @example * - * _.assign({ 'name': 'moe' }, { 'age': 40 }); - * // => { 'name': 'moe', 'age': 40 } + * _.assign({ 'name': 'fred' }, { 'employer': 'slate' }); + * // => { 'name': 'fred', 'employer': 'slate' } * * 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 object = { 'name': 'barney' }; + * defaults(object, { 'name': 'fred', 'employer': 'slate' }); + * // => { 'name': 'barney', 'employer': 'slate' } */ var assign = createIterator(defaultsIteratorOptions, { 'top': @@ -1962,7 +2057,7 @@ }); /** - * Creates a clone of `value`. If `deep` is `true` nested objects will also + * Creates a clone of `value`. If `isDeep` is `true` nested objects will also * be cloned, otherwise they will be assigned by reference. If a callback * is provided it will be executed to produce the cloned values. If the * callback returns `undefined` cloning will be handled by the method instead. @@ -1972,23 +2067,23 @@ * @memberOf _ * @category Objects * @param {*} value The value to clone. - * @param {boolean} [deep=false] Specify a deep clone. + * @param {boolean} [isDeep=false] Specify a deep clone. * @param {Function} [callback] The function to customize cloning values. * @param {*} [thisArg] The `this` binding of `callback`. * @returns {*} Returns the cloned value. * @example * - * var stooges = [ - * { 'name': 'moe', 'age': 40 }, - * { 'name': 'larry', 'age': 50 } + * var characters = [ + * { 'name': 'barney', 'age': 36 }, + * { 'name': 'fred', 'age': 40 } * ]; * - * var shallow = _.clone(stooges); - * shallow[0] === stooges[0]; + * var shallow = _.clone(characters); + * shallow[0] === characters[0]; * // => true * - * var deep = _.clone(stooges, true); - * deep[0] === stooges[0]; + * var deep = _.clone(characters, true); + * deep[0] === characters[0]; * // => false * * _.mixin({ @@ -2001,15 +2096,15 @@ * clone.childNodes.length; * // => 0 */ - function clone(value, deep, callback, thisArg) { + function clone(value, isDeep, callback, thisArg) { // allows working with "Collections" methods without using their `index` - // and `collection` arguments for `deep` and `callback` - if (typeof deep != 'boolean' && deep != null) { + // and `collection` arguments for `isDeep` and `callback` + if (typeof isDeep != 'boolean' && isDeep != null) { thisArg = callback; - callback = deep; - deep = false; + callback = isDeep; + isDeep = false; } - return baseClone(value, deep, typeof callback == 'function' && baseCreateCallback(callback, thisArg, 1)); + return baseClone(value, isDeep, typeof callback == 'function' && baseCreateCallback(callback, thisArg, 1)); } /** @@ -2032,13 +2127,13 @@ * @returns {*} Returns the deep cloned value. * @example * - * var stooges = [ - * { 'name': 'moe', 'age': 40 }, - * { 'name': 'larry', 'age': 50 } + * var characters = [ + * { 'name': 'barney', 'age': 36 }, + * { 'name': 'fred', 'age': 40 } * ]; * - * var deep = _.cloneDeep(stooges); - * deep[0] === stooges[0]; + * var deep = _.cloneDeep(characters); + * deep[0] === characters[0]; * // => false * * var view = { @@ -2057,6 +2152,42 @@ return baseClone(value, true, typeof callback == 'function' && baseCreateCallback(callback, thisArg, 1)); } + /** + * Creates an object that inherits from the given `prototype` object. If a + * `properties` object is provided its own enumerable properties are assigned + * to the created object. + * + * @static + * @memberOf _ + * @category Objects + * @param {Object} prototype The object to inherit from. + * @param {Object} [properties] The properties to assign to the object. + * @returns {Object} Returns the new object. + * @example + * + * function Shape() { + * this.x = 0; + * this.y = 0; + * } + * + * function Circle() { + * Shape.call(this); + * } + * + * Circle.prototype = _.create(Shape.prototype, { 'constructor': Circle }); + * + * var circle = new Circle; + * circle instanceof Circle; + * // => true + * + * circle instanceof Shape; + * // => true + */ + function create(prototype, properties) { + var result = baseCreate(prototype); + return properties ? assign(result, properties) : result; + } + /** * Assigns own enumerable properties of source object(s) to the destination * object for all destination properties that resolve to `undefined`. Once a @@ -2073,9 +2204,9 @@ * @returns {Object} Returns the destination object. * @example * - * var food = { 'name': 'apple' }; - * _.defaults(food, { 'name': 'banana', 'type': 'fruit' }); - * // => { 'name': 'apple', 'type': 'fruit' } + * var object = { 'name': 'barney' }; + * _.defaults(object, { 'name': 'fred', 'employer': 'slate' }); + * // => { 'name': 'barney', 'employer': 'slate' } */ var defaults = createIterator(defaultsIteratorOptions); @@ -2083,6 +2214,13 @@ * This method is like `_.findIndex` except that it returns the key of the * first element that passes the callback check, instead of the element itself. * + * If a property name is provided for `callback` the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is provided 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 Objects @@ -2094,10 +2232,24 @@ * @returns {string|undefined} 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; + * var characters = { + * 'barney': { 'age': 36, 'blocked': false }, + * 'fred': { 'age': 40, 'blocked': true }, + * 'pebbles': { 'age': 1, 'blocked': false } + * }; + * + * _.findKey(characters, function(chr) { + * return chr.age < 40; * }); - * // => 'b' (property order is not guaranteed across environments) + * // => 'barney' (property order is not guaranteed across environments) + * + * // using "_.where" callback shorthand + * _.findKey(characters, { 'age': 1 }); + * // => 'pebbles' + * + * // using "_.pluck" callback shorthand + * _.findKey(characters, 'blocked'); + * // => 'fred' */ function findKey(object, callback, thisArg) { var result; @@ -2115,6 +2267,13 @@ * This method is like `_.findKey` except that it iterates over elements * of a `collection` in the opposite order. * + * If a property name is provided for `callback` the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is provided 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 Objects @@ -2126,10 +2285,24 @@ * @returns {string|undefined} Returns the key of the found element, else `undefined`. * @example * - * _.findLastKey({ 'a': 1, 'b': 2, 'c': 3, 'd': 4 }, function(num) { - * return num % 2 == 1; + * var characters = { + * 'barney': { 'age': 36, 'blocked': true }, + * 'fred': { 'age': 40, 'blocked': false }, + * 'pebbles': { 'age': 1, 'blocked': true } + * }; + * + * _.findLastKey(characters, function(chr) { + * return chr.age < 40; * }); - * // => returns `c`, assuming `_.findKey` returns `a` + * // => returns `pebbles`, assuming `_.findKey` returns `barney` + * + * // using "_.where" callback shorthand + * _.findLastKey(characters, { 'age': 40 }); + * // => 'fred' + * + * // using "_.pluck" callback shorthand + * _.findLastKey(characters, 'blocked'); + * // => 'pebbles' */ function findLastKey(object, callback, thisArg) { var result; @@ -2159,18 +2332,20 @@ * @returns {Object} Returns `object`. * @example * - * function Dog(name) { - * this.name = name; + * function Shape() { + * this.x = 0; + * this.y = 0; * } * - * Dog.prototype.bark = function() { - * console.log('Woof, woof!'); + * Shape.prototype.move = function(x, y) { + * this.x += x; + * this.y += y; * }; * - * _.forIn(new Dog('Dagny'), function(value, key) { + * _.forIn(new Shape, function(value, key) { * console.log(key); * }); - * // => logs 'bark' and 'name' (property order is not guaranteed across environments) + * // => logs 'x', 'y', and 'move' (property order is not guaranteed across environments) */ var forIn = createIterator(eachIteratorOptions, forOwnIteratorOptions, { 'useHas': false @@ -2189,18 +2364,20 @@ * @returns {Object} Returns `object`. * @example * - * function Dog(name) { - * this.name = name; + * function Shape() { + * this.x = 0; + * this.y = 0; * } * - * Dog.prototype.bark = function() { - * console.log('Woof, woof!'); + * Shape.prototype.move = function(x, y) { + * this.x += x; + * this.y += y; * }; * - * _.forInRight(new Dog('Dagny'), function(value, key) { + * _.forInRight(new Shape, function(value, key) { * console.log(key); * }); - * // => logs 'name' and 'bark' assuming `_.forIn ` logs 'bark' and 'name' + * // => logs 'move', 'y', and 'x' assuming `_.forIn ` logs 'x', 'y', and 'move' */ function forInRight(object, callback, thisArg) { var pairs = []; @@ -2328,8 +2505,8 @@ * @returns {Object} Returns the created inverted object. * @example * - * _.invert({ 'first': 'moe', 'second': 'larry' }); - * // => { 'moe': 'first', 'larry': 'second' } + * _.invert({ 'first': 'fred', 'second': 'barney' }); + * // => { 'fred': 'first', 'barney': 'second' } */ function invert(object) { var index = -1, @@ -2358,7 +2535,8 @@ * // => false */ function isBoolean(value) { - return value === true || value === false || toString.call(value) == boolClass; + return value === true || value === false || + value && typeof value == 'object' && toString.call(value) == boolClass || false; } /** @@ -2375,7 +2553,7 @@ * // => true */ function isDate(value) { - return value ? (typeof value == 'object' && toString.call(value) == dateClass) : false; + return value && typeof value == 'object' && toString.call(value) == dateClass || false; } /** @@ -2392,7 +2570,7 @@ * // => true */ function isElement(value) { - return value ? value.nodeType === 1 : false; + return value && value.nodeType === 1 || false; } /** @@ -2452,13 +2630,13 @@ * @returns {boolean} Returns `true` if the values are equivalent, else `false`. * @example * - * var moe = { 'name': 'moe', 'age': 40 }; - * var copy = { 'name': 'moe', 'age': 40 }; + * var object = { 'name': 'fred' }; + * var copy = { 'name': 'fred' }; * - * moe == copy; + * object == copy; * // => false * - * _.isEqual(moe, copy); + * _.isEqual(object, copy); * // => true * * var words = ['hello', 'goodbye']; @@ -2627,7 +2805,8 @@ * // => true */ function isNumber(value) { - return typeof value == 'number' || toString.call(value) == numberClass; + return typeof value == 'number' || + value && typeof value == 'object' && toString.call(value) == numberClass || false; } /** @@ -2640,18 +2819,18 @@ * @returns {boolean} Returns `true` if `value` is a plain object, else `false`. * @example * - * function Stooge(name, age) { - * this.name = name; - * this.age = age; + * function Shape() { + * this.x = 0; + * this.y = 0; * } * - * _.isPlainObject(new Stooge('moe', 40)); + * _.isPlainObject(new Shape); * // => false * * _.isPlainObject([1, 2, 3]); * // => false * - * _.isPlainObject({ 'name': 'moe', 'age': 40 }); + * _.isPlainObject({ 'x': 0, 'y': 0 }); * // => true */ var isPlainObject = !getPrototypeOf ? shimIsPlainObject : function(value) { @@ -2676,11 +2855,11 @@ * @returns {boolean} Returns `true` if the `value` is a regular expression, else `false`. * @example * - * _.isRegExp(/moe/); + * _.isRegExp(/fred/); * // => true */ function isRegExp(value) { - return (value && objectTypes[typeof value]) ? toString.call(value) == regexpClass : false; + return value && objectTypes[typeof value] && toString.call(value) == regexpClass || false; } /** @@ -2693,11 +2872,12 @@ * @returns {boolean} Returns `true` if the `value` is a string, else `false`. * @example * - * _.isString('moe'); + * _.isString('fred'); * // => true */ function isString(value) { - return typeof value == 'string' || toString.call(value) == stringClass; + return typeof value == 'string' || + value && typeof value == 'object' && toString.call(value) == stringClass || false; } /** @@ -2737,21 +2917,21 @@ * @example * * var names = { - * 'stooges': [ - * { 'name': 'moe' }, - * { 'name': 'larry' } + * 'characters': [ + * { 'name': 'barney' }, + * { 'name': 'fred' } * ] * }; * * var ages = { - * 'stooges': [ - * { 'age': 40 }, - * { 'age': 50 } + * 'characters': [ + * { 'age': 36 }, + * { 'age': 40 } * ] * }; * * _.merge(names, ages); - * // => { 'stooges': [{ 'name': 'moe', 'age': 40 }, { 'name': 'larry', 'age': 50 }] } + * // => { 'characters': [{ 'name': 'barney', 'age': 36 }, { 'name': 'fred', 'age': 40 }] } * * var food = { * 'fruits': ['apple'], @@ -2775,6 +2955,7 @@ if (!isObject(object)) { return object; } + // allows working with `_.reduce` and `_.reduceRight` without using // their `index` and `collection` arguments if (typeof args[2] != 'number') { @@ -2785,7 +2966,7 @@ } else if (length > 2 && typeof args[length - 1] == 'function') { callback = args[--length]; } - var sources = nativeSlice.call(arguments, 1, length), + var sources = slice(arguments, 1, length), index = -1, stackA = getArray(), stackB = getArray(); @@ -2816,32 +2997,38 @@ * @returns {Object} Returns an object without the omitted properties. * @example * - * _.omit({ 'name': 'moe', 'age': 40 }, 'age'); - * // => { 'name': 'moe' } + * _.omit({ 'name': 'fred', 'age': 40 }, 'age'); + * // => { 'name': 'fred' } * - * _.omit({ 'name': 'moe', 'age': 40 }, function(value) { + * _.omit({ 'name': 'fred', 'age': 40 }, function(value) { * return typeof value == 'number'; * }); - * // => { 'name': 'moe' } + * // => { 'name': 'fred' } */ function omit(object, callback, thisArg) { - var indexOf = getIndexOf(), - isFunc = typeof callback == 'function', - result = {}; + var result = {}; + if (typeof callback != 'function') { + var props = []; + forIn(object, function(value, key) { + props.push(key); + }); + props = baseDifference(props, baseFlatten(arguments, true, false, 1)); - if (isFunc) { - callback = lodash.createCallback(callback, thisArg, 3); + var index = -1, + length = props.length; + + while (++index < length) { + var key = props[index]; + result[key] = object[key]; + } } else { - var props = baseFlatten(arguments, true, false, 1); + callback = lodash.createCallback(callback, thisArg, 3); + forIn(object, function(value, key, object) { + if (!callback(value, key, object)) { + result[key] = value; + } + }); } - forIn(object, function(value, key, object) { - if (isFunc - ? !callback(value, key, object) - : indexOf(props, key) < 0 - ) { - result[key] = value; - } - }); return result; } @@ -2856,8 +3043,8 @@ * @returns {Array} Returns new array of key-value pairs. * @example * - * _.pairs({ 'moe': 30, 'larry': 40 }); - * // => [['moe', 30], ['larry', 40]] (property order is not guaranteed across environments) + * _.pairs({ 'barney': 36, 'fred': 40 }); + * // => [['barney', 36], ['fred', 40]] (property order is not guaranteed across environments) */ function pairs(object) { var index = -1, @@ -2891,13 +3078,13 @@ * @returns {Object} Returns an object composed of the picked properties. * @example * - * _.pick({ 'name': 'moe', '_userid': 'moe1' }, 'name'); - * // => { 'name': 'moe' } + * _.pick({ 'name': 'fred', '_userid': 'fred1' }, 'name'); + * // => { 'name': 'fred' } * - * _.pick({ 'name': 'moe', '_userid': 'moe1' }, function(value, key) { + * _.pick({ 'name': 'fred', '_userid': 'fred1' }, function(value, key) { * return key.charAt(0) != '_'; * }); - * // => { 'name': 'moe' } + * // => { 'name': 'fred' } */ function pick(object, callback, thisArg) { var result = {}; @@ -2934,7 +3121,7 @@ * @static * @memberOf _ * @category Objects - * @param {Array|Object} collection The collection to iterate over. + * @param {Array|Object} object The object to iterate over. * @param {Function} [callback=identity] The function called per iteration. * @param {*} [accumulator] The custom accumulator value. * @param {*} [thisArg] The `this` binding of `callback`. @@ -2956,8 +3143,6 @@ */ function transform(object, callback, accumulator, thisArg) { var isArr = isArray(object); - callback = baseCreateCallback(callback, thisArg, 4); - if (accumulator == null) { if (isArr) { accumulator = []; @@ -2965,12 +3150,15 @@ var ctor = object && object.constructor, proto = ctor && ctor.prototype; - accumulator = createObject(proto); + accumulator = baseCreate(proto); } } - (isArr ? baseEach : forOwn)(object, function(value, index, object) { - return callback(accumulator, value, index, object); - }); + if (callback) { + callback = lodash.createCallback(callback, thisArg, 4); + (isArr ? baseEach : forOwn)(object, function(value, index, object) { + return callback(accumulator, value, index, object); + }); + } return accumulator; } @@ -3019,8 +3207,8 @@ * _.at(['a', 'b', 'c', 'd', 'e'], [0, 2, 4]); * // => ['a', 'c', 'e'] * - * _.at(['moe', 'larry', 'curly'], 0, 2); - * // => ['moe', 'curly'] + * _.at(['fred', 'barney', 'pebbles'], 0, 2); + * // => ['fred', 'pebbles'] */ function at(collection) { var args = arguments, @@ -3059,10 +3247,10 @@ * _.contains([1, 2, 3], 1, 2); * // => false * - * _.contains({ 'name': 'moe', 'age': 40 }, 'moe'); + * _.contains({ 'name': 'fred', 'age': 40 }, 'fred'); * // => true * - * _.contains('curly', 'ur'); + * _.contains('pebbles', 'eb'); * // => true */ function contains(collection, target, fromIndex) { @@ -3149,20 +3337,20 @@ * else `false`. * @example * - * _.every([true, 1, null, 'yes'], Boolean); + * _.every([true, 1, null, 'yes']); * // => false * - * var stooges = [ - * { 'name': 'moe', 'age': 40 }, - * { 'name': 'larry', 'age': 50 } + * var characters = [ + * { 'name': 'barney', 'age': 36 }, + * { 'name': 'fred', 'age': 40 } * ]; * * // using "_.pluck" callback shorthand - * _.every(stooges, 'age'); + * _.every(characters, 'age'); * // => true * * // using "_.where" callback shorthand - * _.every(stooges, { 'age': 50 }); + * _.every(characters, { 'age': 36 }); * // => false */ function every(collection, callback, thisArg) { @@ -3213,18 +3401,18 @@ * 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' } + * var characters = [ + * { 'name': 'barney', 'age': 36, 'blocked': false }, + * { 'name': 'fred', 'age': 40, 'blocked': true } * ]; * * // using "_.pluck" callback shorthand - * _.filter(food, 'organic'); - * // => [{ 'name': 'carrot', 'organic': true, 'type': 'vegetable' }] + * _.filter(characters, 'blocked'); + * // => [{ 'name': 'fred', 'age': 40, 'blocked': true }] * * // using "_.where" callback shorthand - * _.filter(food, { 'type': 'fruit' }); - * // => [{ 'name': 'apple', 'organic': false, 'type': 'fruit' }] + * _.filter(characters, { 'age': 36 }); + * // => [{ 'name': 'barney', 'age': 36, 'blocked': false }] */ function filter(collection, callback, thisArg) { var result = []; @@ -3274,24 +3462,24 @@ * @returns {*} 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' } + * var characters = [ + * { 'name': 'barney', 'age': 36, 'blocked': false }, + * { 'name': 'fred', 'age': 40, 'blocked': true }, + * { 'name': 'pebbles', 'age': 1, 'blocked': false } * ]; * + * _.find(characters, function(chr) { + * return chr.age < 40; + * }); + * // => { 'name': 'barney', 'age': 36, 'blocked': false } + * * // using "_.where" callback shorthand - * _.find(food, { 'type': 'vegetable' }); - * // => { 'name': 'beet', 'organic': false, 'type': 'vegetable' } + * _.find(characters, { 'age': 1 }); + * // => { 'name': 'pebbles', 'age': 1, 'blocked': false } * * // using "_.pluck" callback shorthand - * _.find(food, 'organic'); - * // => { 'name': 'banana', 'organic': true, 'type': 'fruit' } + * _.find(characters, 'blocked'); + * // => { 'name': 'fred', 'age': 40, 'blocked': true } */ function find(collection, callback, thisArg) { callback = lodash.createCallback(callback, thisArg, 3); @@ -3356,6 +3544,10 @@ * (value, index|key, collection). Callbacks may exit iteration early by * explicitly returning `false`. * + * Note: As with other "Collections" methods, objects with a `length` property + * are iterated like arrays. To avoid this behavior `_.forIn` or `_.forOwn` + * may be used for object iteration. + * * @static * @memberOf _ * @alias each @@ -3506,7 +3698,7 @@ * _.indexBy(keys, function(key) { return String.fromCharCode(key.code); }); * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } } * - * _.indexBy(stooges, function(key) { this.fromCharCode(key.code); }, String); + * _.indexBy(characters, function(key) { this.fromCharCode(key.code); }, String); * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } } */ var indexBy = createAggregator(function(result, value, key) { @@ -3536,7 +3728,7 @@ * // => [['1', '2', '3'], ['4', '5', '6']] */ function invoke(collection, methodName) { - var args = nativeSlice.call(arguments, 2), + var args = slice(arguments, 2), index = -1, isFunc = typeof methodName == 'function', length = collection ? collection.length : 0, @@ -3578,14 +3770,14 @@ * _.map({ 'one': 1, 'two': 2, 'three': 3 }, function(num) { return num * 3; }); * // => [3, 6, 9] (property order is not guaranteed across environments) * - * var stooges = [ - * { 'name': 'moe', 'age': 40 }, - * { 'name': 'larry', 'age': 50 } + * var characters = [ + * { 'name': 'barney', 'age': 36 }, + * { 'name': 'fred', 'age': 40 } * ]; * * // using "_.pluck" callback shorthand - * _.map(stooges, 'name'); - * // => ['moe', 'larry'] + * _.map(characters, 'name'); + * // => ['barney', 'fred'] */ function map(collection, callback, thisArg) { var index = -1, @@ -3633,23 +3825,28 @@ * _.max([4, 2, 8, 6]); * // => 8 * - * var stooges = [ - * { 'name': 'moe', 'age': 40 }, - * { 'name': 'larry', 'age': 50 } + * var characters = [ + * { 'name': 'barney', 'age': 36 }, + * { 'name': 'fred', 'age': 40 } * ]; * - * _.max(stooges, function(stooge) { return stooge.age; }); - * // => { 'name': 'larry', 'age': 50 }; + * _.max(characters, function(chr) { return chr.age; }); + * // => { 'name': 'fred', 'age': 40 }; * * // using "_.pluck" callback shorthand - * _.max(stooges, 'age'); - * // => { 'name': 'larry', 'age': 50 }; + * _.max(characters, 'age'); + * // => { 'name': 'fred', 'age': 40 }; */ function max(collection, callback, thisArg) { var computed = -Infinity, result = computed; - if (!callback && isArray(collection)) { + // allows working with functions like `_.map` without using + // their `index` argument as a callback + if (typeof callback != 'function' && thisArg && thisArg[callback] === collection) { + callback = null; + } + if (callback == null && isArray(collection)) { var index = -1, length = collection.length; @@ -3660,7 +3857,7 @@ } } } else { - callback = (!callback && isString(collection)) + callback = (callback == null && isString(collection)) ? charAtCallback : lodash.createCallback(callback, thisArg, 3); @@ -3703,23 +3900,28 @@ * _.min([4, 2, 8, 6]); * // => 2 * - * var stooges = [ - * { 'name': 'moe', 'age': 40 }, - * { 'name': 'larry', 'age': 50 } + * var characters = [ + * { 'name': 'barney', 'age': 36 }, + * { 'name': 'fred', 'age': 40 } * ]; * - * _.min(stooges, function(stooge) { return stooge.age; }); - * // => { 'name': 'moe', 'age': 40 }; + * _.min(characters, function(chr) { return chr.age; }); + * // => { 'name': 'barney', 'age': 36 }; * * // using "_.pluck" callback shorthand - * _.min(stooges, 'age'); - * // => { 'name': 'moe', 'age': 40 }; + * _.min(characters, 'age'); + * // => { 'name': 'barney', 'age': 36 }; */ function min(collection, callback, thisArg) { var computed = Infinity, result = computed; - if (!callback && isArray(collection)) { + // allows working with functions like `_.map` without using + // their `index` argument as a callback + if (typeof callback != 'function' && thisArg && thisArg[callback] === collection) { + callback = null; + } + if (callback == null && isArray(collection)) { var index = -1, length = collection.length; @@ -3730,7 +3932,7 @@ } } } else { - callback = (!callback && isString(collection)) + callback = (callback == null && isString(collection)) ? charAtCallback : lodash.createCallback(callback, thisArg, 3); @@ -3746,7 +3948,7 @@ } /** - * Retrieves the value of a specified property from all elements in the `collection`. + * Retrieves the value of a specified property from all elements in the collection. * * @static * @memberOf _ @@ -3757,13 +3959,13 @@ * @returns {Array} Returns a new array of property values. * @example * - * var stooges = [ - * { 'name': 'moe', 'age': 40 }, - * { 'name': 'larry', 'age': 50 } + * var characters = [ + * { 'name': 'barney', 'age': 36 }, + * { 'name': 'fred', 'age': 40 } * ]; * - * _.pluck(stooges, 'name'); - * // => ['moe', 'larry'] + * _.pluck(characters, 'name'); + * // => ['barney', 'fred'] */ var pluck = map; @@ -3799,7 +4001,7 @@ */ function reduce(collection, callback, accumulator, thisArg) { var noaccum = arguments.length < 3; - callback = baseCreateCallback(callback, thisArg, 4); + callback = lodash.createCallback(callback, thisArg, 4); if (isArray(collection)) { var index = -1, @@ -3842,7 +4044,7 @@ */ function reduceRight(collection, callback, accumulator, thisArg) { var noaccum = arguments.length < 3; - callback = baseCreateCallback(callback, thisArg, 4); + callback = lodash.createCallback(callback, thisArg, 4); forEachRight(collection, function(value, index, collection) { accumulator = noaccum ? (noaccum = false, value) @@ -3876,18 +4078,18 @@ * 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' } + * var characters = [ + * { 'name': 'barney', 'age': 36, 'blocked': false }, + * { 'name': 'fred', 'age': 40, 'blocked': true } * ]; * * // using "_.pluck" callback shorthand - * _.reject(food, 'organic'); - * // => [{ 'name': 'apple', 'organic': false, 'type': 'fruit' }] + * _.reject(characters, 'blocked'); + * // => [{ 'name': 'barney', 'age': 36, 'blocked': false }] * * // using "_.where" callback shorthand - * _.reject(food, { 'type': 'fruit' }); - * // => [{ 'name': 'carrot', 'organic': true, 'type': 'vegetable' }] + * _.reject(characters, { 'age': 36 }); + * // => [{ 'name': 'fred', 'age': 40, 'blocked': true }] */ function reject(collection, callback, thisArg) { callback = lodash.createCallback(callback, thisArg, 3); @@ -3904,8 +4106,8 @@ * @category Collections * @param {Array|Object|string} collection The collection to sample. * @param {number} [n] The number of elements to sample. - * @param- {Object} [guard] Allows working with functions, like `_.map`, - * without using their `key` and `object` arguments as sources. + * @param- {Object} [guard] Allows working with functions like `_.map` + * without using their `index` arguments as `n`. * @returns {Array} Returns the random sample(s) of `collection`. * @example * @@ -3916,14 +4118,13 @@ * // => [3, 1] */ function sample(collection, n, guard) { - var length = collection ? collection.length : 0; - if (typeof length != 'number') { + if (collection && typeof collection.length != 'number') { collection = values(collection); } else if (support.unindexedChars && isString(collection)) { collection = collection.split(''); } if (n == null || guard) { - return collection ? collection[random(length - 1)] : undefined; + return collection ? collection[baseRandom(0, collection.length - 1)] : undefined; } var result = shuffle(collection); result.length = nativeMin(nativeMax(0, n), result.length); @@ -3950,7 +4151,7 @@ result = Array(typeof length == 'number' ? length : 0); forEach(collection, function(value) { - var rand = random(++index); + var rand = baseRandom(0, ++index); result[index] = result[rand]; result[rand] = value; }); @@ -3974,7 +4175,7 @@ * _.size({ 'one': 1, 'two': 2, 'three': 3 }); * // => 3 * - * _.size('curly'); + * _.size('pebbles'); * // => 5 */ function size(collection) { @@ -4011,17 +4212,17 @@ * _.some([null, 0, 'yes', false], Boolean); * // => true * - * var food = [ - * { 'name': 'apple', 'organic': false, 'type': 'fruit' }, - * { 'name': 'carrot', 'organic': true, 'type': 'vegetable' } + * var characters = [ + * { 'name': 'barney', 'age': 36, 'blocked': false }, + * { 'name': 'fred', 'age': 40, 'blocked': true } * ]; * * // using "_.pluck" callback shorthand - * _.some(food, 'organic'); + * _.some(characters, 'blocked'); * // => true * * // using "_.where" callback shorthand - * _.some(food, { 'type': 'meat' }); + * _.some(characters, { 'age': 1 }); * // => false */ function some(collection, callback, thisArg) { @@ -4139,16 +4340,16 @@ * @returns {Array} Returns a new array of elements that have the given properties. * @example * - * var stooges = [ - * { 'name': 'curly', 'age': 30, 'quotes': ['Oh, a wise guy, eh?', 'Poifect!'] }, - * { 'name': 'moe', 'age': 40, 'quotes': ['Spread out!', 'You knucklehead!'] } + * var characters = [ + * { 'name': 'barney', 'age': 36, 'pets': ['hoppy'] }, + * { 'name': 'fred', 'age': 40, 'pets': ['baby puss', 'dino'] } * ]; * - * _.where(stooges, { 'age': 40 }); - * // => [{ 'name': 'moe', 'age': 40, 'quotes': ['Spread out!', 'You knucklehead!'] }] + * _.where(characters, { 'age': 36 }); + * // => [{ 'name': 'barney', 'age': 36, 'pets': ['hoppy'] }] * - * _.where(stooges, { 'quotes': ['Poifect!'] }); - * // => [{ 'name': 'curly', 'age': 30, 'quotes': ['Oh, a wise guy, eh?', 'Poifect!'] }] + * _.where(characters, { 'pets': ['dino'] }); + * // => [{ 'name': 'fred', 'age': 40, 'pets': ['baby puss', 'dino'] }] */ var where = filter; @@ -4190,7 +4391,7 @@ * @memberOf _ * @category Arrays * @param {Array} array The array to process. - * @param {...Array} [array] The arrays of values to exclude. + * @param {...Array} [values] The arrays of values to exclude. * @returns {Array} Returns a new array of filtered values. * @example * @@ -4198,39 +4399,20 @@ * // => [1, 3, 4] */ function difference(array) { - var index = -1, - indexOf = getIndexOf(), - length = array ? array.length : 0, - seen = baseFlatten(arguments, true, true, 1), - result = []; - - var isLarge = length >= largeArraySize && indexOf === baseIndexOf; - - if (isLarge) { - var cache = createCache(seen); - if (cache) { - indexOf = cacheIndexOf; - seen = cache; - } else { - isLarge = false; - } - } - while (++index < length) { - var value = array[index]; - if (indexOf(seen, value) < 0) { - result.push(value); - } - } - if (isLarge) { - releaseObject(seen); - } - return result; + return baseDifference(array, baseFlatten(arguments, true, true, 1)); } /** * This method is like `_.find` except that it returns the index of the first * element that passes the callback check, instead of the element itself. * + * If a property name is provided for `callback` the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is provided 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 @@ -4242,9 +4424,23 @@ * @returns {number} Returns the index of the found element, else `-1`. * @example * - * _.findIndex(['apple', 'banana', 'beet'], function(food) { - * return /^b/.test(food); + * var characters = [ + * { 'name': 'barney', 'age': 36, 'blocked': false }, + * { 'name': 'fred', 'age': 40, 'blocked': true }, + * { 'name': 'pebbles', 'age': 1, 'blocked': false } + * ]; + * + * _.findIndex(characters, function(chr) { + * return chr.age < 20; * }); + * // => 2 + * + * // using "_.where" callback shorthand + * _.findIndex(characters, { 'age': 36 }); + * // => 0 + * + * // using "_.pluck" callback shorthand + * _.findIndex(characters, 'blocked'); * // => 1 */ function findIndex(array, callback, thisArg) { @@ -4264,6 +4460,13 @@ * This method is like `_.findIndex` except that it iterates over elements * of a `collection` from right to left. * + * If a property name is provided for `callback` the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is provided 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 @@ -4275,9 +4478,23 @@ * @returns {number} Returns the index of the found element, else `-1`. * @example * - * _.findLastIndex(['apple', 'banana', 'beet'], function(food) { - * return /^b/.test(food); + * var characters = [ + * { 'name': 'barney', 'age': 36, 'blocked': true }, + * { 'name': 'fred', 'age': 40, 'blocked': false }, + * { 'name': 'pebbles', 'age': 1, 'blocked': true } + * ]; + * + * _.findLastIndex(characters, function(chr) { + * return chr.age > 30; * }); + * // => 1 + * + * // using "_.where" callback shorthand + * _.findLastIndex(characters, { 'age': 36 }); + * // => 0 + * + * // using "_.pluck" callback shorthand + * _.findLastIndex(characters, 'blocked'); * // => 2 */ function findLastIndex(array, callback, thisArg) { @@ -4328,24 +4545,19 @@ * }); * // => [1, 2] * - * var food = [ - * { 'name': 'banana', 'organic': true }, - * { 'name': 'beet', 'organic': false }, + * var characters = [ + * { 'name': 'barney', 'blocked': true, 'employer': 'slate' }, + * { 'name': 'fred', 'blocked': false, 'employer': 'slate' }, + * { 'name': 'pebbles', 'blocked': true, 'employer': 'na' } * ]; * * // 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' } - * ]; + * _.first(characters, 'blocked'); + * // => [{ 'name': 'barney', 'blocked': true, 'employer': 'slate' }] * * // using "_.where" callback shorthand - * _.first(food, { 'type': 'fruit' }); - * // => [{ 'name': 'apple', 'type': 'fruit' }, { 'name': 'banana', 'type': 'fruit' }] + * _.pluck(_.first(characters, { 'employer': 'slate' }), 'name'); + * // => ['barney', 'fred'] */ function first(array, callback, thisArg) { var n = 0, @@ -4398,20 +4610,20 @@ * _.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!'] } + * var characters = [ + * { 'name': 'barney', 'age': 30, 'pets': ['hoppy'] }, + * { 'name': 'fred', 'age': 40, 'pets': ['baby puss', 'dino'] } * ]; * * // using "_.pluck" callback shorthand - * _.flatten(stooges, 'quotes'); - * // => ['Oh, a wise guy, eh?', 'Poifect!', 'Spread out!', 'You knucklehead!'] + * _.flatten(characters, 'pets'); + * // => ['hoppy', 'baby puss', 'dino'] */ function flatten(array, isShallow, callback, thisArg) { // juggle arguments if (typeof isShallow != 'boolean' && isShallow != null) { thisArg = callback; - callback = !(thisArg && thisArg[isShallow] === array) ? isShallow : null; + callback = (typeof isShallow != 'function' && thisArg && thisArg[isShallow] === array) ? null : isShallow; isShallow = false; } if (callback != null) { @@ -4491,24 +4703,19 @@ * }); * // => [1] * - * var food = [ - * { 'name': 'beet', 'organic': false }, - * { 'name': 'carrot', 'organic': true } + * var characters = [ + * { 'name': 'barney', 'blocked': false, 'employer': 'slate' }, + * { 'name': 'fred', 'blocked': true, 'employer': 'slate' }, + * { 'name': 'pebbles', 'blocked': true, 'employer': 'na' } * ]; * * // 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' } - * ]; + * _.initial(characters, 'blocked'); + * // => [{ 'name': 'barney', 'blocked': false, 'employer': 'slate' }] * * // using "_.where" callback shorthand - * _.initial(food, { 'type': 'vegetable' }); - * // => [{ 'name': 'banana', 'type': 'fruit' }] + * _.pluck(_.initial(characters, { 'employer': 'na' }), 'name'); + * // => ['barney', 'fred'] */ function initial(array, callback, thisArg) { var n = 0, @@ -4621,24 +4828,19 @@ * }); * // => [2, 3] * - * var food = [ - * { 'name': 'beet', 'organic': false }, - * { 'name': 'carrot', 'organic': true } + * var characters = [ + * { 'name': 'barney', 'blocked': false, 'employer': 'slate' }, + * { 'name': 'fred', 'blocked': true, 'employer': 'slate' }, + * { 'name': 'pebbles', 'blocked': true, 'employer': 'na' } * ]; * * // 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' } - * ]; + * _.pluck(_.last(characters, 'blocked'), 'name'); + * // => ['fred', 'pebbles'] * * // using "_.where" callback shorthand - * _.last(food, { 'type': 'vegetable' }); - * // => [{ 'name': 'beet', 'type': 'vegetable' }, { 'name': 'carrot', 'type': 'vegetable' }] + * _.last(characters, { 'employer': 'na' }); + * // => [{ 'name': 'pebbles', 'blocked': true, 'employer': 'na' }] */ function last(array, callback, thisArg) { var n = 0, @@ -4664,6 +4866,13 @@ * equality for comparisons, i.e. `===`. If `fromIndex` is negative, it is used * as the offset from the end of the collection. * + * If a property name is provided for `callback` the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is provided 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 @@ -4742,17 +4951,17 @@ * @returns {Array} Returns a new range array. * @example * - * _.range(10); - * // => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + * _.range(4); + * // => [0, 1, 2, 3] * - * _.range(1, 11); - * // => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + * _.range(1, 5); + * // => [1, 2, 3, 4] * - * _.range(0, 30, 5); - * // => [0, 5, 10, 15, 20, 25] + * _.range(0, 20, 5); + * // => [0, 5, 10, 15] * - * _.range(0, -10, -1); - * // => [0, -1, -2, -3, -4, -5, -6, -7, -8, -9] + * _.range(0, -4, -1); + * // => [0, -1, -2, -3] * * _.range(1, 4, 0); * // => [1, 1, 1] @@ -4768,7 +4977,7 @@ end = start; start = 0; } - // use `Array(length)` so engines, like Chakra and V8, avoid slower modes + // use `Array(length)` so engines like Chakra and V8 avoid slower modes // http://youtu.be/XAqIpGU8ZZk#t=17m25s var index = -1, length = nativeMax(0, ceil((end - start) / (step || 1))), @@ -4868,24 +5077,19 @@ * }); * // => [3] * - * var food = [ - * { 'name': 'banana', 'organic': true }, - * { 'name': 'beet', 'organic': false }, + * var characters = [ + * { 'name': 'barney', 'blocked': true, 'employer': 'slate' }, + * { 'name': 'fred', 'blocked': false, 'employer': 'slate' }, + * { 'name': 'pebbles', 'blocked': true, 'employer': 'na' } * ]; * * // 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' } - * ]; + * _.pluck(_.rest(characters, 'blocked'), 'name'); + * // => ['fred', 'pebbles'] * * // using "_.where" callback shorthand - * _.rest(food, { 'type': 'fruit' }); - * // => [{ 'name': 'beet', 'type': 'vegetable' }] + * _.rest(characters, { 'employer': 'slate' }); + * // => [{ 'name': 'pebbles', 'blocked': true, 'employer': 'na' }] */ function rest(array, callback, thisArg) { if (typeof callback != 'number' && callback != null) { @@ -5034,7 +5238,7 @@ // juggle arguments if (typeof isSorted != 'boolean' && isSorted != null) { thisArg = callback; - callback = !(thisArg && thisArg[isSorted] === array) ? isSorted : null; + callback = (typeof isSorted != 'function' && thisArg && thisArg[isSorted] === array) ? null : isSorted; isSorted = false; } if (callback != null) { @@ -5059,7 +5263,7 @@ * // => [2, 3, 4] */ function without(array) { - return difference(array, nativeSlice.call(arguments, 1)); + return baseDifference(array, slice(arguments, 1)); } /** @@ -5075,8 +5279,8 @@ * @returns {Array} Returns a new array of grouped elements. * @example * - * _.zip(['moe', 'larry'], [30, 40], [true, false]); - * // => [['moe', 30, true], ['larry', 40, false]] + * _.zip(['fred', 'barney'], [30, 40], [true, false]); + * // => [['fred', 30, true], ['barney', 40, false]] */ function zip() { var array = arguments.length > 1 ? arguments : arguments[0], @@ -5105,8 +5309,8 @@ * corresponding values. * @example * - * _.zipObject(['moe', 'larry'], [30, 40]); - * // => { 'moe': 30, 'larry': 40 } + * _.zipObject(['fred', 'barney'], [30, 40]); + * // => { 'fred': 30, 'barney': 40 } */ function zipObject(keys, values) { var index = -1, @@ -5179,14 +5383,14 @@ * return greeting + ' ' + this.name; * }; * - * func = _.bind(func, { 'name': 'moe' }, 'hi'); + * func = _.bind(func, { 'name': 'fred' }, 'hi'); * func(); - * // => 'hi moe' + * // => 'hi fred' */ function bind(func, thisArg) { return arguments.length > 2 - ? createBound(func, 17, nativeSlice.call(arguments, 2), null, thisArg) - : createBound(func, 1, null, null, thisArg); + ? createWrapper(func, 17, slice(arguments, 2), null, thisArg) + : createWrapper(func, 1, null, null, thisArg); } /** @@ -5220,7 +5424,7 @@ while (++index < length) { var key = funcs[index]; - object[key] = createBound(object[key], 1, null, null, object); + object[key] = createWrapper(object[key], 1, null, null, object); } return object; } @@ -5242,7 +5446,7 @@ * @example * * var object = { - * 'name': 'moe', + * 'name': 'fred', * 'greet': function(greeting) { * return greeting + ' ' + this.name; * } @@ -5250,19 +5454,19 @@ * * var func = _.bindKey(object, 'greet', 'hi'); * func(); - * // => 'hi moe' + * // => 'hi fred' * * object.greet = function(greeting) { - * return greeting + ', ' + this.name + '!'; + * return greeting + 'ya ' + this.name + '!'; * }; * * func(); - * // => 'hi, moe!' + * // => 'hiya fred!' */ function bindKey(object, key) { return arguments.length > 2 - ? createBound(key, 19, nativeSlice.call(arguments, 2), null, object) - : createBound(key, 3, null, null, object); + ? createWrapper(key, 19, slice(arguments, 2), null, object) + : createWrapper(key, 3, null, null, object); } /** @@ -5279,7 +5483,7 @@ * @example * * var realNameMap = { - * 'curly': 'jerome' + * 'pebbles': 'penelope' * }; * * var format = function(name) { @@ -5292,8 +5496,8 @@ * }; * * var welcome = _.compose(greet, format); - * welcome('curly'); - * // => 'Hiya Jerome!' + * welcome('pebbles'); + * // => 'Hiya Penelope!' */ function compose() { var funcs = arguments, @@ -5330,9 +5534,9 @@ * @returns {Function} Returns a callback function. * @example * - * var stooges = [ - * { 'name': 'moe', 'age': 40 }, - * { 'name': 'larry', 'age': 50 } + * var characters = [ + * { 'name': 'barney', 'age': 36 }, + * { 'name': 'fred', 'age': 40 } * ]; * * // wrap to create custom callback shorthands @@ -5343,8 +5547,8 @@ * }; * }); * - * _.filter(stooges, 'age__gt45'); - * // => [{ 'name': 'larry', 'age': 50 }] + * _.filter(characters, 'age__gt38'); + * // => [{ 'name': 'fred', 'age': 40 }] */ function createCallback(func, thisArg, argCount) { var type = typeof func; @@ -5413,7 +5617,7 @@ */ function curry(func, arity) { arity = typeof arity == 'number' ? arity : (+arity || func.length); - return createBound(func, 4, null, null, null, arity); + return createWrapper(func, 4, null, null, null, arity); } /** @@ -5490,6 +5694,9 @@ if (isCalled) { lastCalled = now(); result = func.apply(thisArg, args); + if (!timeoutId && !maxTimeoutId) { + args = thisArg = null; + } } } else { timeoutId = setTimeout(delayed, remaining); @@ -5504,6 +5711,9 @@ if (trailing || (maxWait !== wait)) { lastCalled = now(); result = func.apply(thisArg, args); + if (!timeoutId && !maxTimeoutId) { + args = thisArg = null; + } } }; @@ -5519,8 +5729,10 @@ if (!maxTimeoutId && !leading) { lastCalled = stamp; } - var remaining = maxWait - (stamp - lastCalled); - if (remaining <= 0) { + var remaining = maxWait - (stamp - lastCalled), + isCalled = remaining <= 0; + + if (isCalled) { if (maxTimeoutId) { maxTimeoutId = clearTimeout(maxTimeoutId); } @@ -5531,12 +5743,19 @@ maxTimeoutId = setTimeout(maxDelayed, remaining); } } - if (!timeoutId && wait !== maxWait) { + if (isCalled && timeoutId) { + timeoutId = clearTimeout(timeoutId); + } + else if (!timeoutId && wait !== maxWait) { timeoutId = setTimeout(delayed, wait); } if (leadingCall) { + isCalled = true; result = func.apply(thisArg, args); } + if (isCalled && !timeoutId && !maxTimeoutId) { + args = thisArg = null; + } return result; }; } @@ -5560,11 +5779,11 @@ if (!isFunction(func)) { throw new TypeError; } - var args = nativeSlice.call(arguments, 1); + var args = slice(arguments, 1); return setTimeout(function() { func.apply(undefined, args); }, 1); } // use `setImmediate` if available in Node.js - if (isV8 && moduleExports && typeof setImmediate == 'function') { + if (setImmediate) { defer = function(func) { if (!isFunction(func)) { throw new TypeError; @@ -5594,7 +5813,7 @@ if (!isFunction(func)) { throw new TypeError; } - var args = nativeSlice.call(arguments, 2); + var args = slice(arguments, 2); return setTimeout(function() { func.apply(undefined, args); }, wait); } @@ -5618,19 +5837,22 @@ * return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2); * }); * + * fibonacci(9) + * // => 34 + * * var data = { - * 'moe': { 'name': 'moe', 'age': 40 }, - * 'curly': { 'name': 'curly', 'age': 60 } + * 'fred': { 'name': 'fred', 'age': 40 }, + * 'pebbles': { 'name': 'pebbles', 'age': 1 } * }; * * // modifying the result cache - * var stooge = _.memoize(function(name) { return data[name]; }, _.identity); - * stooge('curly'); - * // => { 'name': 'curly', 'age': 60 } + * var get = _.memoize(function(name) { return data[name]; }, _.identity); + * get('pebbles'); + * // => { 'name': 'pebbles', 'age': 1 } * - * stooge.cache.curly.name = 'jerome'; - * stooge('curly'); - * // => { 'name': 'jerome', 'age': 60 } + * get.cache.pebbles.name = 'penelope'; + * get('pebbles'); + * // => { 'name': 'penelope', 'age': 1 } */ function memoize(func, resolver) { if (!isFunction(func)) { @@ -5700,11 +5922,11 @@ * * var greet = function(greeting, name) { return greeting + ' ' + name; }; * var hi = _.partial(greet, 'hi'); - * hi('moe'); - * // => 'hi moe' + * hi('fred'); + * // => 'hi fred' */ function partial(func) { - return createBound(func, 16, nativeSlice.call(arguments, 1)); + return createWrapper(func, 16, slice(arguments, 1)); } /** @@ -5735,7 +5957,7 @@ * // => { '_': _, 'jq': $ } */ function partialRight(func) { - return createBound(func, 32, null, nativeSlice.call(arguments, 1)); + return createWrapper(func, 32, null, slice(arguments, 1)); } /** @@ -5786,8 +6008,7 @@ debounceOptions.maxWait = wait; debounceOptions.trailing = trailing; - var result = debounce(func, wait, debounceOptions); - return result; + return debounce(func, wait, debounceOptions); } /** @@ -5804,22 +6025,15 @@ * @returns {Function} Returns the new function. * @example * - * var hello = function(name) { return 'hello ' + name; }; - * hello = _.wrap(hello, function(func) { - * return 'before, ' + func('moe') + ', after'; + * var p = _.wrap(_.escape, function(func, text) { + * return '

' + func(text) + '

'; * }); - * hello(); - * // => 'before, hello moe, after' + * + * p('Fred, Wilma, & Pebbles'); + * // => '

Fred, Wilma, & Pebbles

' */ function wrap(value, wrapper) { - if (!isFunction(wrapper)) { - throw new TypeError; - } - return function() { - var args = [value]; - push.apply(args, arguments); - return wrapper.apply(this, args); - }; + return createWrapper(wrapper, 16, [value]); } /*--------------------------------------------------------------------------*/ @@ -5835,8 +6049,8 @@ * @returns {string} Returns the escaped string. * @example * - * _.escape('Moe, Larry & Curly'); - * // => 'Moe, Larry & Curly' + * _.escape('Fred, Wilma, & Pebbles'); + * // => 'Fred, Wilma, & Pebbles' */ function escape(string) { return string == null ? '' : String(string).replace(reUnescapedHtml, escapeHtmlChar); @@ -5852,8 +6066,8 @@ * @returns {*} Returns `value`. * @example * - * var moe = { 'name': 'moe' }; - * moe === _.identity(moe); + * var object = { 'name': 'fred' }; + * _.identity(object) === object; * // => true */ function identity(value) { @@ -5877,11 +6091,11 @@ * } * }); * - * _.capitalize('moe'); - * // => 'Moe' + * _.capitalize('fred'); + * // => 'Fred' * - * _('moe').capitalize(); - * // => 'Moe' + * _('fred').capitalize(); + * // => 'Fred' */ function mixin(object, source) { var ctor = object, @@ -5929,6 +6143,22 @@ return this; } + /** + * A no-operation function. + * + * @static + * @memberOf _ + * @category Utilities + * @example + * + * var object = { 'name': 'fred' }; + * _.noop(object) === undefined; + * // => true + */ + function noop() { + // no operation performed + } + /** * Converts the given value into an integer of the specified radix. * If `radix` is `undefined` or `0` a `radix` of `10` is used unless the @@ -5949,7 +6179,7 @@ * // => 8 */ var parseInt = nativeParseInt(whitespace + '08') == 8 ? nativeParseInt : function(value, radix) { - // Firefox and Opera still follow the ES3 specified implementation of `parseInt` + // Firefox < 21 and Opera < 15 follow the ES3 specified implementation of `parseInt` return nativeParseInt(isString(value) ? value.replace(reLeadingSpacesAndZeros, '') : value, radix || 0); }; @@ -6004,10 +6234,11 @@ } else { max = +max || 0; } - var rand = nativeRandom(); - return (floating || min % 1 || max % 1) - ? nativeMin(min + (rand * (max - min + parseFloat('1e-' + ((rand +'').length - 1)))), max) - : min + floor(rand * (max - min + 1)); + if (floating || min % 1 || max % 1) { + var rand = nativeRandom(); + return nativeMin(min + (rand * (max - min + parseFloat('1e-' + ((rand +'').length - 1)))), max); + } + return baseRandom(min, max); } /** @@ -6052,7 +6283,7 @@ * debugging. See http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl * * For more information on precompiling templates see: - * http://lodash.com/#custom-builds + * http://lodash.com/custom-builds * * For more information on Chrome extension sandboxes see: * http://developer.chrome.com/stable/extensions/sandboxingEval.html @@ -6075,8 +6306,8 @@ * * // using the "interpolate" delimiter to create a compiled template * var compiled = _.template('hello <%= name %>'); - * compiled({ 'name': 'moe' }); - * // => 'hello moe' + * compiled({ 'name': 'fred' }); + * // => 'hello fred' * * // 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 1479af3cce..34d84eda2d 100644 --- a/perf/perf.js +++ b/perf/perf.js @@ -1,16 +1,16 @@ -(function(window) { +;(function(root) { /** Use a single "load" function */ - var load = typeof require == 'function' ? require : window.load; + var load = typeof require == 'function' ? require : root.load; /** The file path of the Lo-Dash file to test */ var filePath = (function() { var min = 0; - var result = window.phantom + var result = root.phantom ? phantom.args - : (window.system + : (root.system ? (min = 1, system.args) - : (window.process ? (min = 2, process.argv) : (window.arguments || [])) + : (root.process ? (min = 2, process.argv) : (root.arguments || [])) ); var last = result[result.length - 1]; @@ -26,22 +26,22 @@ }()); /** Load Lo-Dash */ - var lodash = window.lodash || (window.lodash = ( - lodash = load(filePath) || window._, + var lodash = root.lodash || (root.lodash = ( + lodash = load(filePath) || root._, lodash = lodash._ || lodash, lodash.noConflict() )); /** Load Benchmark.js */ - var Benchmark = window.Benchmark || (window.Benchmark = ( - Benchmark = load('../vendor/benchmark.js/benchmark.js') || window.Benchmark, + var Benchmark = root.Benchmark || (root.Benchmark = ( + Benchmark = load('../vendor/benchmark.js/benchmark.js') || root.Benchmark, Benchmark = Benchmark.Benchmark || Benchmark, - Benchmark.runInContext(lodash.extend({}, window, { '_': lodash })) + Benchmark.runInContext(lodash.extend({}, root, { '_': lodash })) )); /** Load Underscore */ - var _ = window._ || (window._ = ( - _ = load('../vendor/underscore/underscore.js') || window._, + var _ = root._ || (root._ = ( + _ = load('../vendor/underscore/underscore.js') || root._, _._ || _ )); @@ -67,28 +67,28 @@ var toString = Object.prototype.toString; /** The `ui` object */ - var ui = window.ui || (window.ui = { + var ui = root.ui || (root.ui = { 'buildPath': basename(filePath, '.js'), 'otherPath': 'underscore' }); /** The Lo-Dash build basename */ - var buildName = window.buildName = basename(ui.buildPath, '.js'); + var buildName = root.buildName = basename(ui.buildPath, '.js'); /** The other library basename */ - var otherName = window.otherName = (function() { + var otherName = root.otherName = (function() { var result = basename(ui.otherPath, '.js'); return result + (result == buildName ? ' (2)' : ''); }()); /** Detect if in a browser environment */ - var isBrowser = isHostType(window, 'document') && isHostType(window, 'navigator'); + var isBrowser = isHostType(root, 'document') && isHostType(root, 'navigator'); /** Detect Java environment */ - var isJava = !isBrowser && /Java/.test(toString.call(window.java)); + var isJava = !isBrowser && /Java/.test(toString.call(root.java)); /** Add `console.log()` support for Narwhal, Rhino, and RingoJS */ - var console = window.console || (window.console = { 'log': window.print }); + var console = root.console || (root.console = { 'log': root.print }); /*--------------------------------------------------------------------------*/ @@ -173,7 +173,7 @@ * @private (@public in the browser) */ function run() { - fbPanel = (fbPanel = window.document && document.getElementById('FirebugUI')) && + fbPanel = (fbPanel = root.document && document.getElementById('FirebugUI')) && (fbPanel = (fbPanel = fbPanel.contentWindow || fbPanel.contentDocument).document || fbPanel) && fbPanel.getElementById('fbPanel1'); @@ -257,8 +257,8 @@ lodash.extend(Benchmark.options, { 'async': true, 'setup': '\ - var _ = window._,\ - lodash = window.lodash,\ + var _ = global._,\ + lodash = global.lodash,\ belt = this.name == buildName ? lodash : _;\ \ var index,\ @@ -280,11 +280,10 @@ }\ \ if (typeof bind != "undefined") {\ - var thisArg = { "name": "moe" },\ - ctor = function() {};\ + var thisArg = { "name": "fred" };\ \ var func = function(greeting, punctuation) {\ - return greeting + ", " + this.name + (punctuation || ".");\ + return greeting + " " + this.name + (punctuation || ".");\ };\ \ var _boundNormal = _.bind(func, thisArg),\ @@ -296,8 +295,8 @@ lodashBoundPartial = lodash.bind(func, thisArg, "hi");\ \ for (index = 0; index < 10; index++) {\ - _boundMultiple = _.bind(_boundMultiple, { "name": "moe" + index });\ - lodashBoundMultiple = lodash.bind(lodashBoundMultiple, { "name": "moe" + index });\ + _boundMultiple = _.bind(_boundMultiple, { "name": "fred" + index });\ + lodashBoundMultiple = lodash.bind(lodashBoundMultiple, { "name": "fred" + index });\ }\ }\ \ @@ -483,6 +482,15 @@ }\ }\ \ + if (typeof partial != "undefined") {\ + var func = function(greeting, punctuation) {\ + return greeting + " fred" + (punctuation || ".");\ + };\ + \ + var _partial = _.partial(func, "hi"),\ + lodashPartial = lodash.partial(func, "hi");\ + }\ + \ if (typeof template != "undefined") {\ var tplData = {\ "header1": "Header1",\ @@ -537,6 +545,18 @@ lodashFindWhere = lodash.findWhere || lodash.find,\ whereObject = { "num": 9 };\ }\ + if (typeof wrap != "undefined") {\ + var add = function(a, b) {\ + return a + b;\ + };\ + \ + var average = function(func, a, b) {\ + return (func(a, b) / 2).toFixed(2);\ + };\ + \ + var _wrapped = _.wrap(add, average);\ + lodashWrapped = lodash.wrap(add, average);\ + }\ if (typeof zip != "undefined") {\ var unzipped = [["a", "b", "c"], [1, 2, 3], [true, false, true]];\ }' @@ -592,25 +612,13 @@ /*--------------------------------------------------------------------------*/ suites.push( - Benchmark.Suite('`_.bind` (uses native `Function#bind` if available and inferred fast)') + Benchmark.Suite('`_.bind`') .add(buildName, { - 'fn': 'lodash.bind(func, { "name": "moe" })', + 'fn': 'lodash.bind(func, { "name": "fred" })', 'teardown': 'function bind(){}' }) .add(otherName, { - 'fn': '_.bind(func, { "name": "moe" })', - 'teardown': 'function bind(){}' - }) - ); - - suites.push( - Benchmark.Suite('bound call') - .add(buildName, { - 'fn': 'lodashBoundNormal()', - 'teardown': 'function bind(){}' - }) - .add(otherName, { - 'fn': '_boundNormal()', + 'fn': '_.bind(func, { "name": "fred" })', 'teardown': 'function bind(){}' }) ); @@ -628,19 +636,7 @@ ); suites.push( - Benchmark.Suite('bound and partially applied call (uses native `Function#bind` if available)') - .add(buildName, { - 'fn': 'lodashBoundPartial()', - 'teardown': 'function bind(){}' - }) - .add(otherName, { - 'fn': '_boundPartial()', - 'teardown': 'function bind(){}' - }) - ); - - suites.push( - Benchmark.Suite('bound and partially applied call with arguments (uses native `Function#bind` if available)') + Benchmark.Suite('bound and partially applied call with arguments') .add(buildName, { 'fn': 'lodashBoundPartial("!")', 'teardown': 'function bind(){}' @@ -1466,6 +1462,32 @@ /*--------------------------------------------------------------------------*/ + suites.push( + Benchmark.Suite('`_.partial`') + .add(buildName, { + 'fn': 'lodash.partial(func, "hi")', + 'teardown': 'function partial(){}' + }) + .add(otherName, { + 'fn': '_.partial(func, "hi")', + 'teardown': 'function partial(){}' + }) + ); + + suites.push( + Benchmark.Suite('partially applied call with arguments') + .add(buildName, { + 'fn': 'lodashPartial("!")', + 'teardown': 'function partial(){}' + }) + .add(otherName, { + 'fn': '_partial("!")', + 'teardown': 'function partial(){}' + }) + ); + + /*--------------------------------------------------------------------------*/ + suites.push( Benchmark.Suite('`_.pick`') .add(buildName, '\ @@ -1946,6 +1968,20 @@ /*--------------------------------------------------------------------------*/ + suites.push( + Benchmark.Suite('`_.wrap` result called') + .add(buildName, { + 'fn': 'lodashWrapped(2, 5)', + 'teardown': 'function wrap(){}' + }) + .add(otherName, { + 'fn': '_wrapped(2, 5)', + 'teardown': 'function wrap(){}' + }) + ); + + /*--------------------------------------------------------------------------*/ + suites.push( Benchmark.Suite('`_.zip`') .add(buildName, { @@ -1964,8 +2000,8 @@ log(Benchmark.platform); } // in the browser, expose `run` to be called later - if (window.document && !window.phantom) { - window.run = run; + if (root.document && !root.phantom) { + root.run = run; } else { run(); } diff --git a/test/asset/test-ui.js b/test/asset/test-ui.js index 29dd1f9c3f..ab5b03851d 100644 --- a/test/asset/test-ui.js +++ b/test/asset/test-ui.js @@ -34,47 +34,6 @@ /*--------------------------------------------------------------------------*/ - // expose `ui.urlParams` properties - ui.urlParams = { - 'build': build, - 'loader': loader - }; - - // expose Lo-Dash build file path - ui.buildPath = (function() { - var result; - switch (build) { - case 'lodash-compat': result = 'dist/lodash.compat.min.js'; break; - case 'lodash-modern-dev': result = 'dist/lodash.js'; break; - case 'lodash-modern': result = 'dist/lodash.min.js'; break; - case 'lodash-legacy': result = 'dist/lodash.legacy.min.js'; break; - case 'lodash-mobile': result = 'dist/lodash.mobile.min.js'; break; - case 'lodash-underscore': result = 'dist/lodash.underscore.min.js'; break; - case 'lodash-custom-dev': result = 'lodash.custom.js'; break; - case 'lodash-custom': result = 'lodash.custom.min.js'; break; - case 'lodash-compat-dev': - case null: result = 'lodash.js'; break; - default: return build; - } - return basePath + result; - }()); - - // expose module loader file path - ui.loaderPath = (function() { - var result; - switch (loader) { - case 'curl': result = 'vendor/curl/dist/curl-kitchen-sink/curl.js'; break; - case 'dojo': result = 'vendor/dojo/dojo.js'; break; - case 'requirejs': - case null: result = 'vendor/requirejs/require.js'; break; - default: return loader; - } - return basePath + result; - }()); - - // used to indicate testing a modularized build - ui.isModularize = /\b(?:commonjs|(index|main)\.js|lodash-(?:amd|node)|modularize|npm)\b/.test([location.pathname, location.search, ui.buildPath]); - // initialize controls addListener(window, 'load', function() { function eventHandler(event) { @@ -118,11 +77,11 @@ loaderList.selectedIndex = (function() { switch (loader) { - case 'none': return 0 case 'curl': return 1; case 'dojo': return 2; - case 'requirejs': - case null: return 3; + case 'requirejs': return 3; + case 'none': + case null: return 0; } return -1; }()); @@ -168,6 +127,47 @@ init(); }); + // expose Lo-Dash build file path + ui.buildPath = (function() { + var result; + switch (build) { + case 'lodash-compat': result = 'dist/lodash.compat.min.js'; break; + case 'lodash-modern-dev': result = 'dist/lodash.js'; break; + case 'lodash-modern': result = 'dist/lodash.min.js'; break; + case 'lodash-legacy': result = 'dist/lodash.legacy.min.js'; break; + case 'lodash-mobile': result = 'dist/lodash.mobile.min.js'; break; + case 'lodash-underscore': result = 'dist/lodash.underscore.min.js'; break; + case 'lodash-custom-dev': result = 'lodash.custom.js'; break; + case 'lodash-custom': result = 'lodash.custom.min.js'; break; + case null: build = 'lodash-compat-dev'; + case 'lodash-compat-dev': result = 'lodash.js'; break; + default: return build; + } + return basePath + result; + }()); + + // expose module loader file path + ui.loaderPath = (function() { + var result; + switch (loader) { + case 'curl': result = 'vendor/curl/dist/curl-kitchen-sink/curl.js'; break; + case 'dojo': result = 'vendor/dojo/dojo.js'; break; + case 'requirejs': result = 'vendor/requirejs/require.js'; break; + case null: loader = 'none'; return ''; + default: return loader; + } + return basePath + result; + }()); + + // expose `ui.urlParams` properties + ui.urlParams = { + 'build': build, + 'loader': loader + }; + + // used to indicate testing a modularized build + ui.isModularize = /\b(?:commonjs|(index|main)\.js|lodash-(?:amd|node)|modularize|npm)\b/.test([location.pathname, location.search, ui.buildPath]); + // expose `ui` window.ui = ui; diff --git a/test/backbone.html b/test/backbone.html index a4962d73cb..8b9fa6be66 100644 --- a/test/backbone.html +++ b/test/backbone.html @@ -26,6 +26,72 @@

Test