diff --git a/build.js b/build.js index 55a77c385..6e7a8b9ef 100755 --- a/build.js +++ b/build.js @@ -177,6 +177,7 @@ 'bottom', 'firstArg', 'hasDontEnumBug', + 'hasEnumPrototype', 'isKeysFast', 'loop', 'nonEnumArgs', @@ -1924,8 +1925,9 @@ // remove `prototype` [[Enumerable]] fix from `iteratorTemplate` source = source.replace(getIteratorTemplate(source), function(match) { return match - .replace(/(?: *\/\/.*\n)* *["'] *(?:<% *)?if *\(!hasDontEnumBug *(?:&&|\))[\s\S]+?<% *} *(?:%>|["']).+/g, '') - .replace(/!hasDontEnumBug *\|\|/g, ''); + .replace(/(?: *\/\/.*\n)* *["']( *)<% *if *\(hasDontEnumBug[\s\S]+?["']\1<% *} *%>.+/, '') + .replace(/(?: *\/\/.*\n)* *["'] *(?:<% *)?if *\(hasEnumPrototype *(?:&&|\))[\s\S]+?<% *} *(?:%>|["']).+/g, '') + .replace(/hasEnumPrototype *\|\|/g, ''); }); } vm.runInContext(source, context); @@ -2095,10 +2097,10 @@ } }); - // remove `hasDontEnumBug`, `iteratesOwnLast`, and `nonEnumArgs` declarations and assignments + // remove `hasDontEnumBug`, `hasEnumPrototype`, `iteratesOwnLast`, and `nonEnumArgs` declarations and assignments source = source - .replace(/^ *\(function\(\) *{[\s\S]+?}\(1\)\);\n/m, '') - .replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var (?:hasDontEnumBug|iteratesOwnLast|nonEnumArgs).+\n/g, ''); + .replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var (?:hasDontEnumBug|hasEnumPrototype|iteratesOwnLast|nonEnumArgs).+\n/g, '') + .replace(/^ *\(function\(\) *{[\s\S]+?}\(1\)\);\n/m, ''); // remove `iteratesOwnLast` from `shimIsPlainObject` source = source.replace(matchFunction(source, 'shimIsPlainObject'), function(match) { @@ -2256,6 +2258,7 @@ if ((source.match(/\bcreateIterator\b/g) || []).length < 2) { source = removeFunction(source, 'createIterator'); source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var hasDontEnumBug;|.+?hasDontEnumBug *=.+/g, ''); + source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var hasEnumPrototype;|.+?hasEnumPrototype *=.+/g, ''); source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var nonEnumArgs;|.+?nonEnumArgs *=.+/g, ''); } if (isRemoved(source, 'createIterator', 'bind', 'keys')) { @@ -2267,8 +2270,8 @@ source = removeVar(source, 'nativeKeys'); source = removeKeysOptimization(source); } - if (!source.match(/var (?:hasDontEnumBug|iteratesOwnLast|nonEnumArgs)\b/g)) { - // remove `hasDontEnumBug`, `iteratesOwnLast`, and `nonEnumArgs` assignments + if (!source.match(/var (?:hasDontEnumBug|hasEnumPrototype|iteratesOwnLast|nonEnumArgs)\b/g)) { + // remove `hasDontEnumBug`, `hasEnumPrototype`, `iteratesOwnLast`, and `nonEnumArgs` assignments source = source.replace(/ *\(function\(\) *{[\s\S]+?}\(1\)\);\n/, ''); } } diff --git a/build/pre-compile.js b/build/pre-compile.js index e727e3dfc..7b9dec296 100644 --- a/build/pre-compile.js +++ b/build/pre-compile.js @@ -40,6 +40,7 @@ 'bottom', 'firstArg', 'hasDontEnumBug', + 'hasEnumPrototype', 'isKeysFast', 'loop', 'nonEnumArgs', diff --git a/lodash.js b/lodash.js index 09599acd9..5c2e7b00f 100644 --- a/lodash.js +++ b/lodash.js @@ -127,6 +127,18 @@ */ var hasDontEnumBug; + /** + * Detect if a `prototype` properties are enumerable by default: + * + * Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1 + * (if the prototype or a property on the prototype has been set) + * incorrectly sets a function's `prototype` property [[Enumerable]] + * value to `true`. Because of this Lo-Dash standardizes on skipping + * the the `prototype` property of functions regardless of its + * [[Enumerable]] value. + */ + var hasEnumPrototype; + /** Detect if own properties are iterated after inherited properties (IE < 9) */ var iteratesOwnLast; @@ -154,6 +166,7 @@ for (prop in arguments) { nonEnumArgs = !prop; } hasDontEnumBug = !/valueOf/.test(props); + hasEnumPrototype = propertyIsEnumerable.call(ctor, 'prototype'); iteratesOwnLast = props[0] != 'x'; }(1)); @@ -385,7 +398,7 @@ // value to `true`. Because of this Lo-Dash standardizes on skipping // the the `prototype` property of functions regardless of its // [[Enumerable]] value. - ' <% if (!hasDontEnumBug) { %>\n' + + ' <% if (hasEnumPrototype) { %>\n' + " var skipProto = typeof iterable == 'function' && \n" + " propertyIsEnumerable.call(iterable, 'prototype');\n" + ' <% } %>' + @@ -397,22 +410,22 @@ ' length = ownProps.length;\n\n' + ' while (++ownIndex < length) {\n' + ' index = ownProps[ownIndex];\n' + - " <% if (!hasDontEnumBug) { %>if (!(skipProto && index == 'prototype')) {\n <% } %>" + + " <% if (hasEnumPrototype) { %>if (!(skipProto && index == 'prototype')) {\n <% } %>" + ' <%= loop %>\n' + - ' <% if (!hasDontEnumBug) { %>}\n<% } %>' + + ' <% if (hasEnumPrototype) { %>}\n<% } %>' + ' }' + // else using a for-in loop ' <% } else { %>\n' + ' for (index in iterable) {<%' + - ' if (!hasDontEnumBug || useHas) { %>\n if (<%' + - " if (!hasDontEnumBug) { %>!(skipProto && index == 'prototype')<% }" + - ' if (!hasDontEnumBug && useHas) { %> && <% }' + + ' if (hasEnumPrototype || useHas) { %>\n if (<%' + + " if (hasEnumPrototype) { %>!(skipProto && index == 'prototype')<% }" + + ' if (hasEnumPrototype && useHas) { %> && <% }' + ' if (useHas) { %>hasOwnProperty.call(iterable, index)<% }' + ' %>) {' + ' <% } %>\n' + ' <%= loop %>;' + - ' <% if (!hasDontEnumBug || useHas) { %>\n }<% } %>\n' + + ' <% if (hasEnumPrototype || useHas) { %>\n }<% } %>\n' + ' }' + ' <% } %>' + @@ -678,6 +691,7 @@ var data = { // support properties 'hasDontEnumBug': hasDontEnumBug, + 'hasEnumPrototype': hasEnumPrototype, 'isKeysFast': isKeysFast, 'nonEnumArgs': nonEnumArgs, 'noCharByIndex': noCharByIndex, @@ -931,7 +945,7 @@ */ var keys = !nativeKeys ? shimKeys : function(object) { // avoid iterating over the `prototype` property - return typeof object == 'function' && propertyIsEnumerable.call(object, 'prototype') + return hasEnumPrototype && typeof object == 'function' && propertyIsEnumerable.call(object, 'prototype') ? shimKeys(object) : (isObject(object) ? nativeKeys(object) : []); };