Add more _.forIn iteration tests and prep support for Error object iteration.

Former-commit-id: 3676681717d0648c9f96570a4952f7c35e6a9bec
This commit is contained in:
John-David Dalton
2013-05-07 01:38:13 -07:00
parent aad55fc3db
commit e0cf4e644b
3 changed files with 102 additions and 56 deletions

View File

@@ -14,6 +14,9 @@
'className', 'className',
'collection', 'collection',
'ctor', 'ctor',
'ctorByClass',
'errorClass',
'errorProto',
'guard', 'guard',
'hasOwnProperty', 'hasOwnProperty',
'index', 'index',
@@ -22,14 +25,13 @@
'isProto', 'isProto',
'isString', 'isString',
'iterable', 'iterable',
'iterated',
'length', 'length',
'keys', 'keys',
'lodash', 'lodash',
'nonEnum', 'nonEnum',
'nonEnumProps', 'nonEnumProps',
'object', 'object',
'objectRef', 'objectProto',
'objectTypes', 'objectTypes',
'ownIndex', 'ownIndex',
'ownProps', 'ownProps',
@@ -37,7 +39,10 @@
'result', 'result',
'skipProto', 'skipProto',
'source', 'source',
'thisArg' 'stringClass',
'stringProto',
'thisArg',
'toString'
]; ];
/** Used to minify `iteratorTemplate` data properties */ /** Used to minify `iteratorTemplate` data properties */

106
lodash.js
View File

