diff --git a/build/pre-compile.js b/build/pre-compile.js index 071c8f31c..f583edde9 100644 --- a/build/pre-compile.js +++ b/build/pre-compile.js @@ -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 */ diff --git a/lodash.js b/lodash.js index 419cb1a09..241693e9f 100644 --- a/lodash.js +++ b/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() { diff --git a/test/test.js b/test/test.js index f260ca850..5ff378c08 100644 --- a/test/test.js +++ b/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;