From 65227f601ee548a7926d82b389bcd0386c4a07d7 Mon Sep 17 00:00:00 2001 From: John-David Dalton Date: Sun, 21 Jul 2013 12:32:02 -0700 Subject: [PATCH] Rename `basicXYZ` functions to `baseXYZ`, extract `_.clone`, `_.merge`, `_.isEqual` functions into their own `baseXYZ` functions, and rework `createBound` to flatten multiple calls. Former-commit-id: 8efa6004d747103e9ec6507755fa6ffceb01b16f --- build.js | 371 +++++++++--------- build/pre-compile.js | 15 +- lodash.js | 882 ++++++++++++++++++++++--------------------- test/test.js | 1 + 4 files changed, 646 insertions(+), 623 deletions(-) diff --git a/build.js b/build.js index 78a0dd427..bfc4a95e0 100644 --- a/build.js +++ b/build.js @@ -92,38 +92,38 @@ // public functions 'after': [], 'assign': ['createCallback', 'createIterator'], - 'at': ['basicFlatten', 'isString'], + 'at': ['baseFlatten', 'isString'], 'bind': ['createBound'], - 'bindAll': ['basicFlatten', 'bind', 'functions'], + 'bindAll': ['baseFlatten', 'bind', 'functions'], 'bindKey': ['createBound'], - 'clone': ['assign', 'createCallback', 'forEach', 'forOwn', 'getArray', 'isArray', 'isObject', 'isNode', 'releaseArray', 'slice'], + 'clone': ['baseClone', 'createCallback'], 'cloneDeep': ['clone'], 'compact': [], 'compose': [], - 'contains': ['basicEach', 'getIndexOf', 'isString'], + 'contains': ['baseEach', 'getIndexOf', 'isString'], 'countBy': ['createCallback', 'forEach'], - 'createCallback': ['identity', 'isEqual', 'isObject', 'keys'], + 'createCallback': ['hasThis', 'identity', 'isEqual', 'isObject', 'keys'], 'debounce': ['isObject'], 'defaults': ['createCallback', 'createIterator'], 'defer': ['bind'], 'delay': [], - 'difference': ['basicFlatten', 'cacheIndexOf', 'createCache', 'getIndexOf', 'releaseObject'], + 'difference': ['baseFlatten', 'cacheIndexOf', 'createCache', 'getIndexOf', 'releaseObject'], 'escape': ['escapeHtmlChar', 'keys'], - 'every': ['basicEach', 'createCallback', 'isArray'], - 'filter': ['basicEach', 'createCallback', 'isArray'], - 'find': ['basicEach', 'createCallback', 'isArray'], + 'every': ['baseEach', 'createCallback', 'isArray'], + 'filter': ['baseEach', 'createCallback', 'isArray'], + 'find': ['baseEach', 'createCallback', 'isArray'], 'findIndex': ['createCallback'], 'findKey': ['createCallback', 'forOwn'], 'first': ['createCallback', 'slice'], - 'flatten': ['basicFlatten', 'map'], - 'forEach': ['basicEach', 'createCallback', 'isArray'], + 'flatten': ['baseFlatten', 'map'], + 'forEach': ['baseEach', 'createCallback', 'isArray'], 'forIn': ['createIterator'], 'forOwn': ['createIterator'], 'functions': ['forIn', 'isFunction'], 'groupBy': ['createCallback', 'forEach'], 'has': [], 'identity': [], - 'indexOf': ['basicIndexOf', 'sortedIndex'], + 'indexOf': ['baseIndexOf', 'sortedIndex'], 'initial': ['createCallback', 'slice'], 'intersection': ['cacheIndexOf', 'createCache', 'getArray', 'getIndexOf', 'releaseArray', 'releaseObject'], 'invert': ['keys'], @@ -134,7 +134,7 @@ 'isDate': [], 'isElement': [], 'isEmpty': ['forOwn', 'isArguments', 'isFunction'], - 'isEqual': ['createCallback', 'forIn', 'getArray', 'isArguments', 'isFunction', 'isNode', 'releaseArray'], + 'isEqual': ['baseIsEqual', 'createCallback'], 'isFinite': [], 'isFunction': [], 'isNaN': ['isNumber'], @@ -148,24 +148,24 @@ 'keys': ['isArguments', 'isObject', 'shimKeys'], 'last': ['createCallback', 'slice'], 'lastIndexOf': [], - 'map': ['basicEach', 'createCallback', 'isArray'], - 'max': ['basicEach', 'charAtCallback', 'createCallback', 'isArray', 'isString'], + 'map': ['baseEach', 'createCallback', 'isArray'], + 'max': ['baseEach', 'charAtCallback', 'createCallback', 'isArray', 'isString'], 'memoize': [], - 'merge': ['createCallback', 'forEach', 'forOwn', 'getArray', 'isArray', 'isObject', 'isPlainObject', 'releaseArray'], - 'min': ['basicEach', 'charAtCallback', 'createCallback', 'isArray', 'isString'], + 'merge': ['baseMerge', 'createCallback', 'getArray', 'releaseArray'], + 'min': ['baseEach', 'charAtCallback', 'createCallback', 'isArray', 'isString'], 'mixin': ['forEach', 'functions', 'isFunction'], 'noConflict': [], - 'omit': ['basicFlatten', 'createCallback', 'forIn', 'getIndexOf'], + 'omit': ['baseFlatten', 'createCallback', 'forIn', 'getIndexOf'], 'once': [], 'pairs': ['keys'], 'parseInt': ['isString'], 'partial': ['createBound'], 'partialRight': ['createBound'], - 'pick': ['basicFlatten', 'createCallback', 'forIn', 'isObject'], + 'pick': ['baseFlatten', 'createCallback', 'forIn', 'isObject'], 'pluck': ['map'], 'random': [], 'range': [], - 'reduce': ['basicEach', 'createCallback', 'isArray'], + 'reduce': ['baseEach', 'createCallback', 'isArray'], 'reduceRight': ['createCallback', 'forEach', 'isString', 'keys'], 'reject': ['createCallback', 'filter'], 'rest': ['createCallback', 'slice'], @@ -173,7 +173,7 @@ 'runInContext': ['defaults', 'pick'], 'shuffle': ['forEach'], 'size': ['keys'], - 'some': ['basicEach', 'createCallback', 'isArray'], + 'some': ['baseEach', 'createCallback', 'isArray'], 'sortBy': ['compareAscending', 'createCallback', 'forEach', 'getObject', 'releaseObject'], 'sortedIndex': ['createCallback', 'identity'], 'tap': ['value'], @@ -183,10 +183,10 @@ 'toArray': ['isString', 'slice', 'values'], 'transform': ['createCallback', 'createObject', 'forOwn', 'isArray'], 'unescape': ['keys', 'unescapeHtmlChar'], - 'union': ['basicFlatten', 'basicUniq'], - 'uniq': ['basicUniq', 'createCallback'], + 'union': ['baseFlatten', 'baseUniq'], + 'uniq': ['baseUniq', 'createCallback'], 'uniqueId': [], - 'value': ['basicEach', 'forOwn', 'isArray', 'lodash', 'mixin', 'wrapperValueOf', 'lodashWrapper'], + 'value': ['baseEach', 'forOwn', 'isArray', 'lodash', 'mixin', 'wrapperValueOf', 'lodashWrapper'], 'values': ['keys'], 'where': ['filter'], 'without': ['difference'], @@ -195,23 +195,27 @@ 'zipObject': [], // private functions - 'basicEach': ['createIterator'], - 'basicFlatten': ['isArguments', 'isArray'], - 'basicIndexOf': [], - 'basicUniq': ['cacheIndexOf', 'createCache', 'getArray', 'getIndexOf', 'releaseArray', 'releaseObject'], - 'cacheIndexOf': ['basicIndexOf'], + 'baseClone': ['assign', 'forEach', 'forOwn', 'getArray', 'isArray', 'isObject', 'isNode', 'releaseArray', 'slice'], + 'baseEach': ['createIterator'], + 'baseFlatten': ['isArguments', 'isArray'], + 'baseIndexOf': [], + 'baseIsEqual': ['forIn', 'getArray', 'isArguments', 'isFunction', 'isNode', 'releaseArray'], + 'baseMerge': ['forEach', 'forOwn', 'isArray', 'isObject', 'isPlainObject'], + 'baseUniq': ['cacheIndexOf', 'createCache', 'getArray', 'getIndexOf', 'releaseArray', 'releaseObject'], + 'cacheIndexOf': ['baseIndexOf'], 'cachePush': [], 'charAtCallback': [], 'compareAscending': [], - 'createBound': ['createObject', 'isFunction', 'isObject'], + 'createBound': ['createObject', 'isFunction', 'isObject', 'setBindData'], 'createCache': ['cachePush', 'getObject', 'releaseObject'], 'createIterator': ['getObject', 'isArguments', 'isArray', 'isString', 'keys', 'iteratorTemplate', 'lodash', 'releaseObject'], 'createObject': [ 'isObject', 'noop'], 'escapeHtmlChar': [], 'escapeStringChar': [], 'getArray': [], - 'getIndexOf': ['basicIndexOf', 'indexOf'], + 'getIndexOf': ['baseIndexOf', 'indexOf'], 'getObject': [], + 'hasThis': ['setBindData'], 'isNode': [], 'iteratorTemplate': [], 'lodash': ['lodashWrapper'], @@ -219,6 +223,7 @@ 'noop': [], 'releaseArray': [], 'releaseObject': [], + 'setBindData': [], 'shimIsPlainObject': ['forIn', 'isArguments', 'isFunction', 'isNode'], 'shimKeys': ['createIterator'], 'slice': [], @@ -242,6 +247,7 @@ 'at': ['support'], 'bind': ['support'], 'clone': ['support'], + 'createBound': ['support'], 'isArguments': ['support'], 'isEmpty': ['support'], 'isEqual': ['support'], @@ -256,9 +262,8 @@ /** Used to track variable dependencies of identifiers */ var varDependencyMap = { + 'baseIsEqual': ['objectTypes'], 'bind': ['reNative'], - 'bindKey': ['indicatorObject'], - 'createCallback': ['indicatorObject'], 'createIterator': ['indicatorObject', 'objectTypes'], 'createObject': ['reNative'], 'defer': ['objectTypes', 'reNative'], @@ -266,13 +271,10 @@ 'escapeHtmlChar': ['htmlEscapes'], 'htmlUnescapes': ['htmlEscapes'], 'isArray': ['reNative'], - 'isEqual': ['indicatorObject', 'objectTypes'], 'isObject': ['objectTypes'], 'isPlainObject': ['reNative'], 'isRegExp': ['objectTypes'], 'keys': ['reNative'], - 'merge': ['indicatorObject'], - 'partialRight': ['indicatorObject'], 'reEscapedHtml': ['htmlUnescapes'], 'reUnescapedHtml': ['htmlEscapes'], 'support': ['reNative'], @@ -519,10 +521,13 @@ /** List of private functions */ var privateFuncs = [ - 'basicEach', - 'basicFlatten', - 'basicIndex', - 'basicUniq', + 'baseClone', + 'baseEach', + 'baseFlatten', + 'baseIndexOf', + 'baseIsEqual', + 'baseMerge', + 'baseUniq', 'cacheIndexOf', 'cachePush', 'charAtCallback', @@ -534,6 +539,7 @@ 'escapeStringChar', 'getArray', 'getObject', + 'hasThis', 'isNode', 'iteratorTemplate', 'lodash', @@ -541,6 +547,7 @@ 'noop', 'releaseArray', 'releaseObject', + 'setBindData', 'shimIsPlainObject', 'shimKeys', 'slice', @@ -716,7 +723,7 @@ ].join('\n')); // replace wrapper `Array` method assignments - source = source.replace(/^(?:(?: *\/\/.*\n)*(?: *if *\(.+\n)?( *)(basicEach|forEach)\(\['[\s\S]+?\n\1}\);(?:\n *})?\n+)+/m, function(match, indent, methodName) { + source = source.replace(/^(?:(?: *\/\/.*\n)*(?: *if *\(.+\n)?( *)(baseEach|forEach)\(\['[\s\S]+?\n\1}\);(?:\n *})?\n+)+/m, function(match, indent, methodName) { return indent + [ '// add `Array` mutator functions to the wrapper', methodName + "(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(methodName) {", @@ -1360,18 +1367,6 @@ return result ? result[0] : ''; } - /** - * Gets the `hasThis` fork from `source`. - * - * @private - * @param {String} source The source to inspect. - * @returns {String} Returns the `isFunction` fork. - */ - function getHasThisFork(source) { - var result = source.match(/(?:\s*\/\/.*)*\n( *)if *\(!defineProperty\s*\|\|\s*!reThis\)[\s\S]+?\n\1}/); - return result ? result[0] : ''; - } - /** * Gets the Lo-Dash method assignments snippet from `source`. * @@ -1635,6 +1630,30 @@ return source.replace(getDeferFork(source), ''); } + /** + * Removes ES5 specific optimizations from `source`. + * + * @private + * @param {String} source The source to process. + * @returns {String} Returns the modified source. + */ + function removeEsOptimization(source) { + // remove `__bindData` logic and `setBindData` function calls from `createBound` + source = source.replace(matchFunction(source, 'createBound'), function(match) { + return match + .replace(/\bargs *=.+?__bindData__.+\s+/, '') + .replace(/^( *)if *\(args\)[\s\S]+?\n\1}/m, '') + .replace(/^.+?setBindData.+\n/m, '') + }); + + // remove `hasThis` use `_.createCallback` + source = source.replace(matchFunction(source, 'createCallback'), function(match) { + return match.replace(/\s*\|\|\s*!hasThis\(.+?\)/, ''); + }); + + return source; + } + /** * Removes all references to `identifier` from `createIterator` in `source`. * @@ -1756,8 +1775,8 @@ function removeGetIndexOf(source) { source = removeFunction(source, 'getIndexOf'); - // replace all `getIndexOf` calls with `basicIndexOf` - source = source.replace(/\bgetIndexOf\(\)/g, 'basicIndexOf'); + // replace all `getIndexOf` calls with `baseIndexOf` + source = source.replace(/\bgetIndexOf\(\)/g, 'baseIndexOf'); return source; } @@ -1795,35 +1814,6 @@ return source.replace(getIsFunctionFork(source), ''); } - /** - * Removes `hasThis` and its binding optimizations from `source`. - * - * @private - * @param {String} source The source to process. - * @returns {String} Returns the modified source. - */ - function removeHasThis(source) { - source = removeFunction(source, 'hasThis'); - - // remove `hasThis` use `_.createCallback` - source = source.replace(matchFunction(source, 'createCallback'), function(match) { - return match.replace(/\s*\|\|\s*!hasThis\(.+?\)/, ''); - }); - - return source; - } - - /** - * Removes the `hasThis` fork from `source`. - * - * @private - * @param {String} source The source to process. - * @returns {String} Returns the modified source. - */ - function removeHasThisFork(source) { - return source.replace(getHasThisFork(source), ''); - } - /** * Removes the `Object.keys` object iteration optimization from `source`. * @@ -2014,8 +2004,8 @@ function removeSupportArgsObject(source) { source = removeSupportProp(source, 'argsObject'); - // remove `argsAreObjects` from `_.isEqual` - source = source.replace(matchFunction(source, 'isEqual'), function(match) { + // remove `argsAreObjects` from `baseIsEqual` + source = source.replace(matchFunction(source, 'baseIsEqual'), function(match) { return match.replace(/!support.\argsObject[^:]+:\s*/g, ''); }); @@ -2079,15 +2069,15 @@ function removeSupportNodeClass(source) { source = removeSupportProp(source, 'nodeClass'); - // remove `support.nodeClass` from `_.clone` and `shimIsPlainObject` - _.each(['clone', 'shimIsPlainObject'], function(funcName) { + // remove `support.nodeClass` from `baseClone` and `shimIsPlainObject` + _.each(['baseClone', 'shimIsPlainObject'], function(funcName) { source = source.replace(matchFunction(source, funcName), function(match) { return match.replace(/\s*\|\|\s*\(!support\.nodeClass[\s\S]+?\)\)/, ''); }); }); - // remove `support.nodeClass` from `_.isEqual` - source = source.replace(matchFunction(source, 'isEqual'), function(match) { + // remove `support.nodeClass` from `baseIsEqual` + source = source.replace(matchFunction(source, 'baseIsEqual'), function(match) { return match.replace(/\s*\|\|\s*\(!support\.nodeClass[\s\S]+?\)\)\)/, ''); }); @@ -2734,19 +2724,23 @@ else if (isModularize) { _.forOwn(funcDependencyMap, function(deps, funcName) { if (_.contains(deps, 'getIndexOf')) { - (deps = funcDependencyMap[funcName] = _.without(deps, 'getIndexOf')).push( 'basicIndexOf'); + (deps = funcDependencyMap[funcName] = _.without(deps, 'getIndexOf')).push( 'baseIndexOf'); } if (_.contains(deps, 'lodash') || _.contains(deps, 'lodashWrapper')) { funcDependencyMap[funcName] = _.without(deps, 'lodash', 'lodashWrapper'); } }); } + if (isLegacy || isMobile || isUnderscore) { + funcDependencyMap.createBound = _.without(funcDependencyMap.createBound, 'setBindData'); + funcDependencyMap.createCallback = _.without(funcDependencyMap.createCallback, 'hasThis'); + } if (_.contains(plusFuncs, 'chain') == !isUnderscore) { funcDependencyMap.mixin = _.without(funcDependencyMap.mixin, 'isFunction'); } if (isUnderscore) { - if (!isLodash('clone') && !isLodash('cloneDeep')) { - funcDependencyMap.clone = _.without(funcDependencyMap.clone, 'forEach', 'forOwn'); + if (!isLodash('baseClone') && !isLodash('clone') && !isLodash('cloneDeep')) { + (funcDependencyMap.clone = _.without(funcDependencyMap.clone, 'baseClone')).push('assign', 'isArray', 'isObject'); } if (!isLodash('contains')) { funcDependencyMap.contains = _.without(funcDependencyMap.contains, 'isString'); @@ -2757,8 +2751,8 @@ if (!isLodash('isEmpty')) { funcDependencyMap.isEmpty = ['isArray', 'isString']; } - if (!isLodash('isEqual')) { - funcDependencyMap.isEqual = _.without(funcDependencyMap.isEqual, 'forIn', 'isArguments'); + if (!isLodash('baseIsEqual') && !isLodash('isEqual')) { + funcDependencyMap.baseIsEqual = _.without(funcDependencyMap.baseIsEqual, 'forIn', 'isArguments'); } if (!isLodash('pick')){ funcDependencyMap.pick = _.without(funcDependencyMap.pick, 'forIn', 'isObject'); @@ -2770,7 +2764,7 @@ funcDependencyMap.toArray.push('isArray', 'map'); } if (!isLodash('where')) { - funcDependencyMap.createCallback = _.without(funcDependencyMap.createCallback, 'isEqual'); + funcDependencyMap.createCallback = _.without(funcDependencyMap.createCallback, 'baseIsEqual'); funcDependencyMap.where.push('find', 'isEmpty'); } if (!isLodash('forOwn')) { @@ -2779,28 +2773,35 @@ }); } if (!isLodash('forIn')) { - _.each(['isEqual', 'shimIsPlainObject'], function(funcName) { + _.each(['baseIsEqual', 'shimIsPlainObject'], function(funcName) { (varDependencyMap[funcName] || (varDependencyMap[funcName] = [])).push('indicatorObject'); }); } - _.each(['basicUniq', 'difference', 'intersection'], function(funcName) { + _.each(['baseUniq', 'difference', 'intersection'], function(funcName) { if (!isLodash(funcName)) { (funcDependencyMap[funcName] = _.without(funcDependencyMap[funcName], 'cacheIndexOf', 'createCache')).push('getIndexOf'); } }); - _.each(['basicEach', 'forEach', 'forIn', 'forOwn'], function(funcName) { - if (funcName == 'basicEach' || !isLodash(funcName)) { + _.each(['baseEach', 'forEach', 'forIn', 'forOwn'], function(funcName) { + if (funcName == 'baseEach' || !isLodash(funcName)) { (varDependencyMap[funcName] || (varDependencyMap[funcName] = [])).push('indicatorObject'); } }); _.each(['clone', 'isEqual', 'omit', 'pick'], function(funcName) { - if (funcName == 'clone' - ? (!isLodash('clone') && !isLodash('cloneDeep')) - : !isLodash(funcName) - ) { + if (funcName == 'isEqual') { + if (isLodash('baseIsEqual') || isLodash('isEqual')) { + return; + } + } + if (funcName == 'clone') { + if (isLodash('baseClone') || isLodash('clone') || isLodash('cloneDeep')) { + return; + } + } + if (!isLodash(funcName)) { funcDependencyMap[funcName] = _.without(funcDependencyMap[funcName], 'createCallback'); } }); @@ -2820,7 +2821,7 @@ }); } if (isModern || isUnderscore) { - _.each(['assign', 'basicEach', 'defaults', 'forIn', 'forOwn', 'shimKeys'], function(funcName) { + _.each(['assign', 'baseEach', 'defaults', 'forIn', 'forOwn', 'shimKeys'], function(funcName) { if (!(isUnderscore && isLodash(funcName))) { (varDependencyMap[funcName] || (varDependencyMap[funcName] = [])).push('objectTypes'); @@ -2864,12 +2865,12 @@ }); } if (!isMobile) { - _.each(['clone', 'transform', 'value'], function(funcName) { - (funcDependencyMap[funcName] = _.without(funcDependencyMap[funcName], 'basicEach')).push('forEach'); + _.each(['baseClone', 'transform', 'value'], function(funcName) { + (funcDependencyMap[funcName] = _.without(funcDependencyMap[funcName], 'baseEach')).push('forEach'); }); _.each(['contains', 'every', 'filter', 'find', 'forEach', 'map', 'max', 'min', 'reduce', 'some'], function(funcName) { - (funcDependencyMap[funcName] = _.without(funcDependencyMap[funcName], 'basicEach')).push('forOwn'); + (funcDependencyMap[funcName] = _.without(funcDependencyMap[funcName], 'baseEach')).push('forOwn'); }); _.each(['every', 'find', 'filter', 'forEach', 'forIn', 'forOwn', 'map', 'reduce', 'shimKeys'], function(funcName) { @@ -3008,7 +3009,6 @@ } if (isModern) { source = removeIsArgumentsFork(source); - source = removeHasThisFork(source); source = removeSupportSpliceObjects(source); if (isMobile) { @@ -3026,8 +3026,8 @@ }); } } - if ((isLegacy || isMobile || isUnderscore) && !isLodash('createCallback')) { - source = removeHasThis(source); + if (isLegacy || isMobile || isUnderscore) { + source = removeEsOptimization(source); } if (isLegacy || isMobile || isUnderscore) { if (isMobile || (!isLodash('assign') && !isLodash('defaults') && !isLodash('forIn') && !isLodash('forOwn'))) { @@ -3064,7 +3064,7 @@ ' }', ' }', ' } else {', - ' basicEach(collection, callback);', + ' baseEach(collection, callback);', ' }', ' return collection;', '}', @@ -3093,7 +3093,7 @@ ' }', ' } else {', ' result = [];', - ' basicEach(collection, function(value, key, collection) {', + ' baseEach(collection, function(value, key, collection) {', ' result[++index] = callback(value, key, collection);', ' });', ' }', @@ -3124,7 +3124,7 @@ match = match.replace(/^( *)var noaccum\b/m, '$1if (!collection) return accumulator;\n$&'); } else if (/^(?:max|min)$/.test(funcName)) { - match = match.replace(/\bbasicEach\(/, 'forEach('); + match = match.replace(/\bbaseEach\(/, 'forEach('); if (!isUnderscore || isLodash(funcName)) { return match; } @@ -3175,7 +3175,7 @@ ].join('\n')); } // replace `_.clone` - if (!isLodash('clone') && !isLodash('cloneDeep')) { + if (!isLodash('baseClone') && !isLodash('clone') && !isLodash('cloneDeep')) { source = replaceFunction(source, 'clone', [ 'function clone(value) {', ' return isObject(value)', @@ -3194,7 +3194,7 @@ " if (length && typeof length == 'number') {", ' result = indexOf(collection, target) > -1;', ' } else {', - ' basicEach(collection, function(value) {', + ' baseEach(collection, function(value) {', ' return !(result = value === target);', ' });', ' }', @@ -3230,7 +3230,7 @@ ' var index = -1,', ' indexOf = getIndexOf(),', ' length = array.length,', - ' flattened = basicFlatten(arguments, true, true, 1),', + ' flattened = baseFlatten(arguments, true, true, 1),', ' result = [];', '', ' while (++index < length) {', @@ -3288,7 +3288,7 @@ if (!isLodash('flatten')) { source = replaceFunction(source, 'flatten', [ 'function flatten(array, isShallow) {', - ' return basicFlatten(array, isShallow);', + ' return baseFlatten(array, isShallow);', '}' ].join('\n')); } @@ -3340,9 +3340,15 @@ ].join('\n')); } // replace `_.isEqual` - if (!isLodash('isEqual')) { + if (!isLodash('baseIsEqual') && !isLodash('isEqual')) { source = replaceFunction(source, 'isEqual', [ - 'function isEqual(a, b, stackA, stackB) {', + 'function isEqual(a, b) {', + ' return baseIsEqual(a, b);', + '}' + ].join('\n')); + + source = replaceFunction(source, 'baseIsEqual', [ + 'function baseIsEqual(a, b, stackA, stackB) {', ' if (a === b) {', ' return a !== 0 || (1 / a == 1 / b);', ' }', @@ -3380,7 +3386,7 @@ ' var isArr = className == arrayClass;', ' if (!isArr) {', ' if (a instanceof lodash || b instanceof lodash) {', - ' return isEqual(a.__wrapped__ || a, b.__wrapped__ || b, stackA, stackB);', + ' return baseIsEqual(a.__wrapped__ || a, b.__wrapped__ || b, stackA, stackB);', ' }', ' if (className != objectClass) {', ' return false;', @@ -3416,7 +3422,7 @@ '', ' if (result) {', ' while (size--) {', - ' if (!(result = isEqual(a[size], b[size], stackA, stackB))) {', + ' if (!(result = baseIsEqual(a[size], b[size], stackA, stackB))) {', ' break;', ' }', ' }', @@ -3426,7 +3432,7 @@ ' forIn(b, function(value, key, b) {', ' if (hasOwnProperty.call(b, key)) {', ' size++;', - ' return (result = hasOwnProperty.call(a, key) && isEqual(a[key], value, stackA, stackB));', + ' return (result = hasOwnProperty.call(a, key) && baseIsEqual(a[key], value, stackA, stackB));', ' }', ' });', '', @@ -3460,7 +3466,7 @@ source = replaceFunction(source, 'omit', [ 'function omit(object) {', ' var indexOf = getIndexOf(),', - ' props = basicFlatten(arguments, true, false, 1),', + ' props = baseFlatten(arguments, true, false, 1),', ' result = {};', '', ' forIn(object, function(value, key) {', @@ -3477,7 +3483,7 @@ source = replaceFunction(source, 'pick', [ 'function pick(object) {', ' var index = -1,', - ' props = basicFlatten(arguments, true, false, 1),', + ' props = baseFlatten(arguments, true, false, 1),', ' length = props.length,', ' result = {};', '', @@ -3637,10 +3643,10 @@ '}' ].join('\n')); } - // replace `basicUniq` + // replace `baseUniq` if (!isLodash('uniq')) { - source = replaceFunction(source, 'basicUniq', [ - 'function basicUniq(array, isSorted, callback) {', + source = replaceFunction(source, 'baseUniq', [ + 'function baseUniq(array, isSorted, callback) {', ' var index = -1,', ' indexOf = getIndexOf(),', ' length = array ? array.length : 0,', @@ -3689,8 +3695,8 @@ return match // remove unnecessary fast path .replace(/^(( *)var props *=.+?),[\s\S]+?\n\2}/m, '$1;') - // remove `_.isEqual` use - .replace(/=.+?\bisEqual\((.+?), *(.+?),.+?\)/, '= $1 === $2'); + // remove `baseIsEqual` use + .replace(/=.+?\bbaseIsEqual\((.+?), *(.+?),.+?\)/, '= $1 === $2'); }); } // replace `_.zip` @@ -3720,7 +3726,7 @@ // replace `slice` with `nativeSlice.call` _.each(['clone', 'first', 'initial', 'last', 'rest', 'toArray'], function(funcName) { if (funcName == 'clone' - ? (!isLodash('clone') && !isLodash('cloneDeep')) + ? (!isLodash('baseClone') && !isLodash('clone') && !isLodash('cloneDeep')) : !isLodash(funcName) ) { source = source.replace(matchFunction(source, funcName), function(match) { @@ -3737,42 +3743,21 @@ }); } }); - - // remove unused features from `createBound` - if (_.every(['bindKey', 'partial', 'partialRight'], function(funcName) { - return !_.contains(buildFuncs, funcName); - })) { - source = source.replace(matchFunction(source, 'createBound'), function(match) { - return match - .replace(/, *indicator[^)]*/, '') - .replace(/(function createBound\([^{]+{)[\s\S]+?(\n *)(function bound)/, function(match, part1, indent, part2) { - return [ - part1, - 'if (!isFunction(func)) {', - ' throw new TypeError;', - '}', - part2 - ].join(indent); - }) - .replace(/thisBinding *=[^}]+}/, 'thisBinding = thisArg;\n') - .replace(/\(args *=.+/, 'partialArgs.concat(nativeSlice.call(args))'); - }); - } } // add Underscore's chaining functions if (_.contains(plusFuncs, 'chain') == !isUnderscore) { source = addUnderscoreChaining(source); } - // replace `basicEach` references with `forEach` and `forOwn` + // replace `baseEach` references with `forEach` and `forOwn` if (isUnderscore || (isModern && !isMobile)) { - // replace `basicEach` with `_.forOwn` in "Collections" functions - source = source.replace(/\bbasicEach(?=\(collection)/g, 'forOwn'); + // replace `baseEach` with `_.forOwn` in "Collections" functions + source = source.replace(/\bbaseEach(?=\(collection)/g, 'forOwn'); - // replace `basicEach` with `_.forEach` in the rest of the functions - source = source.replace(/(\?\s*)basicEach(?=\s*:)/g, '$1forEach'); + // replace `baseEach` with `_.forEach` in the rest of the functions + source = source.replace(/(\?\s*)baseEach(?=\s*:)/g, '$1forEach'); - // replace `basicEach` with `_.forEach` in the function assignment snippet - source = source.replace(/\bbasicEach(?=\(\[')/g, 'forEach'); + // replace `baseEach` with `_.forEach` in the function assignment snippet + source = source.replace(/\bbaseEach(?=\(\[')/g, 'forEach'); } var context = vm.createContext({ @@ -3871,9 +3856,9 @@ }); if (isUnderscore) { - // unexpose "exit early" feature of `basicEach`, `_.forEach`, `_.forIn`, and `_.forOwn` - _.each(['basicEach', 'forEach', 'forIn', 'forOwn'], function(funcName) { - if (funcName == 'basicEach' || !isLodash(funcName)) { + // unexpose "exit early" feature of `baseEach`, `_.forEach`, `_.forIn`, and `_.forOwn` + _.each(['baseEach', 'forEach', 'forIn', 'forOwn'], function(funcName) { + if (funcName == 'baseEach' || !isLodash(funcName)) { source = source.replace(matchFunction(source, funcName), function(match) { return match.replace(/=== *false\)/g, '=== indicatorObject)'); }); @@ -3900,9 +3885,9 @@ }); }); } - // modify `_.isEqual` and `shimIsPlainObject` to use the private `indicatorObject` + // modify `baseEqual` and `shimIsPlainObject` to use the private `indicatorObject` if (!isLodash('forIn')) { - source = source.replace(matchFunction(source, 'isEqual'), function(match) { + source = source.replace(matchFunction(source, 'baseIsEqual'), function(match) { return match.replace(/\(result *= *(.+?)\);/g, '!(result = $1) && indicatorObject;'); }); @@ -3926,9 +3911,9 @@ if (!isLodash('createCallback')) { source = source.replace(/\blodash\.(createCallback\()\b/g, '$1'); } - // remove chainability from `basicEach` and `_.forEach` + // remove chainability from `baseEach` and `_.forEach` if (!isLodash('forEach')) { - _.each(['basicEach', 'forEach'], function(funcName) { + _.each(['baseEach', 'forEach'], function(funcName) { source = source.replace(matchFunction(source, funcName), function(match) { return match .replace(/\n *return .+?([};\s]+)$/, '$1') @@ -4009,7 +3994,7 @@ // remove all `lodash.prototype` additions source = source .replace(/(?:\s*\/\/.*)*\n( *)forOwn\(lodash,[\s\S]+?\n\1}.+/g, '') - .replace(/(?:\s*\/\/.*)*\n( *)(?:basicEach|forEach)\(\['[\s\S]+?\n\1}.+/g, '') + .replace(/(?:\s*\/\/.*)*\n( *)(?:baseEach|forEach)\(\['[\s\S]+?\n\1}.+/g, '') .replace(/(?:\s*\/\/.*)*\n *lodash\.prototype\.[\s\S]+?;/g, ''); } if (!isNoDep) { @@ -4063,6 +4048,32 @@ } } + // remove functions from the build + allFuncs.forEach(function(otherName) { + if (!_.contains(buildFuncs, otherName) && + !(otherName == 'findWhere' && !isUnderscore) && + !(otherName == 'lodash' && !isNoDep)) { + source = removeFunction(source, otherName); + if (!isNoDep) { + source = removeFromCreateIterator(source, otherName); + } + } + }); + + // remove forks of removed functions + _.each(['createObject', 'defer', 'isArguments', 'isArray', 'isFunction'], function(funcName) { + if (isExcluded(funcName)) { + source = eval('remove' + capitalize(funcName) + 'Fork')(source); + } + }); + + // remove unneeded property dependencies + _.each(propDependencies, function(propName) { + if (!_.contains(includeProps, propName)) { + source = removeProp(source, propName); + } + }); + // remove code used to resolve unneeded `support` properties source = source.replace(matchVar(source, 'support'), function(match) { return match.replace(/^ *\(function[\s\S]+?\n(( *)var ctor *=[\s\S]+?(?:\n *for.+)+\n)([\s\S]+?)}\(1\)\);\n/m, function(match, setup, indent, body) { @@ -4093,32 +4104,6 @@ }); }); - // remove functions from the build - allFuncs.forEach(function(otherName) { - if (!_.contains(buildFuncs, otherName) && - !(otherName == 'findWhere' && !isUnderscore) && - !(otherName == 'lodash' && !isNoDep)) { - source = removeFunction(source, otherName); - if (!isNoDep) { - source = removeFromCreateIterator(source, otherName); - } - } - }); - - // remove forks of removed functions - _.each(['createObject', 'defer', 'hasThis', 'isArguments', 'isArray', 'isFunction'], function(funcName) { - if (isExcluded(funcName)) { - source = eval('remove' + capitalize(funcName) + 'Fork')(source); - } - }); - - // remove unneeded property dependencies - _.each(propDependencies, function(propName) { - if (!_.contains(includeProps, propName)) { - source = removeProp(source, propName); - } - }); - // remove unused variables (function() { var isShallow = isExcluded('runInContext'), diff --git a/build/pre-compile.js b/build/pre-compile.js index 5ca64c551..08644cbdf 100644 --- a/build/pre-compile.js +++ b/build/pre-compile.js @@ -80,6 +80,8 @@ 'TypeError', 'VERSION', '_', + '__bindData__', + '__chain__', '__wrapped__', 'after', 'all', @@ -95,6 +97,7 @@ 'bindAll', 'bindKey', 'cache', + 'chain', 'clearTimeout', 'clone', 'cloneDeep', @@ -127,6 +130,7 @@ 'find', 'findIndex', 'findKey', + 'findWhere', 'first', 'flatten', 'foldl', @@ -244,12 +248,7 @@ 'without', 'wrap', 'zip', - 'zipObject', - - // properties used by the `backbone` and `underscore` builds - '__chain__', - 'chain', - 'findWhere' + 'zipObject' ]; /*--------------------------------------------------------------------------*/ @@ -374,8 +373,8 @@ 'createIterator\\((?:{|[a-zA-Z]+)[\\s\\S]*?\\);\\n', // match variables storing `createIterator` options '^( *)var [a-zA-Z]+IteratorOptions\\b[\\s\\S]+?\\n\\2}', - // match `basicUniq`, `cachePush`, `createCache`, `createIterator`, `getObject`, and `releaseObject` functions - '^( *)(?:var|function) +(?:basicUniq|cachePush|createCache|createIterator|getObject|releaseObject)\\b[\\s\\S]+?\\n\\3}' + // match `baseUniq`, `cachePush`, `createCache`, `createIterator`, `getObject`, and `releaseObject` functions + '^( *)(?:var|function) +(?:baseUniq|cachePush|createCache|createIterator|getObject|releaseObject)\\b[\\s\\S]+?\\n\\3}' ].join('|'), 'gm') ); diff --git a/lodash.js b/lodash.js index 89c0a6d12..2511f0cd6 100644 --- a/lodash.js +++ b/lodash.js @@ -143,7 +143,7 @@ /*--------------------------------------------------------------------------*/ /** - * A basic implementation of `_.indexOf` without support for binary searches + * A base implementation of `_.indexOf` without support for binary searches * or `fromIndex` constraints. * * @private @@ -152,7 +152,7 @@ * @param {Number} [fromIndex=0] The index to search from. * @returns {Number} Returns the index of the matched value or `-1`. */ - function basicIndexOf(array, value, fromIndex) { + function baseIndexOf(array, value, fromIndex) { var index = (fromIndex || 0) - 1, length = array ? array.length : 0; @@ -187,7 +187,7 @@ cache = cache[type] || (cache[type] = {}); return type == 'object' - ? (cache[key] && basicIndexOf(cache[key], value) > -1 ? 0 : -1) + ? (cache[key] && baseIndexOf(cache[key], value) > -1 ? 0 : -1) : (cache[key] ? 0 : -1); } @@ -485,6 +485,7 @@ /** Native method shortcuts */ var ceil = Math.ceil, clearTimeout = context.clearTimeout, + concat = arrayRef.concat, defineProperty = reNative.test(defineProperty = Object.defineProperty) && defineProperty, floor = Math.floor, fnToString = Function.prototype.toString, @@ -967,7 +968,97 @@ /*--------------------------------------------------------------------------*/ /** - * A basic implementation of `_.flatten` without support for `callback` + * A base implementation of `_.clone` without argument juggling or support + * for `thisArg` binding. + * + * @private + * @param {Mixed} value The value to clone. + * @param {Boolean} [deep=false] A flag to indicate a deep clone. + * @param {Function} [callback] The function to customize cloning values. + * @param {Array} [stackA=[]] Tracks traversed source objects. + * @param {Array} [stackB=[]] Associates clones with source counterparts. + * @returns {Mixed} Returns the cloned `value`. + */ + function baseClone(value, deep, callback, stackA, stackB) { + var result = value; + + if (callback) { + result = callback(result); + if (typeof result != 'undefined') { + return result; + } + result = value; + } + // inspect [[Class]] + var isObj = isObject(result); + if (isObj) { + var className = toString.call(result); + if (!cloneableClasses[className] || (!support.nodeClass && isNode(result))) { + return result; + } + var isArr = isArray(result); + } + // shallow clone + if (!isObj || !deep) { + return isObj + ? (isArr ? slice(result) : assign({}, result)) + : result; + } + var ctor = ctorByClass[className]; + switch (className) { + case boolClass: + case dateClass: + return new ctor(+result); + + case numberClass: + case stringClass: + return new ctor(result); + + case regexpClass: + return ctor(result.source, reFlags.exec(result)); + } + // check for circular references and return corresponding clone + var initedStack = !stackA; + stackA || (stackA = getArray()); + stackB || (stackB = getArray()); + + var length = stackA.length; + while (length--) { + if (stackA[length] == value) { + return stackB[length]; + } + } + // init cloned object + result = isArr ? ctor(result.length) : {}; + + // add array properties assigned by `RegExp#exec` + if (isArr) { + if (hasOwnProperty.call(value, 'index')) { + result.index = value.index; + } + if (hasOwnProperty.call(value, 'input')) { + result.input = value.input; + } + } + // add the source value to the stack of traversed objects + // and associate it with its clone + stackA.push(value); + stackB.push(result); + + // recursively populate clone (susceptible to call stack limits) + (isArr ? baseEach : forOwn)(value, function(objValue, key) { + result[key] = baseClone(objValue, deep, callback, stackA, stackB); + }); + + if (initedStack) { + releaseArray(stackA); + releaseArray(stackB); + } + return result; + } + + /** + * A base implementation of `_.flatten` without support for `callback` * shorthands or `thisArg` binding. * * @private @@ -977,7 +1068,7 @@ * @param {Number} [fromIndex=0] The index to start from. * @returns {Array} Returns a new flattened array. */ - function basicFlatten(array, isShallow, isArgArrays, fromIndex) { + function baseFlatten(array, isShallow, isArgArrays, fromIndex) { var index = (fromIndex || 0) - 1, length = array ? array.length : 0, result = []; @@ -986,7 +1077,7 @@ var value = array[index]; // recursively flatten arrays (susceptible to call stack limits) if (value && typeof value == 'object' && (isArray(value) || isArguments(value))) { - push.apply(result, isShallow ? value : basicFlatten(value, isShallow, isArgArrays)); + push.apply(result, isShallow ? value : baseFlatten(value, isShallow, isArgArrays)); } else if (!isArgArrays) { result.push(value); } @@ -995,7 +1086,241 @@ } /** - * A basic implementation of `_.uniq` without support for `callback` shorthands + * A base implementation of `_.isEqual`, without support for `thisArg` binding, + * that allows partial "_.where" style comparisons. + * + * @private + * @param {Mixed} a The value to compare. + * @param {Mixed} b The other value to compare. + * @param {Function} [callback] The function to customize comparing values. + * @param {Function} [isWhere] A flag to indicate performing partial comparisons. + * @param {Array} [stackA=[]] Tracks traversed `a` objects. + * @param {Array} [stackB=[]] Tracks traversed `b` objects. + * @returns {Boolean} Returns `true`, if the values are equivalent, else `false`. + */ + function baseIsEqual(a, b, callback, isWhere, stackA, stackB) { + // used to indicate that when comparing objects, `a` has at least the properties of `b` + if (callback) { + var result = callback(a, b); + if (typeof result != 'undefined') { + return !!result; + } + } + // exit early for identical values + if (a === b) { + // treat `+0` vs. `-0` as not equal + return a !== 0 || (1 / a == 1 / b); + } + var type = typeof a, + otherType = typeof b; + + // exit early for unlike primitive values + if (a === a && + !(a && objectTypes[type]) && + !(b && objectTypes[otherType])) { + return false; + } + // exit early for `null` and `undefined`, avoiding ES3's Function#call behavior + // http://es5.github.io/#x15.3.4.4 + if (a == null || b == null) { + return a === b; + } + // compare [[Class]] names + var className = toString.call(a), + otherClass = toString.call(b); + + if (className == argsClass) { + className = objectClass; + } + if (otherClass == argsClass) { + otherClass = objectClass; + } + if (className != otherClass) { + return false; + } + switch (className) { + case boolClass: + case dateClass: + // coerce dates and booleans to numbers, dates to milliseconds and booleans + // to `1` or `0`, treating invalid dates coerced to `NaN` as not equal + return +a == +b; + + case numberClass: + // treat `NaN` vs. `NaN` as equal + return (a != +a) + ? b != +b + // but treat `+0` vs. `-0` as not equal + : (a == 0 ? (1 / a == 1 / b) : a == +b); + + case regexpClass: + case stringClass: + // coerce regexes to strings (http://es5.github.io/#x15.10.6.4) + // treat string primitives and their corresponding object instances as equal + return a == String(b); + } + 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); + } + // exit for functions and DOM nodes + if (className != objectClass || (!support.nodeClass && (isNode(a) || isNode(b)))) { + return false; + } + // in older versions of Opera, `arguments` objects have `Array` constructors + var ctorA = !support.argsObject && isArguments(a) ? Object : a.constructor, + ctorB = !support.argsObject && isArguments(b) ? Object : b.constructor; + + // non `Object` object instances with different constructors are not equal + if (ctorA != ctorB && !( + isFunction(ctorA) && ctorA instanceof ctorA && + isFunction(ctorB) && ctorB instanceof ctorB + )) { + return false; + } + } + // assume cyclic structures are equal + // the algorithm for detecting cyclic structures is adapted from ES 5.1 + // section 15.12.3, abstract operation `JO` (http://es5.github.io/#x15.12.3) + var initedStack = !stackA; + stackA || (stackA = getArray()); + stackB || (stackB = getArray()); + + var length = stackA.length; + while (length--) { + if (stackA[length] == a) { + return stackB[length] == b; + } + } + var size = 0; + result = true; + + // add `a` and `b` to the stack of traversed objects + stackA.push(a); + stackB.push(b); + + // recursively compare objects and arrays (susceptible to call stack limits) + if (isArr) { + length = a.length; + size = b.length; + + // compare lengths to determine if a deep comparison is necessary + result = size == a.length; + if (!result && !isWhere) { + return result; + } + // deep compare the contents, ignoring non-numeric properties + while (size--) { + var index = length, + value = b[size]; + + if (isWhere) { + while (index--) { + if ((result = baseIsEqual(a[index], value, callback, isWhere, stackA, stackB))) { + break; + } + } + } else if (!(result = baseIsEqual(a[size], value, callback, isWhere, stackA, stackB))) { + break; + } + } + return result; + } + // deep compare objects using `forIn`, instead of `forOwn`, to avoid `Object.keys` + // which, in this case, is more costly + forIn(b, function(value, key, b) { + if (hasOwnProperty.call(b, key)) { + // count the number of properties. + size++; + // deep compare each property value. + return (result = hasOwnProperty.call(a, key) && baseIsEqual(a[key], value, callback, isWhere, stackA, stackB)); + } + }); + + if (result && !isWhere) { + // ensure both objects have the same number of properties + forIn(a, function(value, key, a) { + if (hasOwnProperty.call(a, key)) { + // `size` will be `-1` if `a` has more properties than `b` + return (result = --size > -1); + } + }); + } + if (initedStack) { + releaseArray(stackA); + releaseArray(stackB); + } + return result; + } + + /** + * A base implementation of `_.merge` without argument juggling or support + * for `thisArg` binding. + * + * @private + * @param {Object} object The destination object. + * @param {Object} source The source object. + * @param {Function} [callback] The function to customize merging properties. + * @param {Array} [stackA=[]] Tracks traversed source objects. + * @param {Array} [stackB=[]] Associates values with source counterparts. + */ + function baseMerge(object, source, callback, stackA, stackB) { + (isArray(source) ? forEach : forOwn)(source, function(source, key) { + var found, + isArr, + result = source, + value = object[key]; + + if (source && ((isArr = isArray(source)) || isPlainObject(source))) { + // avoid merging previously merged cyclic sources + var stackLength = stackA.length; + while (stackLength--) { + if ((found = stackA[stackLength] == source)) { + value = stackB[stackLength]; + break; + } + } + if (!found) { + var isShallow; + if (callback) { + result = callback(value, source); + if ((isShallow = typeof result != 'undefined')) { + value = result; + } + } + if (!isShallow) { + value = isArr + ? (isArray(value) ? value : []) + : (isPlainObject(value) ? value : {}); + } + // add `source` and associated `value` to the stack of traversed objects + stackA.push(source); + stackB.push(value); + + // recursively merge objects and arrays (susceptible to call stack limits) + if (!isShallow) { + baseMerge(value, source, callback, stackA, stackB); + } + } + } + else { + if (callback) { + result = callback(value, source); + if (typeof result == 'undefined') { + result = source; + } + } + if (typeof result != 'undefined') { + value = result; + } + } + object[key] = value; + }); + } + + /** + * A base implementation of `_.uniq` without support for `callback` shorthands * or `thisArg` binding. * * @private @@ -1004,13 +1329,13 @@ * @param {Function} [callback] The function called per iteration. * @returns {Array} Returns a duplicate-value-free array. */ - function basicUniq(array, isSorted, callback) { + function baseUniq(array, isSorted, callback) { var index = -1, indexOf = getIndexOf(), length = array ? array.length : 0, result = []; - var isLarge = !isSorted && length >= largeArraySize && indexOf === basicIndexOf, + var isLarge = !isSorted && length >= largeArraySize && indexOf === baseIndexOf, seen = (callback || isLarge) ? getArray() : result; if (isLarge) { @@ -1059,49 +1384,60 @@ * applying arguments from the right. * @returns {Function} Returns the new bound function. */ - function createBound(func, thisArg, partialArgs, indicator) { - var isFunc = isFunction(func), - isPartial = !partialArgs, + function createBound(func, thisArg, partialArgs, partialRightArgs, isPartial, isAlt) { + var isFunc = isFunction(func); + if (!isFunc && !(isAlt && !isPartial)) { + throw new TypeError; + } + var args = func.__bindData__, key = thisArg; - // juggle arguments - if (isPartial) { - var rightIndicator = indicator; - partialArgs = thisArg; - } - else if (!isFunc) { - if (!indicator) { - throw new TypeError; + if (args) { + push.apply(args[2], partialArgs); + push.apply(args[3], partialRightArgs); + if (!isPartial && args[4]) { + args[1] = thisArg; + args[4] = false; } + return createBound.apply(null, args); + } + // juggle arguments + if (!isPartial && !isFunc) { thisArg = func; } - - function bound() { - // `Function#bind` spec - // http://es5.github.io/#x15.3.4.5 - var args = arguments, - thisBinding = isPartial ? this : thisArg; - - if (!isFunc) { - func = thisArg[key]; - } - if (partialArgs.length) { - args = args.length - ? (args = nativeSlice.call(args), rightIndicator ? args.concat(partialArgs) : partialArgs.concat(args)) - : partialArgs; - } - 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); + if (!isPartial && !isAlt && (support.fastBind || (nativeBind && partialArgs.length))) { + // use `Function#bind` if it exists and is fast + // (in V8 `Function#bind` is slower except when partially applied) + var result = nativeBind.call.apply(nativeBind, concat.call(arrayRef, func, thisArg, partialArgs)); } - return bound; + else { + var result = function bound() { + // `Function#bind` spec + // http://es5.github.io/#x15.3.4.5 + var args = arguments, + thisBinding = isPartial ? this : thisArg; + + if (!isFunc) { + func = thisArg[key]; + } + if (partialArgs.length || partialRightArgs.length) { + args = concat.apply(partialArgs, args); + push.apply(args, partialRightArgs); + } + 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); + }; + } + setBindData(result, nativeSlice.call(arguments)); + return result } /** @@ -1194,13 +1530,13 @@ /** * Gets the appropriate "indexOf" function. If the `_.indexOf` method is * customized, this method returns the custom method, otherwise it returns - * the `basicIndexOf` function. + * the `baseIndexOf` function. * * @private * @returns {Function} Returns the "indexOf" function. */ function getIndexOf() { - var result = (result = lodash.indexOf) === indexOf ? basicIndexOf : result; + var result = (result = lodash.indexOf) === indexOf ? baseIndexOf : result; return result; } @@ -1212,24 +1548,29 @@ * @returns {Boolean} Returns `true` if `this` is referenced, else `false`. */ function hasThis(func) { - var result = func.__hasThis__; - if (typeof result == 'boolean') { - return result; + var result = func.__bindData__; + if (typeof result != 'undefined') { + return result === true || (result && result[4]); } - result = reThis.test(fnToString.call(func)); - defineProperty(func, '__hasThis__', { - 'configurable': true, - 'enumerable': false, - 'writable': true, - 'value': result - }); + result = !reThis || reThis.test(fnToString.call(func)); + setBindData(func, result); return result; } - // fallback for older browsers - if (!defineProperty || !reThis) { - hasThis = function() { - return true; - }; + + /** + * Sets `this` binding data on a given function. + * + * @private + * @param {Function} func The function to set data on. + * @param {Mixed} value The value to set. + */ + function setBindData(func, value) { + defineProperty(func, '__bindData__', { + 'configurable': false, + 'enumerable': false, + 'value': value, + 'writable': false + }); } /** @@ -1386,7 +1727,7 @@ * @param {Mixed} [thisArg] The `this` binding of `callback`. * @returns {Array|Object|String} Returns `collection`. */ - var basicEach = createIterator(eachIteratorOptions); + var baseEach = createIterator(eachIteratorOptions); /** * Used to convert characters to HTML entities: @@ -1470,8 +1811,6 @@ * @param {Boolean} [deep=false] A flag to indicate a deep clone. * @param {Function} [callback] The function to customize cloning values. * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @param- {Array} [stackA=[]] Tracks traversed source objects. - * @param- {Array} [stackB=[]] Associates clones with source counterparts. * @returns {Mixed} Returns the cloned `value`. * @example * @@ -1498,9 +1837,7 @@ * clone.childNodes.length; * // => 0 */ - function clone(value, deep, callback, thisArg, stackA, stackB) { - var result = value; - + function clone(value, deep, callback, thisArg) { // allows working with "Collections" methods without using their `index` // and `collection` arguments for `deep` and `callback` if (typeof deep != 'boolean' && deep != null) { @@ -1508,83 +1845,7 @@ callback = deep; deep = false; } - if (typeof callback == 'function') { - callback = (typeof thisArg == 'undefined') - ? callback - : lodash.createCallback(callback, thisArg, 1); - - result = callback(result); - if (typeof result != 'undefined') { - return result; - } - result = value; - } - // inspect [[Class]] - var isObj = isObject(result); - if (isObj) { - var className = toString.call(result); - if (!cloneableClasses[className] || (!support.nodeClass && isNode(result))) { - return result; - } - var isArr = isArray(result); - } - // shallow clone - if (!isObj || !deep) { - return isObj - ? (isArr ? slice(result) : assign({}, result)) - : result; - } - var ctor = ctorByClass[className]; - switch (className) { - case boolClass: - case dateClass: - return new ctor(+result); - - case numberClass: - case stringClass: - return new ctor(result); - - case regexpClass: - return ctor(result.source, reFlags.exec(result)); - } - // check for circular references and return corresponding clone - var initedStack = !stackA; - stackA || (stackA = getArray()); - stackB || (stackB = getArray()); - - var length = stackA.length; - while (length--) { - if (stackA[length] == value) { - return stackB[length]; - } - } - // init cloned object - result = isArr ? ctor(result.length) : {}; - - // add array properties assigned by `RegExp#exec` - if (isArr) { - if (hasOwnProperty.call(value, 'index')) { - result.index = value.index; - } - if (hasOwnProperty.call(value, 'input')) { - result.input = value.input; - } - } - // add the source value to the stack of traversed objects - // and associate it with its clone - stackA.push(value); - stackB.push(result); - - // recursively populate clone (susceptible to call stack limits) - (isArr ? basicEach : forOwn)(value, function(objValue, key) { - result[key] = clone(objValue, deep, callback, undefined, stackA, stackB); - }); - - if (initedStack) { - releaseArray(stackA); - releaseArray(stackB); - } - return result; + return baseClone(value, deep, typeof callback == 'function' && lodash.createCallback(callback, thisArg, 1)); } /** @@ -1629,7 +1890,7 @@ * // => false */ function cloneDeep(value, callback, thisArg) { - return clone(value, true, callback, thisArg); + return baseClone(value, true, typeof callback == 'function' && lodash.createCallback(callback, thisArg, 1)); } /** @@ -1917,8 +2178,6 @@ * @param {Mixed} b The other value to compare. * @param {Function} [callback] The function to customize comparing values. * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @param- {Array} [stackA=[]] Tracks traversed `a` objects. - * @param- {Array} [stackB=[]] Tracks traversed `b` objects. * @returns {Boolean} Returns `true`, if the values are equivalent, else `false`. * @example * @@ -1943,162 +2202,8 @@ * }); * // => true */ - function isEqual(a, b, callback, thisArg, stackA, stackB) { - // used to indicate that when comparing objects, `a` has at least the properties of `b` - var whereIndicator = callback === indicatorObject; - if (typeof callback == 'function' && !whereIndicator) { - callback = lodash.createCallback(callback, thisArg, 2); - var result = callback(a, b); - if (typeof result != 'undefined') { - return !!result; - } - } - // exit early for identical values - if (a === b) { - // treat `+0` vs. `-0` as not equal - return a !== 0 || (1 / a == 1 / b); - } - var type = typeof a, - otherType = typeof b; - - // exit early for unlike primitive values - if (a === a && - !(a && objectTypes[type]) && - !(b && objectTypes[otherType])) { - return false; - } - // exit early for `null` and `undefined`, avoiding ES3's Function#call behavior - // http://es5.github.io/#x15.3.4.4 - if (a == null || b == null) { - return a === b; - } - // compare [[Class]] names - var className = toString.call(a), - otherClass = toString.call(b); - - if (className == argsClass) { - className = objectClass; - } - if (otherClass == argsClass) { - otherClass = objectClass; - } - if (className != otherClass) { - return false; - } - switch (className) { - case boolClass: - case dateClass: - // coerce dates and booleans to numbers, dates to milliseconds and booleans - // to `1` or `0`, treating invalid dates coerced to `NaN` as not equal - return +a == +b; - - case numberClass: - // treat `NaN` vs. `NaN` as equal - return (a != +a) - ? b != +b - // but treat `+0` vs. `-0` as not equal - : (a == 0 ? (1 / a == 1 / b) : a == +b); - - case regexpClass: - case stringClass: - // coerce regexes to strings (http://es5.github.io/#x15.10.6.4) - // treat string primitives and their corresponding object instances as equal - return a == String(b); - } - var isArr = className == arrayClass; - if (!isArr) { - // unwrap any `lodash` wrapped values - if (hasOwnProperty.call(a, '__wrapped__ ') || hasOwnProperty.call(b, '__wrapped__')) { - return isEqual(a.__wrapped__ || a, b.__wrapped__ || b, callback, thisArg, stackA, stackB); - } - // exit for functions and DOM nodes - if (className != objectClass || (!support.nodeClass && (isNode(a) || isNode(b)))) { - return false; - } - // in older versions of Opera, `arguments` objects have `Array` constructors - var ctorA = !support.argsObject && isArguments(a) ? Object : a.constructor, - ctorB = !support.argsObject && isArguments(b) ? Object : b.constructor; - - // non `Object` object instances with different constructors are not equal - if (ctorA != ctorB && !( - isFunction(ctorA) && ctorA instanceof ctorA && - isFunction(ctorB) && ctorB instanceof ctorB - )) { - return false; - } - } - // assume cyclic structures are equal - // the algorithm for detecting cyclic structures is adapted from ES 5.1 - // section 15.12.3, abstract operation `JO` (http://es5.github.io/#x15.12.3) - var initedStack = !stackA; - stackA || (stackA = getArray()); - stackB || (stackB = getArray()); - - var length = stackA.length; - while (length--) { - if (stackA[length] == a) { - return stackB[length] == b; - } - } - var size = 0; - result = true; - - // add `a` and `b` to the stack of traversed objects - stackA.push(a); - stackB.push(b); - - // recursively compare objects and arrays (susceptible to call stack limits) - if (isArr) { - length = a.length; - size = b.length; - - // compare lengths to determine if a deep comparison is necessary - result = size == a.length; - if (!result && !whereIndicator) { - return result; - } - // deep compare the contents, ignoring non-numeric properties - while (size--) { - var index = length, - value = b[size]; - - if (whereIndicator) { - while (index--) { - if ((result = isEqual(a[index], value, callback, thisArg, stackA, stackB))) { - break; - } - } - } else if (!(result = isEqual(a[size], value, callback, thisArg, stackA, stackB))) { - break; - } - } - return result; - } - // deep compare objects using `forIn`, instead of `forOwn`, to avoid `Object.keys` - // which, in this case, is more costly - forIn(b, function(value, key, b) { - if (hasOwnProperty.call(b, key)) { - // count the number of properties. - size++; - // deep compare each property value. - return (result = hasOwnProperty.call(a, key) && isEqual(a[key], value, callback, thisArg, stackA, stackB)); - } - }); - - if (result && !whereIndicator) { - // ensure both objects have the same number of properties - forIn(a, function(value, key, a) { - if (hasOwnProperty.call(a, key)) { - // `size` will be `-1` if `a` has more properties than `b` - return (result = --size > -1); - } - }); - } - if (initedStack) { - releaseArray(stackA); - releaseArray(stackB); - } - return result; + function isEqual(a, b, callback, thisArg) { + return baseIsEqual(a, b, typeof callback == 'function' && lodash.createCallback(callback, thisArg, 2)); } /** @@ -2357,10 +2462,6 @@ * @param {Object} [source1, source2, ...] The source objects. * @param {Function} [callback] The function to customize merging properties. * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @param- {Object} [deepIndicator] Indicates that `stackA` and `stackB` are - * arrays of traversed objects, instead of source objects. - * @param- {Array} [stackA=[]] Tracks traversed source objects. - * @param- {Array} [stackB=[]] Associates values with source counterparts. * @returns {Object} Returns the destination object. * @example * @@ -2396,92 +2497,33 @@ * }); * // => { 'fruits': ['apple', 'banana'], 'vegetables': ['beet', 'carrot] } */ - function merge(object, source, deepIndicator) { + function merge(object) { var args = arguments, - index = 0, length = 2; if (!isObject(object)) { return object; } - if (deepIndicator === indicatorObject) { - var callback = args[3], - stackA = args[4], - stackB = args[5]; - } else { - var initedStack = true; - stackA = getArray(); - stackB = getArray(); - - // allows working with `_.reduce` and `_.reduceRight` without using - // their `index` and `collection` arguments - if (typeof deepIndicator != 'number') { - length = args.length; - } - if (length > 3 && typeof args[length - 2] == 'function') { - callback = lodash.createCallback(args[--length - 1], args[length--], 2); - } else if (length > 2 && typeof args[length - 1] == 'function') { - callback = args[--length]; - } + // allows working with `_.reduce` and `_.reduceRight` without using + // their `index` and `collection` arguments + if (typeof args[2] != 'number') { + length = args.length; } + if (length > 3 && typeof args[length - 2] == 'function') { + var callback = lodash.createCallback(args[--length - 1], args[length--], 2); + } else if (length > 2 && typeof args[length - 1] == 'function') { + callback = args[--length]; + } + var sources = nativeSlice.call(arguments, 1, length), + index = -1, + stackA = getArray(), + stackB = getArray(); + while (++index < length) { - (isArray(args[index]) ? forEach : forOwn)(args[index], function(source, key) { - var found, - isArr, - result = source, - value = object[key]; - - if (source && ((isArr = isArray(source)) || isPlainObject(source))) { - // avoid merging previously merged cyclic sources - var stackLength = stackA.length; - while (stackLength--) { - if ((found = stackA[stackLength] == source)) { - value = stackB[stackLength]; - break; - } - } - if (!found) { - var isShallow; - if (callback) { - result = callback(value, source); - if ((isShallow = typeof result != 'undefined')) { - value = result; - } - } - if (!isShallow) { - value = isArr - ? (isArray(value) ? value : []) - : (isPlainObject(value) ? value : {}); - } - // add `source` and associated `value` to the stack of traversed objects - stackA.push(source); - stackB.push(value); - - // recursively merge objects and arrays (susceptible to call stack limits) - if (!isShallow) { - value = merge(value, source, indicatorObject, callback, stackA, stackB); - } - } - } - else { - if (callback) { - result = callback(value, source); - if (typeof result == 'undefined') { - result = source; - } - } - if (typeof result != 'undefined') { - value = result; - } - } - object[key] = value; - }); - } - - if (initedStack) { - releaseArray(stackA); - releaseArray(stackB); + baseMerge(object, sources[index], callback, stackA, stackB); } + releaseArray(stackA); + releaseArray(stackB); return object; } @@ -2519,7 +2561,7 @@ if (isFunc) { callback = lodash.createCallback(callback, thisArg); } else { - var props = basicFlatten(arguments, true, false, 1); + var props = baseFlatten(arguments, true, false, 1); } forIn(object, function(value, key, object) { if (isFunc @@ -2589,7 +2631,7 @@ var result = {}; if (typeof callback != 'function') { var index = -1, - props = basicFlatten(arguments, true, false, 1), + props = baseFlatten(arguments, true, false, 1), length = isObject(object) ? props.length : 0; while (++index < length) { @@ -2654,7 +2696,7 @@ accumulator = createObject(proto); } } - (isArr ? basicEach : forOwn)(object, function(value, index, object) { + (isArr ? baseEach : forOwn)(object, function(value, index, object) { return callback(accumulator, value, index, object); }); return accumulator; @@ -2710,7 +2752,7 @@ */ function at(collection) { var index = -1, - props = basicFlatten(arguments, true, false, 1), + props = baseFlatten(arguments, true, false, 1), length = props.length, result = Array(length); @@ -2763,7 +2805,7 @@ : indexOf(collection, target, fromIndex) ) > -1; } else { - basicEach(collection, function(value) { + baseEach(collection, function(value) { if (++index >= fromIndex) { return !(result = value === target); } @@ -2871,7 +2913,7 @@ } } } else { - basicEach(collection, function(value, index, collection) { + baseEach(collection, function(value, index, collection) { return (result = !!callback(value, index, collection)); }); } @@ -2933,7 +2975,7 @@ } } } else { - basicEach(collection, function(value, index, collection) { + baseEach(collection, function(value, index, collection) { if (callback(value, index, collection)) { result.push(value); } @@ -3000,7 +3042,7 @@ } } else { var result; - basicEach(collection, function(value, index, collection) { + baseEach(collection, function(value, index, collection) { if (callback(value, index, collection)) { result = value; return false; @@ -3043,7 +3085,7 @@ } } } else { - basicEach(collection, callback, thisArg); + baseEach(collection, callback, thisArg); } return collection; } @@ -3178,7 +3220,7 @@ result[index] = callback(collection[index], index, collection); } } else { - basicEach(collection, function(value, key, collection) { + baseEach(collection, function(value, key, collection) { result[++index] = callback(value, key, collection); }); } @@ -3243,7 +3285,7 @@ ? charAtCallback : lodash.createCallback(callback, thisArg); - basicEach(collection, function(value, index, collection) { + baseEach(collection, function(value, index, collection) { var current = callback(value, index, collection); if (current > computed) { computed = current; @@ -3312,7 +3354,7 @@ ? charAtCallback : lodash.createCallback(callback, thisArg); - basicEach(collection, function(value, index, collection) { + baseEach(collection, function(value, index, collection) { var current = callback(value, index, collection); if (current < computed) { computed = current; @@ -3390,7 +3432,7 @@ accumulator = callback(accumulator, collection[index], index, collection); } } else { - basicEach(collection, function(value, index, collection) { + baseEach(collection, function(value, index, collection) { accumulator = noaccum ? (noaccum = false, value) : callback(accumulator, value, index, collection) @@ -3593,7 +3635,7 @@ } } } else { - basicEach(collection, function(value, index, collection) { + baseEach(collection, function(value, index, collection) { return !(result = callback(value, index, collection)); }); } @@ -3756,10 +3798,10 @@ var index = -1, indexOf = getIndexOf(), length = array ? array.length : 0, - seen = basicFlatten(arguments, true, true, 1), + seen = baseFlatten(arguments, true, true, 1), result = []; - var isLarge = length >= largeArraySize && indexOf === basicIndexOf; + var isLarge = length >= largeArraySize && indexOf === baseIndexOf; if (isLarge) { var cache = createCache(seen); @@ -3944,7 +3986,7 @@ if (callback != null) { array = map(array, callback, thisArg); } - return basicFlatten(array, isShallow); + return baseFlatten(array, isShallow); } /** @@ -3979,7 +4021,7 @@ var index = sortedIndex(array, value); return array[index] === value ? index : -1; } - return array ? basicIndexOf(array, value, fromIndex) : -1; + return array ? baseIndexOf(array, value, fromIndex) : -1; } /** @@ -4084,7 +4126,7 @@ while (++argsIndex < argsLength) { var value = args[argsIndex]; - caches[argsIndex] = indexOf === basicIndexOf && + caches[argsIndex] = indexOf === baseIndexOf && (value ? value.length : 0) >= largeArraySize && createCache(argsIndex ? args[argsIndex] : seen); } @@ -4430,7 +4472,7 @@ * // => [1, 2, 3, 101, 10] */ function union(array) { - return basicUniq(basicFlatten(arguments, true, true)); + return baseUniq(baseFlatten(arguments, true, true)); } /** @@ -4486,7 +4528,7 @@ if (callback != null) { callback = lodash.createCallback(callback, thisArg); } - return basicUniq(array, isSorted, callback); + return baseUniq(array, isSorted, callback); } /** @@ -4622,11 +4664,7 @@ * // => 'hi moe' */ function bind(func, thisArg) { - // use `Function#bind` if it exists and is fast - // (in V8 `Function#bind` is slower except when partially applied) - return support.fastBind || (nativeBind && arguments.length > 2) - ? nativeBind.call.apply(nativeBind, arguments) - : createBound(func, thisArg, nativeSlice.call(arguments, 2)); + return createBound(func, thisArg, nativeSlice.call(arguments, 2), []); } /** @@ -4654,7 +4692,7 @@ * // => alerts 'clicked docs', when the button is clicked */ function bindAll(object) { - var funcs = arguments.length > 1 ? basicFlatten(arguments, true, false, 1) : functions(object), + var funcs = arguments.length > 1 ? baseFlatten(arguments, true, false, 1) : functions(object), index = -1, length = funcs.length; @@ -4700,7 +4738,7 @@ * // => 'hi, moe!' */ function bindKey(object, key) { - return createBound(object, key, nativeSlice.call(arguments, 2), indicatorObject); + return createBound(object, key, nativeSlice.call(arguments, 2), [], false, true); } /** @@ -4807,7 +4845,7 @@ result = false; while (length--) { - if (!(result = isEqual(object[props[length]], func[props[length]], indicatorObject))) { + if (!(result = baseIsEqual(object[props[length]], func[props[length]], null, true))) { break; } } @@ -5090,7 +5128,7 @@ * // => 'hi moe' */ function partial(func) { - return createBound(func, nativeSlice.call(arguments, 1)); + return createBound(func, null, nativeSlice.call(arguments, 1), [], true); } /** @@ -5121,7 +5159,7 @@ * // => { '_': _, 'jq': $ } */ function partialRight(func) { - return createBound(func, nativeSlice.call(arguments, 1), null, indicatorObject); + return createBound(func, null, [], nativeSlice.call(arguments, 1), true, true); } /** @@ -5904,7 +5942,7 @@ lodash.prototype.valueOf = wrapperValueOf; // add `Array` functions that return unwrapped values - basicEach(['join', 'pop', 'shift'], function(methodName) { + baseEach(['join', 'pop', 'shift'], function(methodName) { var func = arrayRef[methodName]; lodash.prototype[methodName] = function() { return func.apply(this.__wrapped__, arguments); @@ -5912,7 +5950,7 @@ }); // add `Array` functions that return the wrapped value - basicEach(['push', 'reverse', 'sort', 'unshift'], function(methodName) { + baseEach(['push', 'reverse', 'sort', 'unshift'], function(methodName) { var func = arrayRef[methodName]; lodash.prototype[methodName] = function() { func.apply(this.__wrapped__, arguments); @@ -5921,7 +5959,7 @@ }); // add `Array` functions that return new wrapped values - basicEach(['concat', 'slice', 'splice'], function(methodName) { + baseEach(['concat', 'slice', 'splice'], function(methodName) { var func = arrayRef[methodName]; lodash.prototype[methodName] = function() { return new lodashWrapper(func.apply(this.__wrapped__, arguments)); @@ -5931,7 +5969,7 @@ // avoid array-like object bugs with `Array#shift` and `Array#splice` // in Firefox < 10 and IE < 9 if (!support.spliceObjects) { - basicEach(['pop', 'shift', 'splice'], function(methodName) { + baseEach(['pop', 'shift', 'splice'], function(methodName) { var func = arrayRef[methodName], isSplice = methodName == 'splice'; @@ -5948,7 +5986,7 @@ } // add pseudo private property to be used and removed during the build process - lodash._basicEach = basicEach; + lodash._baseEach = baseEach; lodash._iteratorTemplate = iteratorTemplate; lodash._shimKeys = shimKeys; diff --git a/test/test.js b/test/test.js index 704d9bfeb..15c1b3620 100644 --- a/test/test.js +++ b/test/test.js @@ -3836,6 +3836,7 @@ 'after', 'bind', 'bindAll', + 'bindKey', 'compose', 'debounce', 'defer',