mirror of
https://github.com/whoisclebs/lodash.git
synced 2026-01-31 15:27:50 +00:00
Add more _.forIn iteration tests and prep support for Error object iteration.
Former-commit-id: 3676681717d0648c9f96570a4952f7c35e6a9bec
This commit is contained in:
@@ -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
106
lodash.js
@@ -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() {
|
||||
|
||||
41
test/test.js
41
test/test.js
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user