From 0717da6e3799e23b11e35d9afda5cc5995e733d3 Mon Sep 17 00:00:00 2001 From: John-David Dalton Date: Sun, 7 Sep 2014 17:22:24 -0700 Subject: [PATCH] Make chaining actions deferred until `value` is called. --- lodash.js | 105 +++++++++++++++++++++++++++++---------------------- test/test.js | 36 ++++++++++++------ 2 files changed, 83 insertions(+), 58 deletions(-) diff --git a/lodash.js b/lodash.js index 764d1ae2e..583d29f82 100644 --- a/lodash.js +++ b/lodash.js @@ -841,7 +841,7 @@ return value; } if (!isArray(value) && hasOwnProperty.call(value, '__wrapped__')) { - value = value.__wrapped__; + return new lodashWrapper(value.__wrapped__, value.__chain__, baseSlice(value.__queue__)); } } return new lodashWrapper(value); @@ -853,10 +853,12 @@ * @private * @param {*} value The value to wrap in a `lodash` instance. * @param {boolean} [chainAll=false] Enable chaining for all methods. + * @param {Array} [queue=[]] Actions to peform to resolve the unwrapped value. * @returns {Object} Returns a `lodash` instance. */ - function lodashWrapper(value, chainAll) { + function lodashWrapper(value, chainAll, queue) { this.__chain__ = !!chainAll; + this.__queue__ = queue || []; this.__wrapped__ = value; } @@ -1875,7 +1877,7 @@ othWrapped = othIsObj && hasOwnProperty.call(other, '__wrapped__'); if (valWrapped || othWrapped) { - return baseIsEqual(valWrapped ? value.__wrapped__ : value, othWrapped ? other.__wrapped__ : other, customizer, isWhere, stackA, stackB); + return baseIsEqual(valWrapped ? value.value() : value, othWrapped ? other.value() : other, customizer, isWhere, stackA, stackB); } if (!isSameClass) { return false; @@ -4525,8 +4527,7 @@ * // => { 'age': 36 } */ function wrapperChain() { - this.__chain__ = true; - return this; + return chain(this); } /** @@ -4542,7 +4543,7 @@ * // => '1,2,3' */ function wrapperToString() { - return String(this.__wrapped__); + return String(this.value()); } /** @@ -4559,7 +4560,26 @@ * // => [1, 2, 3] */ function wrapperValueOf() { - return this.__wrapped__; + var index = -1, + queue = this.__queue__, + length = queue.length, + result = this.__wrapped__; + + while (++index < length) { + var data = queue[index], + methodName = data[0]; + + if (typeof methodName == 'function') { + result = methodName.apply(result, data[2]); + } else { + var args = [result], + object = data[1]; + + push.apply(args, data[2]); + result = object[methodName].apply(object, args); + } + } + return result; } /*------------------------------------------------------------------------*/ @@ -8936,28 +8956,25 @@ length = methodNames.length; while (++index < length) { - var methodName = methodNames[index], - func = object[methodName] = source[methodName]; - + var methodName = methodNames[index]; + object[methodName] = source[methodName]; if (isFunc) { - object.prototype[methodName] = (function(func) { + object.prototype[methodName] = (function(methodName) { return function() { - var chainAll = this.__chain__, - value = this.__wrapped__, - args = [value]; + if (chain || this.__chain__) { + var queue = baseSlice(this.__queue__), + result = object(this.__wrapped__); - push.apply(args, arguments); - var result = func.apply(object, args); - if (chain || chainAll) { - if (value === result && isObject(result)) { - return this; - } - result = new object(result); - result.__chain__ = chainAll; + result.__chain__ = this.__chain__; + result.__queue__ = queue; + queue.push([methodName, object, arguments]); + return result; } - return result; + var args = [this.value()]; + push.apply(args, arguments); + return object[methodName].apply(object, args); }; - }(func)); + }(methodName)); } } return object; @@ -9515,19 +9532,13 @@ // add functions capable of returning wrapped and unwrapped values when chaining lodash.sample = sample; - baseForOwn(lodash, function(func, methodName) { - var callbackable = methodName != 'sample'; - if (!lodash.prototype[methodName]) { - lodash.prototype[methodName] = function(n, guard) { - var chainAll = this.__chain__, - result = func(this.__wrapped__, n, guard); - - return !chainAll && (n == null || (guard && !(callbackable && typeof n == 'function'))) - ? result - : new lodashWrapper(result, chainAll); - }; + lodash.prototype.sample = function(n, guard) { + if (!this.__chain__ && (n == null || guard)) { + return lodash.sample(this.value()); } - }); + this.__queue__.push(['sample', lodash, [n]]); + return this; + }; /*------------------------------------------------------------------------*/ @@ -9557,12 +9568,12 @@ arrayEach(['join', 'pop', 'shift'], function(methodName) { var func = arrayProto[methodName]; lodash.prototype[methodName] = function() { - var chainAll = this.__chain__, - result = func.apply(this.__wrapped__, arguments); - - return chainAll - ? new lodashWrapper(result, chainAll) - : result; + if (!this.__chain__) { + return func.apply(this.value(), arguments) + } + var result = new lodashWrapper(value.__wrapped__, value.__chain__, baseSlice(value.__queue__)) + result.__queue__.push([func, null, arguments]); + return result; }; }); @@ -9570,8 +9581,10 @@ arrayEach(['push', 'reverse', 'sort', 'unshift'], function(methodName) { var func = arrayProto[methodName]; lodash.prototype[methodName] = function() { - func.apply(this.__wrapped__, arguments); - return this; + var args = arguments; + return this.tap(function(value) { + func.apply(value, args); + }); }; }); @@ -9579,7 +9592,7 @@ arrayEach(['concat', 'splice'], function(methodName) { var func = arrayProto[methodName]; lodash.prototype[methodName] = function() { - return new lodashWrapper(func.apply(this.__wrapped__, arguments), this.__chain__); + return new lodashWrapper(func.apply(this.value(), arguments), this.__chain__); }; }); @@ -9592,7 +9605,7 @@ lodash.prototype[methodName] = function() { var chainAll = this.__chain__, - value = this.__wrapped__, + value = this.value(), result = func.apply(value, arguments); if (value.length === 0) { diff --git a/test/test.js b/test/test.js index fb47a4c54..82fdd4d96 100644 --- a/test/test.js +++ b/test/test.js @@ -754,7 +754,7 @@ if (lodashBizarro) { var actual = _.map(values, function(value) { var wrapped = _(lodashBizarro(value)), - unwrapped = wrapped.__wrapped__; + unwrapped = wrapped.value(); return wrapped instanceof _ && (unwrapped === value || (_.isNaN(unwrapped) && _.isNaN(value))); @@ -4160,10 +4160,10 @@ strictEqual(func(array, Boolean), array); }); - test('`_.' + methodName + '` should return the existing wrapped value when chaining', 1, function() { + test('`_.' + methodName + '` should not return the existing wrapped value when chaining', 1, function() { if (!isNpm) { var wrapped = _(array); - strictEqual(wrapped[methodName](_.noop), wrapped); + notStrictEqual(wrapped[methodName](_.noop), wrapped); } else { skipTest(); @@ -4352,10 +4352,10 @@ deepEqual(_.reduce(array, func, { 'a': 1}), { 'a': 1, 'b': 2, 'c': 3 }); }); - test('`_.' + methodName + '` should return the existing wrapped value when chaining', 1, function() { + test('`_.' + methodName + '` should not return the existing wrapped value when chaining', 1, function() { if (!isNpm) { var wrapped = _({ 'a': 1 }); - strictEqual(wrapped[methodName]({ 'b': 2 }), wrapped); + notStrictEqual(wrapped[methodName]({ 'b': 2 }), wrapped); } else { skipTest(); @@ -7681,9 +7681,19 @@ if (!(this instanceof wrapper)) { return new wrapper(value); } + if (_.has(value, '__wrapped__')) { + var chain = value.__chain__, + queue = _.slice(value.__queue__); + + value = value.__wrapped__; + } + this.__chain__ = chain || false; + this.__queue__ = queue || []; this.__wrapped__ = value; } + wrapper.prototype.value = _.prototype.value; + var value = ['a'], source = { 'a': function(array) { return array[0]; }, 'b': 'B' }; @@ -7691,7 +7701,7 @@ _.mixin(source); strictEqual(_.a(value), 'a'); - strictEqual(_(value).a().__wrapped__, 'a'); + strictEqual(_(value).a().value(), 'a'); delete _.a; delete _.prototype.a; @@ -7736,7 +7746,7 @@ var wrapped = wrapper(value), actual = wrapped.a(); - strictEqual(actual.__wrapped__, 'a'); + strictEqual(actual.value(), 'a'); ok(actual instanceof wrapper); delete wrapper.a; @@ -7768,7 +7778,7 @@ actual = wrapped.a(); if (options === true || (options && options.chain)) { - strictEqual(actual.__wrapped__, 'a', message(func, true)); + strictEqual(actual.value(), 'a', message(func, true)); ok(actual instanceof func, message(func, true)); } else { strictEqual(actual, 'a', message(func, false)); @@ -7813,7 +7823,7 @@ ok(pass); }); - test('should return the existing wrapped value when chaining', 2, function() { + test('should not return the existing wrapped value when chaining', 2, function() { if (!isNpm) { _.each([_, wrapper], function(func) { if (func === _) { @@ -7825,7 +7835,7 @@ else { wrapped = _(func); actual = wrapped.mixin(source); - strictEqual(actual, wrapped); + notStrictEqual(actual, wrapped); } delete func.a; delete func.prototype.a; @@ -10175,6 +10185,8 @@ }); ok(actual instanceof _); + + actual.value(); strictEqual(intercepted, array); } else { @@ -11888,9 +11900,9 @@ ]; _.each(funcs, function(methodName) { - test('`_(...).' + methodName + '` should return the existing wrapped value', 1, function() { + test('`_(...).' + methodName + '` should not return the existing wrapped value', 1, function() { if (!isNpm) { - strictEqual(wrapped[methodName](), wrapped); + notStrictEqual(wrapped[methodName](), wrapped); } else { skipTest();