diff --git a/build.js b/build.js index 2a57c6a83..92a597fd0 100755 --- a/build.js +++ b/build.js @@ -223,13 +223,13 @@ 'inLoop', 'init', 'isKeysFast', - 'iteratee', 'object', 'objectBranch', 'noCharByIndex', 'shadowed', 'top', - 'useHas' + 'useHas', + 'useStrict' ]; /** Collections of method names */ @@ -840,13 +840,7 @@ // prepend data object references to property names to avoid having to // use a with-statement iteratorOptions.forEach(function(property) { - if (property == 'iteratee') { - // use a more fine-grained regexp for the `iteratee` property because - // it's a compiled variable as well as a data property - snippet = snippet.replace(/(__t *= *\( *)(iteratee)/, '$1obj.$2'); - } else { - snippet = snippet.replace(RegExp('([^\\w.])\\b' + property + '\\b', 'g'), '$1obj.' + property); - } + snippet = snippet.replace(RegExp('([^\\w.])\\b' + property + '\\b', 'g'), '$1obj.' + property); }); // remove unnecessary code diff --git a/build/post-compile.js b/build/post-compile.js index 915d39f7d..2db173227 100644 --- a/build/post-compile.js +++ b/build/post-compile.js @@ -34,9 +34,6 @@ // move vars exposed by Closure Compiler into the IIFE source = source.replace(/^([^(\n]+)\s*(\(function[^)]+\){)/, '$2$1'); - // use double quotes consistently - source = source.replace(/'use strict'/, '"use strict"'); - // unescape properties (i.e. foo["bar"] => foo.bar) source = source.replace(/(\w)\["([^."]+)"\]/g, '$1.$2'); diff --git a/build/pre-compile.js b/build/pre-compile.js index 759625b04..7e0c04944 100644 --- a/build/pre-compile.js +++ b/build/pre-compile.js @@ -10,17 +10,20 @@ 'accumulator', 'args', 'arrayClass', + 'bind', 'callback', 'className', 'collection', 'compareAscending', 'ctor', 'funcClass', + 'funcs', 'hasOwnProperty', 'identity', 'index', 'isFunc', 'iteratee', + 'iterateeIndex', 'iteratorBind', 'length', 'methodName', @@ -36,8 +39,6 @@ 'result', 'skipProto', 'slice', - 'source', - 'sourceIndex', 'stringClass', 'target', 'thisArg', @@ -58,13 +59,13 @@ 'inLoop', 'init', 'isKeysFast', - 'iteratee', 'object', 'objectBranch', 'noCharByIndex', 'shadowed', 'top', - 'useHas' + 'useHas', + 'useStrict' ]; /** Used to minify variables and string values to a single character */ diff --git a/lodash.js b/lodash.js index 332ee65ae..bc96d53fb 100644 --- a/lodash.js +++ b/lodash.js @@ -266,13 +266,17 @@ * @returns {String} Returns the interpolated text. */ var iteratorTemplate = template( + // conditional strict mode + '<% if (useStrict) { %>\'use strict\';\n<% } %>' + + + // the `iteratee` may be reassigned by the `top` snippet + 'var index, iteratee = <%= firstArg %>, ' + // assign the `result` variable an initial value - 'var result<% if (init) { %> = <%= init %><% } %>;\n' + + 'result<% if (init) { %> = <%= init %><% } %>;\n' + // add code to exit early or do so if the first argument is falsey '<%= exit %>;\n' + // add code after the exit snippet but before the iteration branches '<%= top %>;\n' + - 'var index, iteratee = <%= iteratee %>;\n' + // the following branch is for iterating arrays and array-like objects '<% if (arrayBranch) { %>' + @@ -389,14 +393,14 @@ /** Reusable iterator options for `defaults` and `extend` */ var extendIteratorOptions = { + 'useHas': false, + 'useStrict': false, 'args': 'object', 'init': 'object', 'top': - 'for (var source, sourceIndex = 1, length = arguments.length; sourceIndex < length; sourceIndex++) {\n' + - ' source = arguments[sourceIndex];\n' + - (hasDontEnumBug ? ' if (source) {' : ''), - 'iteratee': 'source', - 'useHas': false, + 'for (var iterateeIndex = 1, length = arguments.length; iterateeIndex < length; iterateeIndex++) {\n' + + ' iteratee = arguments[iterateeIndex];\n' + + (hasDontEnumBug ? ' if (iteratee) {' : ''), 'inLoop': 'result[index] = iteratee[index]', 'bottom': (hasDontEnumBug ? ' }\n' : '') + '}' }; @@ -443,6 +447,12 @@ * @private * @param {Object} [options1, options2, ...] The compile options objects. * + * useHas - A boolean to specify whether or not to use `hasOwnProperty` checks + * in the object loop. + * + * useStrict - A boolean to specify whether or not to include the ES5 + * "use strict" directive. + * * args - A string of comma separated arguments the iteration function will * accept. * @@ -457,12 +467,6 @@ * beforeLoop - A string or object containing an "array" or "object" property * of code to execute before the array or object loops. * - * iteratee - A string or object containing an "array" or "object" property - * of the variable to be iterated in the loop expression. - * - * useHas - A boolean to specify whether or not to use `hasOwnProperty` checks - * in the object loop. - * * inLoop - A string or object containing an "array" or "object" property * of code to execute in the array or object loops. * @@ -506,14 +510,14 @@ } // set additional template `data` values var args = data.args, - firstArg = /^[^,]+/.exec(args)[0], - iteratee = (data.iteratee = data.iteratee || firstArg); + firstArg = /^[^,]+/.exec(args)[0]; data.firstArg = firstArg; data.hasDontEnumBug = hasDontEnumBug; data.isKeysFast = isKeysFast; data.shadowed = shadowed; data.useHas = data.useHas !== false; + data.useStrict = data.useStrict !== false; if (!('noCharByIndex' in data)) { data.noCharByIndex = noCharByIndex; @@ -526,14 +530,14 @@ } // create the function factory var factory = Function( - 'arrayClass, compareAscending, funcClass, hasOwnProperty, identity, ' + - 'iteratorBind, objectTypes, nativeKeys, propertyIsEnumerable, ' + - 'slice, stringClass, toString', - '"use strict"; return function(' + args + ') {\n' + iteratorTemplate(data) + '\n}' + 'arrayClass, bind, compareAscending, funcClass, hasOwnProperty, identity, ' + + 'iteratorBind, objectTypes, nativeKeys, propertyIsEnumerable, slice, ' + + 'stringClass, toString', + 'return function(' + args + ') {\n' + iteratorTemplate(data) + '\n}' ); // return the compiled function return factory( - arrayClass, compareAscending, funcClass, hasOwnProperty, identity, + arrayClass, bind, compareAscending, funcClass, hasOwnProperty, identity, iteratorBind, objectTypes, nativeKeys, propertyIsEnumerable, slice, stringClass, toString ); @@ -2058,19 +2062,23 @@ * jQuery('#lodash_button').on('click', buttonView.onClick); * // => When the button is clicked, `this.label` will have the correct value */ - function bindAll(object) { - var funcs = arguments, - index = 1; - - if (funcs.length == 1) { - index = 0; - funcs = functions(object); - } - for (var length = funcs.length; index < length; index++) { - object[funcs[index]] = bind(object[funcs[index]], object); - } - return object; - } + var bindAll = createIterator({ + 'useHas': false, + 'useStrict': false, + 'args': 'object', + 'init': 'object', + 'top': + 'var funcs = arguments,\n' + + ' length = funcs.length;\n' + + 'if (length > 1) {\n' + + ' for (var index = 1; index < length; index++)\n' + + ' result[funcs[index]] = bind(result[funcs[index]], result);\n' + + ' return result\n' + + '}', + 'inLoop': + 'if (toString.call(result[index]) == funcClass)' + + ' result[index] = bind(result[index], result)' + }); /** * Creates a new function that is the composition of the passed functions, @@ -2496,9 +2504,9 @@ * // => ['all', 'any', 'bind', 'bindAll', 'clone', 'compact', 'compose', ...] */ var functions = createIterator({ + 'useHas': false, 'args': 'object', 'init': '[]', - 'useHas': false, 'inLoop': 'if (toString.call(iteratee[index]) == funcClass) result.push(index)', 'bottom': 'result.sort()' }); diff --git a/perf/perf.js b/perf/perf.js index 20a6bf168..cffb3176f 100644 --- a/perf/perf.js +++ b/perf/perf.js @@ -82,12 +82,28 @@ lodash = window.lodash; var length = 20, + funcNames = lodash.functions(lodash), numbers = [], object = {}, fourNumbers = [5, 25, 10, 30], nestedNumbers = [1, [2], [3, [[4]]]], twoNumbers = [12, 21]; + var bindAllObjects = []; + + var objects = lodash.map(numbers, function(num) { + return { 'num': num }; + }); + + lodash.times(this.count, function(index) { + bindAllObjects[index] = lodash.clone(lodash); + }); + + lodash.times(length, function(index) { + numbers[index] = index; + object['key' + index] = index; + }); + var ctor = function() { }; var func = function(greeting, punctuation) { @@ -219,15 +235,6 @@ }; var words = _.keys(wordToNumber).slice(0, length); - - for (var index = 0; index < length; index++) { - numbers[index] = index; - object['key' + index] = index; - } - - var objects = lodash.map(numbers, function(num) { - return { 'num': num }; - }); } }); @@ -352,6 +359,28 @@ /*--------------------------------------------------------------------------*/ + suites.push( + Benchmark.Suite('`_.bindAll` iterating arguments') + .add('Lo-Dash', function() { + lodash.bindAll.apply(lodash, [bindAllObjects.pop()].concat(funcNames)); + }) + .add('Underscore', function() { + _.bindAll.apply(_, [bindAllObjects.pop()].concat(funcNames)); + }) + ); + + suites.push( + Benchmark.Suite('`_.bindAll` iterating the `object`') + .add('Lo-Dash', function() { + lodash.bindAll(bindAllObjects.pop()); + }) + .add('Underscore', function() { + _.bindAll(bindAllObjects.pop()); + }) + ); + + /*--------------------------------------------------------------------------*/ + suites.push( Benchmark.Suite('`_.each` iterating an array') .add('Lo-Dash', function() { @@ -498,6 +527,18 @@ /*--------------------------------------------------------------------------*/ + suites.push( + Benchmark.Suite('`_.functions`') + .add('Lo-Dash', function() { + lodash.functions(lodash); + }) + .add('Underscore', function() { + _.functions(lodash); + }) + ); + + /*--------------------------------------------------------------------------*/ + suites.push( Benchmark.Suite('`_.difference`') .add('Lo-Dash', function() { diff --git a/test/test.js b/test/test.js index 8771e0c08..bcd82c6d2 100644 --- a/test/test.js +++ b/test/test.js @@ -21,6 +21,9 @@ _._ || _ ); + /** Shortcut used to make object properties immutable */ + var freeze = Object.freeze; + /** Shortcut used to convert array-like objects to arrays */ var slice = [].slice; @@ -52,9 +55,10 @@ * Skips a given number of tests with a passing result. * * @private - * @param {Number} count The number of tests to skip. + * @param {Number} [count=1] The number of tests to skip. */ function skipTest(count) { + count || (count = 1); while (count--) { ok(true, 'test skipped'); } @@ -71,7 +75,7 @@ if (window.document && window.require) { equal((lodashModule || {}).moduleName, 'lodash'); } else { - skipTest(1) + skipTest() } }); @@ -79,7 +83,7 @@ if (window.document && window.require) { equal((underscoreModule || {}).moduleName, 'underscore'); } else { - skipTest(1) + skipTest() } }); @@ -87,7 +91,7 @@ if (window.document) { notDeepEqual(lodashBadShim.keys({ 'a': 1 }), []); } else { - skipTest(1); + skipTest(); } }); }()); @@ -209,6 +213,35 @@ /*--------------------------------------------------------------------------*/ + _.each(['bindAll', 'defaults', 'extend'], function(methodName) { + var func = _[methodName]; + QUnit.module('lodash.' + methodName + ' strict mode checks'); + + test('should not throw strict mode errors', function() { + var object = { 'a': null, 'b': function(){} }, + pass = true; + + if (freeze) { + freeze(object); + try { + if (methodName == 'bindAll') { + func(object); + } else { + func(object, { 'a': 1 }); + } + } catch(e) { + pass = false; + } + ok(pass); + } + else { + skipTest(); + } + }); + }); + + /*--------------------------------------------------------------------------*/ + QUnit.module('lodash.find'); (function() {