Ensure objects work in a lazy chain sequence. [closes #796]

This commit is contained in:
John-David Dalton
2014-11-28 00:02:59 -08:00
parent 5cce5b6707
commit fb9118a044
2 changed files with 147 additions and 49 deletions

105
lodash.js
View File

@@ -1070,7 +1070,7 @@
return value;
}
if (hasOwnProperty.call(value, '__wrapped__')) {
return new LodashWrapper(value.__wrapped__, value.__chain__, baseSlice(value.__queue__));
return new LodashWrapper(value.__wrapped__, value.__chain__, baseSlice(value.__actions__));
}
}
return new LodashWrapper(value);
@@ -1082,11 +1082,11 @@
* @private
* @param {*} value The value to wrap.
* @param {boolean} [chainAll=false] Enable chaining for all wrapper methods.
* @param {Array} [queue=[]] Actions to peform to resolve the unwrapped value.
* @param {Array} [actions=[]] Actions to peform to resolve the unwrapped value.
*/
function LodashWrapper(value, chainAll, queue) {
function LodashWrapper(value, chainAll, actions) {
this.__actions__ = actions || [];
this.__chain__ = !!chainAll;
this.__queue__ = queue || [];
this.__wrapped__ = value;
}
@@ -1321,6 +1321,7 @@
* @param {*} value The value to wrap.
*/
function LazyWrapper(value) {
this.actions = null;
this.dir = 1;
this.dropCount = 0;
this.filtered = false;
@@ -1339,10 +1340,12 @@
* @returns {Object} Returns the cloned `LazyWrapper` object.
*/
function lazyClone() {
var iteratees = this.iteratees,
var actions = this.actions,
iteratees = this.iteratees,
views = this.views,
result = new LazyWrapper(this.wrapped);
result.actions = actions ? baseSlice(actions) : null;
result.dir = this.dir;
result.dropCount = this.dropCount;
result.filtered = this.filtered;
@@ -1378,8 +1381,11 @@
* @returns {*} Returns the unwrapped value.
*/
function lazyValue() {
var array = this.wrapped.value(),
dir = this.dir,
var array = this.wrapped.value();
if (!isArray(array)) {
return baseWrapperValue(array, this.actions);
}
var dir = this.dir,
isRight = dir < 0,
length = array.length,
view = getView(0, length, this.views),
@@ -2658,6 +2664,35 @@
return result;
}
/**
* The base implementation of `wrapperValue` which returns the result of
* performing a sequence of actions on the unwrapped `value`, where each
* successive action is supplied the return value of the previous.
*
* @private
* @param {*} value The unwrapped value.
* @param {Array} actions Actions to peform to resolve the unwrapped value.
* @returns {*} Returns the resolved unwrapped value.
*/
function baseWrapperValue(value, actions) {
var result = value;
if (result instanceof LazyWrapper) {
result = result.value();
}
var index = -1,
length = actions.length;
while (++index < length) {
var args = [result],
action = actions[index],
object = action.object;
push.apply(args, action.args);
result = object[action.name].apply(object, args);
}
return result;
}
/**
* Creates a clone of the given array buffer.
*
@@ -5026,34 +5061,18 @@
/**
* Extracts the unwrapped value from its wrapper.
*
* @name valueOf
* @name value
* @memberOf _
* @alias toJSON, value
* @alias toJSON, valueOf
* @category Chain
* @returns {*} Returns the unwrapped value.
* @returns {*} Returns the resolved unwrapped value.
* @example
*
* _([1, 2, 3]).valueOf();
* _([1, 2, 3]).value();
* // => [1, 2, 3]
*/
function wrapperValueOf() {
var result = this.__wrapped__;
if (result instanceof LazyWrapper) {
result = result.value();
}
var index = -1,
queue = this.__queue__,
length = queue.length;
while (++index < length) {
var args = [result],
data = queue[index],
object = data.object;
push.apply(args, data.args);
result = object[data.name].apply(object, args);
}
return result;
function wrapperValue() {
return baseWrapperValue(this.__wrapped__, this.__actions__);
}
/*------------------------------------------------------------------------*/
@@ -6695,9 +6714,9 @@
}
/**
* Creates a function that invokes the provided functions with the `this`
* binding of the created function, where each successive invocation is
* supplied the return value of the previous.
* Creates a function that returns the result of invoking the provided
* functions with the `this` binding of the created function, where each
* successive invocation is supplied the return value of the previous.
*
* @static
* @memberOf _
@@ -9560,8 +9579,8 @@
var chainAll = this.__chain__;
if (chain || chainAll) {
var result = object(this.__wrapped__);
(result.__actions__ = baseSlice(this.__actions__)).push({ 'args': arguments, 'object': object, 'name': methodName });
result.__chain__ = chainAll;
(result.__queue__ = baseSlice(this.__queue__)).push({ 'args': arguments, 'object': object, 'name': methodName });
return result;
}
var args = [this.value()];
@@ -10316,25 +10335,31 @@
var value = this.__wrapped__,
args = arguments,
chainAll = this.__chain__,
isHybrid = !!this.__actions__.length,
isLazy = value instanceof LazyWrapper,
onlyLazy = isLazy && !this.__queue__.length;
onlyLazy = isLazy && !isHybrid;
if (retUnwrapped && !chainAll) {
return onlyLazy
? func.call(value)
: lodash[methodName](this.value());
}
var interceptor = function(value) {
var otherArgs = [value];
push.apply(otherArgs, args);
return lodash[methodName].apply(lodash, otherArgs);
};
if (isLazy || isArray(value)) {
var wrapper = onlyLazy ? value : new LazyWrapper(this),
result = func.apply(wrapper, args);
if (!retUnwrapped && (isHybrid || result.actions)) {
var actions = result.actions || (result.actions = []);
actions.push({ 'args': [interceptor], 'object': lodash, 'name': 'thru' });
}
return new LodashWrapper(result, chainAll);
}
return this.thru(function(value) {
var otherArgs = [value];
push.apply(otherArgs, args);
return lodash[methodName].apply(lodash, otherArgs);
});
return this.thru(interceptor);
};
});
@@ -10375,7 +10400,7 @@
lodash.prototype.chain = wrapperChain;
lodash.prototype.reverse = wrapperReverse;
lodash.prototype.toString = wrapperToString;
lodash.prototype.toJSON = lodash.prototype.value = lodash.prototype.valueOf = wrapperValueOf;
lodash.prototype.toJSON = lodash.prototype.valueOf = lodash.prototype.value = wrapperValue;
// Add function aliases to the lodash wrapper.
lodash.prototype.collect = lodash.prototype.map;

View File

@@ -293,17 +293,17 @@
*/
function getUnwrappedValue(wrapper) {
var index = -1,
queue = wrapper.__queue__,
length = queue.length,
actions = wrapper.__actions__,
length = actions.length,
result = wrapper.__wrapped__;
while (++index < length) {
var args = [result],
data = queue[index],
object = data.object;
action = actions[index],
object = action.object;
push.apply(args, data.args);
result = object[data.name].apply(object, args);
push.apply(args, action.args);
result = object[action.name].apply(object, args);
}
return result;
}
@@ -2099,6 +2099,19 @@
deepEqual(_.countBy(array, 0), { '1': 1, '2': 2 });
deepEqual(_.countBy(array, 1), { 'a': 2, 'b': 1 });
});
test('should work in a lazy chain sequence', 1, function() {
if (!isNpm) {
var array = [1, 2, 1, 3],
predicate = function(value) { return value > 1; },
actual = _(array).countBy(_.identity).map(String).filter(predicate).take().value();
deepEqual(actual, ['2']);
}
else {
skipTest();
}
});
}());
/*--------------------------------------------------------------------------*/
@@ -4865,6 +4878,20 @@
var actual = _.groupBy(['one', 'two', 'three'], 'length');
deepEqual(actual, { '3': ['one', 'two'], '5': ['three'] });
});
test('should work in a lazy chain sequence', 1, function() {
if (!isNpm) {
var array = [1, 2, 1, 3],
iteratee = function(value) { value.push(value[0]); return value; },
predicate = function(value, index) { return index; },
actual = _(array).groupBy(_.identity).map(iteratee).filter(predicate).take().value();
deepEqual(actual, [[2, 2]]);
}
else {
skipTest();
}
});
}());
/*--------------------------------------------------------------------------*/
@@ -5064,6 +5091,19 @@
deepEqual(_.indexBy(array, 0), { '1': [1 , 'a'], '2': [2, 'b'] });
deepEqual(_.indexBy(array, 1), { 'a': [2, 'a'], 'b': [2, 'b'] });
});
test('should work in a lazy chain sequence', 1, function() {
if (!isNpm) {
var array = [1, 2, 1, 3],
predicate = function(value) { return value > 1; },
actual = _(array).indexBy(_.identity).map(String).filter(predicate).take().value();
deepEqual(actual, ['2']);
}
else {
skipTest();
}
});
}());
/*--------------------------------------------------------------------------*/
@@ -8263,13 +8303,13 @@
return new Wrapper(value);
}
if (_.has(value, '__wrapped__')) {
var chain = value.__chain__,
queue = _.slice(value.__queue__);
var actions = _.slice(value.__actions__),
chain = value.__chain__;
value = value.__wrapped__;
}
this.__actions__ = actions || [];
this.__chain__ = chain || false;
this.__queue__ = queue || [];
this.__wrapped__ = value;
}
@@ -8435,6 +8475,26 @@
skipTest(2);
}
});
test('should produce methods that work in a lazy chain sequence', 1, function() {
if (!isNpm) {
var array = [1, 2, 1, 3],
predicate = function(value) { return value > 1; };
_.mixin({ 'a': _.countBy, 'b': _.filter });
var actual = _(array).a(_.identity).map(String).b(predicate).take().value();
deepEqual(actual, ['2']);
delete _.a;
delete _.prototype.a;
delete _.b;
delete _.prototype.b;
}
else {
skipTest();
}
});
}());
/*--------------------------------------------------------------------------*/
@@ -12914,6 +12974,19 @@
deepEqual(_.zipObject(_.pairs(object)), object);
});
test('should work in a lazy chain sequence', 1, function() {
if (!isNpm) {
var array = [['a', 1], ['b', 2]],
predicate = function(value) { return value > 1; },
actual = _(array).zipObject().map(String).filter(predicate).take().value();
deepEqual(actual, ['2']);
}
else {
skipTest();
}
});
test('should be aliased', 1, function() {
strictEqual(_.object, _.zipObject);
});