Skip to content

Commit eac0039

Browse files
committed
Add has internal function
In some cases, `_.countby()` and `_.groupBy()`, we want to support arrays as valid keys, since they can be coerced to strings. In the other cases, the keys are known to always be strings anyway, so we can save having to do the `_.isArray` check. Fixes #2756.
1 parent 4bd6f69 commit eac0039

File tree

2 files changed

+18
-8
lines changed

2 files changed

+18
-8
lines changed

test/collections.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -758,6 +758,9 @@
758758
6: [jordan]
759759
};
760760
assert.deepEqual(_.groupBy(collection, ['stats', 'power']), expected, 'can group by deep properties');
761+
762+
var foos = [{foo: [1, 2]}, {foo: [1, 2]}]
763+
assert.deepEqual(_.groupBy(foos, 'foo'), {"1,2": foos}, 'will coerce arrays to string keys');
761764
});
762765

763766
QUnit.test('indexBy', function(assert) {
@@ -805,6 +808,9 @@
805808
grouped = _.countBy(array);
806809
assert.strictEqual(grouped['1'], 2);
807810
assert.strictEqual(grouped['3'], 1);
811+
812+
var foos = [{foo: [1, 2]}, {foo: [1, 2]}]
813+
assert.deepEqual(_.countBy(foos, 'foo'), {'1,2': 2}, 'will coerce arrays to string keys');
808814
});
809815

810816
QUnit.test('shuffle', function(assert) {

underscore.js

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,10 @@
148148
};
149149
};
150150

151+
var has = function(obj, path) {
152+
return obj != null && hasOwnProperty.call(obj, path);
153+
}
154+
151155
var deepGet = function(obj, path) {
152156
var length = path.length;
153157
for (var i = 0; i < length; i++) {
@@ -445,7 +449,7 @@
445449
// Groups the object's values by a criterion. Pass either a string attribute
446450
// to group by, or a function that returns the criterion.
447451
_.groupBy = group(function(result, value, key) {
448-
if (_.has(result, key)) result[key].push(value); else result[key] = [value];
452+
if (has(result, key)) result[key].push(value); else result[key] = [value];
449453
});
450454

451455
// Indexes the object's values by a criterion, similar to `groupBy`, but for
@@ -458,7 +462,7 @@
458462
// either a string attribute to count by, or a function that returns the
459463
// criterion.
460464
_.countBy = group(function(result, value, key) {
461-
if (_.has(result, key)) result[key]++; else result[key] = 1;
465+
if (has(result, key)) result[key]++; else result[key] = 1;
462466
});
463467

464468
var reStrSymbol = /[^\ud800-\udfff]|[\ud800-\udbff][\udc00-\udfff]|[\ud800-\udfff]/g;
@@ -811,7 +815,7 @@
811815
var memoize = function(key) {
812816
var cache = memoize.cache;
813817
var address = '' + (hasher ? hasher.apply(this, arguments) : key);
814-
if (!_.has(cache, address)) cache[address] = func.apply(this, arguments);
818+
if (!has(cache, address)) cache[address] = func.apply(this, arguments);
815819
return cache[address];
816820
};
817821
memoize.cache = {};
@@ -978,7 +982,7 @@
978982

979983
// Constructor is a special case.
980984
var prop = 'constructor';
981-
if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop);
985+
if (has(obj, prop) && !_.contains(keys, prop)) keys.push(prop);
982986

983987
while (nonEnumIdx--) {
984988
prop = nonEnumerableProps[nonEnumIdx];
@@ -994,7 +998,7 @@
994998
if (!_.isObject(obj)) return [];
995999
if (nativeKeys) return nativeKeys(obj);
9961000
var keys = [];
997-
for (var key in obj) if (_.has(obj, key)) keys.push(key);
1001+
for (var key in obj) if (has(obj, key)) keys.push(key);
9981002
// Ahem, IE < 9.
9991003
if (hasEnumBug) collectNonEnumProps(obj, keys);
10001004
return keys;
@@ -1279,7 +1283,7 @@
12791283
while (length--) {
12801284
// Deep compare each member
12811285
key = keys[length];
1282-
if (!(_.has(b, key) && eq(a[key], b[key], aStack, bStack))) return false;
1286+
if (!(has(b, key) && eq(a[key], b[key], aStack, bStack))) return false;
12831287
}
12841288
}
12851289
// Remove the first object from the stack of traversed objects.
@@ -1329,7 +1333,7 @@
13291333
// there isn't any inspectable "Arguments" type.
13301334
if (!_.isArguments(arguments)) {
13311335
_.isArguments = function(obj) {
1332-
return _.has(obj, 'callee');
1336+
return has(obj, 'callee');
13331337
};
13341338
}
13351339

@@ -1371,7 +1375,7 @@
13711375
// on itself (in other words, not on a prototype).
13721376
_.has = function(obj, path) {
13731377
if (!_.isArray(path)) {
1374-
return obj != null && hasOwnProperty.call(obj, path);
1378+
return has(obj, path);
13751379
}
13761380
var length = path.length;
13771381
for (var i = 0; i < length; i++) {

0 commit comments

Comments
 (0)