From 94ca50883f7d94d61e037587a3547869d785c76c Mon Sep 17 00:00:00 2001 From: jdalton Date: Thu, 19 Mar 2015 08:37:22 -0700 Subject: [PATCH] Drop `funcDecomp` optimization in `baseCallback` and ensure shortcut fusion for `_.flow` and `_.flowRight` works in minified builds. --- lodash.src.js | 116 ++++++++++++++++++++++++++++---------------------- test/test.js | 49 +-------------------- 2 files changed, 67 insertions(+), 98 deletions(-) diff --git a/lodash.src.js b/lodash.src.js index 16836e55a..5aba9004b 100644 --- a/lodash.src.js +++ b/lodash.src.js @@ -120,9 +120,6 @@ var reRegExpChars = /[.*+?^${}()|[\]\/\\]/g, reHasRegExpChars = RegExp(reRegExpChars.source); - /** Used to detect functions containing a `this` reference. */ - var reThis = /\bthis\b/; - /** Used to match unescaped characters in compiled string literals. */ var reUnescapedString = /['\n\r\u2028\u2029\\]/g; @@ -153,7 +150,7 @@ 'Object', 'RegExp', 'Set', 'String', '_', 'clearTimeout', 'document', 'isFinite', 'parseInt', 'setTimeout', 'TypeError', 'Uint8Array', 'Uint8ClampedArray', 'Uint16Array', 'Uint32Array', 'WeakMap', - 'window', 'WinRTError' + 'window' ]; /** Used to fix the JScript `[[DontEnum]]` bug. */ @@ -831,6 +828,9 @@ /** Used to store function metadata. */ var metaMap = WeakMap && new WeakMap; + /** Used to lookup unminified function names. */ + var realNames = {}; + /** Used to lookup a type array constructors by `toStringTag`. */ var ctorByTag = {}; ctorByTag[float32Tag] = context.Float32Array; @@ -1046,7 +1046,7 @@ * @memberOf _.support * @type boolean */ - support.funcDecomp = !isNative(context.WinRTError) && reThis.test(runInContext); + support.funcDecomp = /\bthis\b/.test(function() { return this; }); /** * Detect if `Function#name` is supported (all but IE). @@ -1846,9 +1846,9 @@ function baseCallback(func, thisArg, argCount) { var type = typeof func; if (type == 'function') { - return (typeof thisArg != 'undefined' && isBindable(func)) - ? bindCallback(func, thisArg, argCount) - : func; + return typeof thisArg == 'undefined' + ? func + : bindCallback(func, thisArg, argCount); } if (func == null) { return identity; @@ -3379,29 +3379,37 @@ if (!length) { return function() { return arguments[0]; }; } - var index = fromRight ? length : -1, + var wrapper, + index = fromRight ? length : -1, leftIndex = 0, - funcs = Array(length), - wrapper = new LodashWrapper([]); + funcs = Array(length); while ((fromRight ? index-- : ++index < length)) { - var func = arguments[index]; + var func = funcs[leftIndex++] = arguments[index]; if (typeof func != 'function') { throw new TypeError(FUNC_ERROR_TEXT); } - var data = func.name === 'wrapper' && getData(func); - if (data && data !== true && isLaziable(data[0])) { - wrapper = wrapper[data[0].name].apply(wrapper, data[3]); - } else if (func.length == 1 && isLaziable(func)) { - wrapper = wrapper[func.name](); - } else { - wrapper = wrapper.thru(func); + var funcName = wrapper ? null : getFuncName(func); + if (funcName && (funcName == 'wrapper' || isLaziable(func))) { + wrapper = new LodashWrapper([]); + } + } + index = wrapper ? -1 : length; + while (++index < length) { + func = funcs[index]; + + var funcName = getFuncName(func), + data = funcName == 'wrapper' ? getData(func) : null; + + if (data && isLaziable(data[0])) { + wrapper = wrapper[getFuncName(data[0])].apply(wrapper, data[3]); + } else { + wrapper = (func.length == 1 && isLaziable(func)) ? wrapper[funcName]() : wrapper.thru(func); } - funcs[leftIndex++] = func; } return function() { var args = arguments; - if (args.length == 1 && isArray(args[0])) { + if (wrapper && args.length == 1 && isArray(args[0])) { return wrapper.plant(args[0]).value(); } var index = 0, @@ -3699,10 +3707,10 @@ partials = holders = null; } - var data = !isBindKey && getData(func), + var data = isBindKey ? null : getData(func), newData = [func, bitmask, thisArg, partials, holders, partialsRight, holdersRight, argPos, ary, arity]; - if (data && data !== true) { + if (data) { mergeData(newData, data); bitmask = newData[1]; arity = newData[9]; @@ -4055,31 +4063,6 @@ return result; } - /** - * Checks if `func` is eligible for `this` binding. - * - * @private - * @param {Function} func The function to check. - * @returns {boolean} Returns `true` if `func` is eligible, else `false`. - */ - function isBindable(func) { - var support = lodash.support, - result = !(support.funcNames ? func.name : support.funcDecomp); - - if (!result) { - var source = fnToString.call(func); - if (!support.funcNames) { - result = !reFuncName.test(source); - } - if (!result) { - // Check if `func` references the `this` keyword and store the result. - result = reThis.test(source) || isNative(func); - baseSetData(func, result); - } - } - return result; - } - /** * Checks if `value` is a valid array-like index. * @@ -4121,9 +4104,28 @@ return false; } + var getFuncName = !support.funcNames ? constant('') : function(func) { + var result = func.name; + if (result) { + return realNames[result] || result; + } + var anons = realNames[result]; + if (!anons) { + return result; + } + var length = anons.length; + while (length--) { + var anon = anons[length]; + if (anon.func == func) { + return anon.name; + } + } + return result; + }; + function isLaziable(func) { - var funcName = support.funcNames ? func.name : ''; - return (funcName && func === lodash[funcName] && funcName in LazyWrapper.prototype) || false; + var funcName = getFuncName(func); + return funcName ? func === lodash[funcName] : false; } /** @@ -11942,6 +11944,20 @@ }; }); + if (support.funcNames) { + realNames[createHybridWrapper(null, BIND_KEY_FLAG).name] = 'wrapper'; + baseForOwn(LazyWrapper.prototype, function(func, methodName) { + var lodashFunc = lodash[methodName], + key = lodashFunc.name; + + if (key) { + realNames[key] = methodName; + } else { + var anons = realNames[key] || (realNames[key] = []); + anons.push({ 'name': methodName, 'func': lodashFunc }); + } + }); + } // Add functions to the lazy wrapper. LazyWrapper.prototype.clone = lazyClone; LazyWrapper.prototype.reverse = lazyReverse; diff --git a/test/test.js b/test/test.js index d317bd24b..3cbea52a8 100644 --- a/test/test.js +++ b/test/test.js @@ -2233,7 +2233,7 @@ }); test('`_.' + methodName + '` should support shortcut fusion', 3, function() { - if (!isNpm) { + if (!isNpm && _.support.funcNames) { var filterCount = 0, mapCount = 0; @@ -2625,53 +2625,6 @@ } }); - test('should return the function provided when there is no `this` reference', 2, function() { - function a() {} - function b() { return this.b; } - - var object = {}; - - if (_.support.funcDecomp) { - strictEqual(_.callback(a, object), a); - notStrictEqual(_.callback(b, object), b); - } - else { - skipTest(2); - } - }); - - test('should work with bizarro `_.support.funcNames`', 6, function() { - function a() {} - - var b = function() {}; - - function c() { - return this; - } - - var object = {}, - supportBizarro = lodashBizarro ? lodashBizarro.support : {}, - funcDecomp = supportBizarro.funcDecomp, - funcNames = supportBizarro.funcNames; - - supportBizarro.funcNames = !supportBizarro.funcNames; - supportBizarro.funcDecomp = true; - - _.each([a, b, c], function(fn) { - if (lodashBizarro && _.support.funcDecomp) { - var callback = lodashBizarro.callback(fn, object); - strictEqual(callback(), fn === c ? object : undefined); - strictEqual(callback === fn, _.support.funcNames && fn === a); - } - else { - skipTest(2); - } - }); - - supportBizarro.funcDecomp = funcDecomp; - supportBizarro.funcNames = funcNames; - }); - test('should work as an iteratee for methods like `_.map`', 1, function() { var fn = function() { return this instanceof Number; }, array = [fn, fn, fn],