diff --git a/build.js b/build.js index 92ca023a9..f065158c6 100755 --- a/build.js +++ b/build.js @@ -439,25 +439,19 @@ if (isMobile) { // inline functions defined with `createIterator` lodash.functions(lodash).forEach(function(funcName) { - var reFunc = RegExp('( +var ' + funcName + ' *= *)((?:[a-zA-Z]+ *\\|\\| *)?)createIterator\\(((?:{|[a-zA-Z])[\\s\\S]+?)\\);\\n'), - parts = source.match(reFunc); + var reFunc = RegExp('(\\bvar ' + funcName.replace(/^_/, '') + ' *= *)createIterator\\(((?:{|[a-zA-Z])[\\s\\S]+?)\\);\\n'); // skip if not defined with `createIterator` - if (!parts) { + if (!reFunc.test(source)) { return; } - // extract function's code - var code = funcName == 'keys' - ? '$1$2' + lodash._createIterator(Function('return ' + parts[3])()) - : '$1' + lodash[funcName]; - - // format code - code = code.replace(/\n(?:.*)/g, function(match) { + // extract and format the function's code + var code = (lodash[funcName] + '').replace(/\n(?:.*)/g, function(match) { match = match.slice(1); return (match == '}' ? '\n ' : '\n ') + match; - }) + ';\n'; + }); - source = source.replace(reFunc, code); + source = source.replace(reFunc, '$1' + code + ';\n'); }); // remove `iteratorTemplate` @@ -490,7 +484,7 @@ } // remove pseudo private properties - source = source.replace(/(?:\s*\/\/.*)*\s*lodash\._(?:createIterator|iteratorTemplate)\b.+\n/g, '\n'); + source = source.replace(/(?:\s*\/\/.*)*\s*lodash\._[^=]+=.+\n/g, '\n'); /*--------------------------------------------------------------------------*/ diff --git a/lodash.js b/lodash.js index 55c4c32f3..425d6e857 100644 --- a/lodash.js +++ b/lodash.js @@ -477,6 +477,21 @@ // no operation performed } + /** + * A shim implementation of `Object.keys` that produces an array of the given + * object's enumerable own property names. + * + * @private + * @param {Object} object The object to inspect. + * @returns {Array} Returns a new array of property names. + */ + var shimKeys = createIterator({ + 'args': 'object', + 'exit': 'if (!objectTypes[typeof object] || object === null) throw TypeError()', + 'init': '[]', + 'inLoop': 'result.push(index)' + }); + /** * Used by `template()` to replace "escape" template delimiters with tokens. * @@ -2643,12 +2658,12 @@ * _.keys({ 'one': 1, 'two': 2, 'three': 3 }); * // => ['one', 'two', 'three'] */ - var keys = nativeKeys || createIterator({ - 'args': 'object', - 'exit': 'if (!objectTypes[typeof object] || object === null) throw TypeError()', - 'init': '[]', - 'inLoop': 'result.push(index)' - }); + var keys = !nativeKeys ? shimKeys : function(object) { + // avoid iterating over the `prototype` property + return typeof object == 'function' + ? shimKeys(object) + : nativeKeys(object); + }; /** * Creates an object composed of the specified properties. Property names may @@ -3209,6 +3224,7 @@ // add pseudo privates used and removed during the build process lodash._createIterator = createIterator; lodash._iteratorTemplate = iteratorTemplate; + lodash._shimKeys = shimKeys; /*--------------------------------------------------------------------------*/ diff --git a/test/test.js b/test/test.js index e98c8ab09..8b4f8d417 100644 --- a/test/test.js +++ b/test/test.js @@ -306,6 +306,20 @@ deepEqual(_.keys(shadowed).sort(), 'constructor hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf'.split(' ')); }); + + test('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.c = 3; + + Foo.a = 1; + Foo.b = 2; + + var expected = ['a', 'b']; + deepEqual(_.keys(Foo), expected); + + Foo.prototype = { 'c': 3 }; + deepEqual(_.keys(Foo), expected); + }); }()); /*--------------------------------------------------------------------------*/