@@ -81,9 +81,9 @@
/** Used to assign default `context` object properties */ /** Used to assign default `context` object properties */
var contextProps = [ var contextProps = [
'Array', 'Boolean', 'Date', 'Function', 'Math', 'Number', 'Object', 'RegExp', 'Array', 'Boolean', 'Date', 'Error', 'Function', 'Math', 'Number', 'Object',
'String', '_', 'attachEvent', 'clearTimeout', 'isFinite', 'isNaN', 'parseInt', 'RegExp', 'String', '_', 'attachEvent', 'clearTimeout', 'isFinite', 'isNaN',
'setImmediate', 'setTimeout' 'parseInt', 'setImmediate', 'setTimeout'
]; ];
/** Used to fix the JScript [[DontEnum]] bug */ /** Used to fix the JScript [[DontEnum]] bug */
@@ -100,6 +100,7 @@
arrayClass = '[object Array]', arrayClass = '[object Array]',
boolClass = '[object Boolean]', boolClass = '[object Boolean]',
dateClass = '[object Date]', dateClass = '[object Date]',
errorClass = '[object Error]',
funcClass = '[object Function]', funcClass = '[object Function]',
numberClass = '[object Number]', numberClass = '[object Number]',
objectClass = '[object Object]', objectClass = '[object Object]',
@@ -157,6 +158,7 @@
var Array = context.Array, var Array = context.Array,
Boolean = context.Boolean, Boolean = context.Boolean,
Date = context.Date, Date = context.Date,
Error = context.Error,
Function = context.Function, Function = context.Function,
Math = context.Math, Math = context.Math,
Number = context.Number, Number = context.Number,
@@ -166,15 +168,17 @@
TypeError = context.TypeError; TypeError = context.TypeError;
/** Used for `Array` and `Object` method references */ /** Used for `Array` and `Object` method references */
var arrayRef = Array(), var arrayProto = Array.prototype,
objectRef = Object(); errorProto = Error.prototype,
objectProto = Object.prototype,
stringProto = String.prototype;
/** Used to restore the original `_` reference in `noConflict` */ /** Used to restore the original `_` reference in `noConflict` */
var oldDash = context._; var oldDash = context._;
/** Used to detect if a method is native */ /** Used to detect if a method is native */
var reNative = RegExp('^' + var reNative = RegExp('^' +
String(objectRef.valueOf) String(objectProto.valueOf)
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
.replace(/valueOf|for [^\]]+/g, '.+?') + '$' .replace(/valueOf|for [^\]]+/g, '.+?') + '$'
); );
@@ -182,14 +186,14 @@
/** Native method shortcuts */ /** Native method shortcuts */
var ceil = Math.ceil, var ceil = Math.ceil,
clearTimeout = context.clearTimeout, clearTimeout = context.clearTimeout,
concat = arrayRef.concat, concat = arrayProto.concat,
floor = Math.floor, floor = Math.floor,
getPrototypeOf = reNative.test(getPrototypeOf = Object.getPrototypeOf) && getPrototypeOf, getPrototypeOf = reNative.test(getPrototypeOf = Object.getPrototypeOf) && getPrototypeOf,
hasOwnProperty = objectRef.hasOwnProperty, hasOwnProperty = objectProto.hasOwnProperty,
push = arrayRef.push, push = arrayProto.push,
setImmediate = context.setImmediate, setImmediate = context.setImmediate,
setTimeout = context.setTimeout, setTimeout = context.setTimeout,
toString = objectRef.toString; toString = objectProto.toString;
/* Native method shortcuts for methods with the same name as other `lodash` methods */ /* Native method shortcuts for methods with the same name as other `lodash` methods */
var nativeBind = reNative.test(nativeBind = toString.bind) && nativeBind, var nativeBind = reNative.test(nativeBind = toString.bind) && nativeBind,
@@ -201,7 +205,7 @@
nativeMin = Math.min, nativeMin = Math.min,
nativeParseInt = context.parseInt, nativeParseInt = context.parseInt,
nativeRandom = Math.random, nativeRandom = Math.random,
nativeSlice = arrayRef.slice; nativeSlice = arrayProto.slice;
/** Detect various environments */ /** Detect various environments */
var isIeOpera = reNative.test(context.attachEvent), var isIeOpera = reNative.test(context.attachEvent),
@@ -212,6 +216,8 @@
ctorByClass[arrayClass] = Array; ctorByClass[arrayClass] = Array;
ctorByClass[boolClass] = Boolean; ctorByClass[boolClass] = Boolean;
ctorByClass[dateClass] = Date; ctorByClass[dateClass] = Date;
ctorByClass[errorClass] = Error;
ctorByClass[funcClass] = Function;
ctorByClass[objectClass] = Object; ctorByClass[objectClass] = Object;
ctorByClass[numberClass] = Number; ctorByClass[numberClass] = Number;
ctorByClass[regexpClass] = RegExp; ctorByClass[regexpClass] = RegExp;
@@ -219,18 +225,18 @@
/** Used to avoid iterating non-enumerable properties in IE < 9 */ /** Used to avoid iterating non-enumerable properties in IE < 9 */
var nonEnumProps = {}; var nonEnumProps = {};
nonEnumProps[arrayClass] = nonEnumProps[dateClass] = nonEnumProps[numberClass] = { 'constructor': 1, 'toLocaleString': 1, 'toString': 1, 'valueOf': 1 }; nonEnumProps[arrayClass] = nonEnumProps[dateClass] = nonEnumProps[numberClass] = { 'constructor': true, 'toLocaleString': true, 'toString': true, 'valueOf': true };
nonEnumProps[boolClass] = nonEnumProps[stringClass] = { 'constructor': 1, 'toString': 1, 'valueOf': 1 }; nonEnumProps[boolClass] = nonEnumProps[stringClass] = { 'constructor': true, 'toString': true, 'valueOf': true };
nonEnumProps[funcClass] = nonEnumProps[regexpClass] = { 'constructor': 1, 'toString': 1 }; nonEnumProps[errorClass] = nonEnumProps[funcClass] = nonEnumProps[regexpClass] = { 'constructor': true, 'toString': true };
nonEnumProps[objectClass] = { 'constructor': 1 }; nonEnumProps[objectClass] = { 'constructor': true };
(function() { (function() {
var length = shadowedProps.length; var length = shadowedProps.length;
while (length--) { while (length--) {
var prop = shadowedProps[length]; var prop = shadowedProps[length];
for (var className in nonEnumProps) { for (var className in nonEnumProps) {
if (hasOwnProperty.call(nonEnumProps, className) && nonEnumProps[className][prop] !== 1) { if (hasOwnProperty.call(nonEnumProps, className) && !hasOwnProperty.call(nonEnumProps[className], prop)) {
nonEnumProps[className][prop] = 0; nonEnumProps[className][prop] = false;
} }
} }
} }
@@ -400,7 +406,7 @@
* @memberOf _.support * @memberOf _.support
* @type Boolean * @type Boolean
*/ */
support.spliceObjects = (arrayRef.splice.call(object, 0, 1), !object[0]); support.spliceObjects = (arrayProto.splice.call(object, 0, 1), !object[0]);
/** /**
* Detect lack of support for accessing string characters by index. * Detect lack of support for accessing string characters by index.
@@ -574,24 +580,24 @@
// defaults to non-enumerable, Lo-Dash skips the `constructor` // defaults to non-enumerable, Lo-Dash skips the `constructor`
// property when it infers it's iterating over a `prototype` object. // property when it infers it's iterating over a `prototype` object.
' <% if (support.nonEnumShadows) { %>\n\n' + ' <% if (support.nonEnumShadows) { %>\n\n' +
' var iterated = {' + ' if (iterable !== objectProto) {\n' +
' <% for (var k = 0; k < 7; k++) { %>\n' + " var ctor = iterable.constructor,\n" +
" '<%= shadowedProps[k] %>': 0<%= (k < 6 ? ',' : '') %>" + ' proto = ctor && ctor.prototype,\n' +
' <% } %>\n' + ' isProto = iterable === proto,\n' +
' };\n\n' + ' nonEnum = nonEnumProps[objectClass];\n\n' +
' var className = toString.call(iterable),\n' + ' if (isProto) {\n' +
' ctor = iterable.constructor,\n' + " var className = iterable === stringProto ? stringClass : iterable === errorProto ? errorClass : toString.call(iterable),\n" +
' proto = ctor && ctor.prototype,\n' + ' nonEnum = nonEnumProps[iterable === (ctorByClass[className] && ctorByClass[className].prototype) ? className : objectClass];\n' +
' isProto = iterable === proto,\n' + ' }\n' +
' nonEnum = nonEnumProps[className];\n\n' +
' <% for (k = 0; k < 7; k++) { %>\n' + ' <% for (k = 0; k < 7; k++) { %>\n' +
" index = '<%= shadowedProps[k] %>';\n" + " index = '<%= shadowedProps[k] %>';\n" +
' if (!iterated[index] && (iterated[index] = (!(isProto && nonEnum[index]) && hasOwnProperty.call(iterable, index))<%' + ' if ((!(isProto && nonEnum[index]) && hasOwnProperty.call(iterable, index))<%' +
' if (!useHas) { %> || (!nonEnum[index] && iterable[index] !== objectRef[index])<% }' + ' if (!useHas) { %> || (!nonEnum[index] && iterable[index] !== objectProto[index])<% }' +
' %>)) {\n' + ' %>) {\n' +
' <%= loop %>\n' + ' <%= loop %>\n' +
' }' +
' <% } %>\n' +
' }' + ' }' +
' <% } %>' +
' <% } %>' + ' <% } %>' +
' <% } %>' + ' <% } %>' +
' <% if (arrays || support.nonEnumArgs) { %>\n}<% } %>\n' + ' <% if (arrays || support.nonEnumArgs) { %>\n}<% } %>\n' +
@@ -805,14 +811,16 @@
// create the function factory // create the function factory
var factory = Function( var factory = Function(
'hasOwnProperty, isArguments, isArray, isString, keys, lodash, ' + 'ctorByClass, errorClass, errorProto, hasOwnProperty, isArguments, ' +
'objectRef, objectTypes, nonEnumProps, toString', 'isArray, isString, keys, lodash, objectClass, objectProto, objectTypes, ' +
'nonEnumProps, stringClass, stringProto, toString',
'return function(' + args + ') {\n' + iteratorTemplate(data) + '\n}' 'return function(' + args + ') {\n' + iteratorTemplate(data) + '\n}'
); );
// return the compiled function // return the compiled function
return factory( return factory(
hasOwnProperty, isArguments, isArray, isString, keys, lodash, ctorByClass, errorClass, errorProto, hasOwnProperty, isArguments,
objectRef, objectTypes, nonEnumProps, toString isArray, isString, keys, lodash, objectClass, objectProto, objectTypes,
nonEnumProps, stringClass, stringProto, toString
); );
} }
@@ -2166,7 +2174,7 @@
if (isFunc) { if (isFunc) {
callback = lodash.createCallback(callback, thisArg); callback = lodash.createCallback(callback, thisArg);
} else { } else {
var props = concat.apply(arrayRef, nativeSlice.call(arguments, 1)); var props = concat.apply(arrayProto, nativeSlice.call(arguments, 1));
} }
forIn(object, function(value, key, object) { forIn(object, function(value, key, object) {
if (isFunc if (isFunc
@@ -2235,7 +2243,7 @@
var result = {}; var result = {};
if (typeof callback != 'function') { if (typeof callback != 'function') {
var index = -1, var index = -1,
props = concat.apply(arrayRef, nativeSlice.call(arguments, 1)), props = concat.apply(arrayProto, nativeSlice.call(arguments, 1)),
length = isObject(object) ? props.length : 0; length = isObject(object) ? props.length : 0;
while (++index < length) { while (++index < length) {
@@ -2305,7 +2313,7 @@
*/ */
function at(collection) { function at(collection) {
var index = -1, var index = -1,
props = concat.apply(arrayRef, nativeSlice.call(arguments, 1)), props = concat.apply(arrayProto, nativeSlice.call(arguments, 1)),
length = props.length, length = props.length,
result = Array(length); result = Array(length);
@@ -3347,7 +3355,7 @@
function difference(array) { function difference(array) {
var index = -1, var index = -1,
length = array ? array.length : 0, length = array ? array.length : 0,
flattened = concat.apply(arrayRef, nativeSlice.call(arguments, 1)), flattened = concat.apply(arrayProto, nativeSlice.call(arguments, 1)),
contains = cachedContains(flattened), contains = cachedContains(flattened),
result = []; result = [];
@@ -4024,9 +4032,9 @@
*/ */
function union(array) { function union(array) {
if (!isArray(array)) { if (!isArray(array)) {
arguments[0] = array ? nativeSlice.call(array) : arrayRef; arguments[0] = array ? nativeSlice.call(array) : arrayProto;
} }
return uniq(concat.apply(arrayRef, arguments)); return uniq(concat.apply(arrayProto, arguments));
} }
/** /**
@@ -4303,7 +4311,7 @@
* // => alerts 'clicked docs', when the button is clicked * // => alerts 'clicked docs', when the button is clicked
*/ */
function bindAll(object) { function bindAll(object) {
var funcs = arguments.length > 1 ? concat.apply(arrayRef, nativeSlice.call(arguments, 1)) : functions(object), var funcs = arguments.length > 1 ? concat.apply(arrayProto, nativeSlice.call(arguments, 1)) : functions(object),
index = -1, index = -1,
length = funcs.length; length = funcs.length;
@@ -5482,7 +5490,7 @@
// add `Array` functions that return unwrapped values // add `Array` functions that return unwrapped values
each(['join', 'pop', 'shift'], function(methodName) { each(['join', 'pop', 'shift'], function(methodName) {
var func = arrayRef[methodName]; var func = arrayProto[methodName];
lodash.prototype[methodName] = function() { lodash.prototype[methodName] = function() {
return func.apply(this.__wrapped__, arguments); return func.apply(this.__wrapped__, arguments);
}; };
@@ -5490,7 +5498,7 @@
// add `Array` functions that return the wrapped value // add `Array` functions that return the wrapped value
each(['push', 'reverse', 'sort', 'unshift'], function(methodName) { each(['push', 'reverse', 'sort', 'unshift'], function(methodName) {
var func = arrayRef[methodName]; var func = arrayProto[methodName];
lodash.prototype[methodName] = function() { lodash.prototype[methodName] = function() {
func.apply(this.__wrapped__, arguments); func.apply(this.__wrapped__, arguments);
return this; return this;
@@ -5499,7 +5507,7 @@
// add `Array` functions that return new wrapped values // add `Array` functions that return new wrapped values
each(['concat', 'slice', 'splice'], function(methodName) { each(['concat', 'slice', 'splice'], function(methodName) {
var func = arrayRef[methodName]; var func = arrayProto[methodName];
lodash.prototype[methodName] = function() { lodash.prototype[methodName] = function() {
return new lodashWrapper(func.apply(this.__wrapped__, arguments)); return new lodashWrapper(func.apply(this.__wrapped__, arguments));
}; };
@@ -5509,7 +5517,7 @@
// in Firefox < 10 and IE < 9 // in Firefox < 10 and IE < 9
if (!support.spliceObjects) { if (!support.spliceObjects) {
each(['pop', 'shift', 'splice'], function(methodName) { each(['pop', 'shift', 'splice'], function(methodName) {
var func = arrayRef[methodName], var func = arrayProto[methodName],
isSplice = methodName == 'splice'; isSplice = methodName == 'splice';
lodash.prototype[methodName] = function() { lodash.prototype[methodName] = function() {

View File

@@ -999,12 +999,25 @@
(function() { (function() {
test('iterates over inherited properties', function() { test('iterates over inherited properties', function() {
function Dog(name) { this.name = name; } function Foo() { this.a = 1; }
Dog.prototype.bark = function() { /* Woof, woof! */ }; Foo.prototype.b = 2;
var keys = []; var keys = [];
_.forIn(new Dog('Dagny'), function(value, key) { keys.push(key); }); _.forIn(new Foo, function(value, key) { keys.push(key); });
deepEqual(keys.sort(), ['bark', 'name']); deepEqual(keys.sort(), ['a', 'b']);
});
test('fixes the JScript [[DontEnum]] bug with inherited properties (test in IE < 9)', function() {
function Foo() {}
Foo.prototype = shadowedObject;
function Bar() {}
Bar.prototype = new Foo;
Bar.prototype.constructor = Bar;
var keys = [];
_.forIn(shadowedObject, function(value, key) { keys.push(key); });
deepEqual(keys.sort(), shadowedProps);
}); });
}()); }());
@@ -1035,6 +1048,26 @@
deepEqual(keys.sort(), shadowedProps); deepEqual(keys.sort(), shadowedProps);
}); });
test('lodash.' + methodName + ' does not iterate over non-enumerable properties (test in IE < 9)', function() {
_.forOwn({
'Array': Array.prototype,
'Boolean': Boolean.prototype,
'Date': Date.prototype,
'Error': Error.prototype,
'Function': Function.prototype,
'Object': Object.prototype,
'Number': Number.prototype,
'TypeError': TypeError.prototype,
'RegExp': RegExp.prototype,
'String': String.prototype
},
function(object, builtin) {
var keys = [];
func(object, function(value, key) { keys.push(key); });
deepEqual(keys, [], 'non-enumerable properties on ' + builtin + '.prototype');
});
});
test('lodash.' + methodName + ' skips the prototype property of functions (test in Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1)', function() { test('lodash.' + methodName + ' skips the prototype property of functions (test in Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1)', function() {
function Foo() {} function Foo() {}
Foo.prototype.a = 1; Foo.prototype.a = 1;