Ensure sparse arrays are treated as dense by _.keys and _.keysIn and ensure support for string objects in _.keys and _.keysIn.

This commit is contained in:
John-David Dalton
2014-03-25 09:26:20 -07:00
parent 0ab5d6a7b8
commit c721721fc5
3 changed files with 37 additions and 16 deletions

View File

@@ -2236,11 +2236,17 @@
var index = -1, var index = -1,
props = keysIn(object), props = keysIn(object),
length = props.length, length = props.length,
objLength = length && object.length,
result = []; result = [];
if (typeof objLength == 'number' && objLength > 0) {
var allowIndexes = isArray(object) || (support.unindexedChars && isString(object)),
maxIndex = objLength - 1;
}
while (++index < length) { while (++index < length) {
var key = props[index]; var key = props[index];
if (hasOwnProperty.call(object, key)) { if ((allowIndexes && key > -1 && key <= maxIndex && key % 1 == 0) ||
hasOwnProperty.call(object, key)) {
result.push(key); result.push(key);
} }
} }
@@ -6497,8 +6503,8 @@
* // => ['x', 'y'] (property order is not guaranteed across environments) * // => ['x', 'y'] (property order is not guaranteed across environments)
*/ */
var keys = !nativeKeys ? shimKeys : function(object) { var keys = !nativeKeys ? shimKeys : function(object) {
if ((support.enumPrototypes && typeof object == 'function') || var length = object ? object.length : 0;
(support.nonEnumArgs && object && object.length && isArguments(object))) { if (typeof length == 'number' && length > 0) {
return shimKeys(object); return shimKeys(object);
} }
return isObject(object) ? nativeKeys(object) : []; return isObject(object) ? nativeKeys(object) : [];
@@ -6525,19 +6531,30 @@
* // => ['x', 'y', 'z'] (property order is not guaranteed across environments) * // => ['x', 'y', 'z'] (property order is not guaranteed across environments)
*/ */
function keysIn(object) { function keysIn(object) {
var result = [];
if (!isObject(object)) { if (!isObject(object)) {
return result; return [];
} }
if (support.nonEnumArgs && object.length && isArguments(object)) { var length = object.length;
object = slice(object); length = (typeof length == 'number' && length > 0 &&
} (isArray(object) || (support.unindexedChars && isString(object)) ||
var skipProto = support.enumPrototypes && typeof object == 'function', (support.nonEnumArgs && isArguments(object))) && length) >>> 0;
skipErrorProps = support.enumErrorProps && (object === errorProto || object instanceof Error);
var maxIndex = length - 1,
result = Array(length),
skipIndexes = length > 0,
skipErrorProps = support.enumErrorProps && (object === errorProto || object instanceof Error),
skipProto = support.enumPrototypes && typeof object == 'function';
if (skipIndexes) {
var index = -1;
while (++index < length) {
result[index] = String(index);
}
}
for (var key in object) { for (var key in object) {
if (!(skipProto && key == 'prototype') && if (!(skipProto && key == 'prototype') &&
!(skipErrorProps && (key == 'message' || key == 'name'))) { !(skipErrorProps && (key == 'message' || key == 'name')) &&
!(skipIndexes && key > -1 && key <= maxIndex && key % 1 == 0)) {
result.push(key); result.push(key);
} }
} }
@@ -6546,9 +6563,9 @@
// attribute of an existing property and the `constructor` property of a // attribute of an existing property and the `constructor` property of a
// prototype defaults to non-enumerable. // prototype defaults to non-enumerable.
if (support.nonEnumShadows && object !== objectProto) { if (support.nonEnumShadows && object !== objectProto) {
var ctor = object.constructor, var ctor = object.constructor;
index = -1, index = -1;
length = shadowedProps.length; length = shadowedProps.length;
if (object === (ctor && ctor.prototype)) { if (object === (ctor && ctor.prototype)) {
var className = object === stringProto ? stringClass : object === errorProto ? errorClass : toString.call(object), var className = object === stringProto ? stringClass : object === errorProto ? errorClass : toString.call(object),

View File

@@ -5091,10 +5091,10 @@
deepEqual(_.keys(object), ['a', 'b']); deepEqual(_.keys(object), ['a', 'b']);
}); });
test('should work with sparse arrays', 1, function() { test('should treat sparse arrays as dense', 1, function() {
var array = [1]; var array = [1];
array[2] = 3; array[2] = 3;
deepEqual(_.keys(array), ['0', '2']); deepEqual(_.keys(array), ['0', '1', '2']);
}); });
test('should work with `arguments` objects (test in IE < 9)', 1, function() { test('should work with `arguments` objects (test in IE < 9)', 1, function() {

View File

@@ -82,6 +82,9 @@
'isEqual': [ 'isEqual': [
'Died on test #60', 'Died on test #60',
'Died on test #63' 'Died on test #63'
],
'keys': [
'is not fooled by sparse arrays; see issue #95'
] ]
}, },
'Utility': { 'Utility': {
@@ -122,6 +125,7 @@
} }
delete QUnit.config.excused.Chaining; delete QUnit.config.excused.Chaining;
delete QUnit.config.excused.Collections.where; delete QUnit.config.excused.Collections.where;
delete QUnit.config.excused.Objects.keys;
delete QUnit.config.excused.Utility['_.escape']; delete QUnit.config.excused.Utility['_.escape'];
delete QUnit.config.excused.Utility['_.unescape']; delete QUnit.config.excused.Utility['_.unescape'];
} }