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

106
lodash.js
View File

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

View File

@@ -999,12 +999,25 @@
(function() {
test('iterates over inherited properties', function() {
function Dog(name) { this.name = name; }
Dog.prototype.bark = function() { /* Woof, woof! */ };
function Foo() { this.a = 1; }
Foo.prototype.b = 2;
var keys = [];
_.forIn(new Dog('Dagny'), function(value, key) { keys.push(key); });
deepEqual(keys.sort(), ['bark', 'name']);
_.forIn(new Foo, function(value, key) { keys.push(key); });
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);
});
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() {
function Foo() {}
Foo.prototype.a = 1;