Avoid enforcing strict mode in _.defaults, _.extend, and _.bindAll and add benchmarks for _.bindAll and _.functions. [closes #45]

Former-commit-id: 1bb0b5155d3ae46052b4a06cb538dff307e8ec5e
This commit is contained in:
John-David Dalton
2012-07-12 15:25:18 -04:00
parent 9530efb4d4
commit 3386c2a7a5
6 changed files with 137 additions and 63 deletions

View File

@@ -223,13 +223,13 @@
'inLoop', 'inLoop',
'init', 'init',
'isKeysFast', 'isKeysFast',
'iteratee',
'object', 'object',
'objectBranch', 'objectBranch',
'noCharByIndex', 'noCharByIndex',
'shadowed', 'shadowed',
'top', 'top',
'useHas' 'useHas',
'useStrict'
]; ];
/** Collections of method names */ /** Collections of method names */
@@ -840,13 +840,7 @@
// prepend data object references to property names to avoid having to // prepend data object references to property names to avoid having to
// use a with-statement // use a with-statement
iteratorOptions.forEach(function(property) { iteratorOptions.forEach(function(property) {
if (property == 'iteratee') { snippet = snippet.replace(RegExp('([^\\w.])\\b' + property + '\\b', 'g'), '$1obj.' + property);
// 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);
}
}); });
// remove unnecessary code // remove unnecessary code

View File

@@ -34,9 +34,6 @@
// move vars exposed by Closure Compiler into the IIFE // move vars exposed by Closure Compiler into the IIFE
source = source.replace(/^([^(\n]+)\s*(\(function[^)]+\){)/, '$2$1'); 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) // unescape properties (i.e. foo["bar"] => foo.bar)
source = source.replace(/(\w)\["([^."]+)"\]/g, '$1.$2'); source = source.replace(/(\w)\["([^."]+)"\]/g, '$1.$2');

View File

@@ -10,17 +10,20 @@
'accumulator', 'accumulator',
'args', 'args',
'arrayClass', 'arrayClass',
'bind',
'callback', 'callback',
'className', 'className',
'collection', 'collection',
'compareAscending', 'compareAscending',
'ctor', 'ctor',
'funcClass', 'funcClass',
'funcs',
'hasOwnProperty', 'hasOwnProperty',
'identity', 'identity',
'index', 'index',
'isFunc', 'isFunc',
'iteratee', 'iteratee',
'iterateeIndex',
'iteratorBind', 'iteratorBind',
'length', 'length',
'methodName', 'methodName',
@@ -36,8 +39,6 @@
'result', 'result',
'skipProto', 'skipProto',
'slice', 'slice',
'source',
'sourceIndex',
'stringClass', 'stringClass',
'target', 'target',
'thisArg', 'thisArg',
@@ -58,13 +59,13 @@
'inLoop', 'inLoop',
'init', 'init',
'isKeysFast', 'isKeysFast',
'iteratee',
'object', 'object',
'objectBranch', 'objectBranch',
'noCharByIndex', 'noCharByIndex',
'shadowed', 'shadowed',
'top', 'top',
'useHas' 'useHas',
'useStrict'
]; ];
/** Used to minify variables and string values to a single character */ /** Used to minify variables and string values to a single character */

View File

@@ -266,13 +266,17 @@
* @returns {String} Returns the interpolated text. * @returns {String} Returns the interpolated text.
*/ */
var iteratorTemplate = template( 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 // 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 // add code to exit early or do so if the first argument is falsey
'<%= exit %>;\n' + '<%= exit %>;\n' +
// add code after the exit snippet but before the iteration branches // add code after the exit snippet but before the iteration branches
'<%= top %>;\n' + '<%= top %>;\n' +
'var index, iteratee = <%= iteratee %>;\n' +
// the following branch is for iterating arrays and array-like objects // the following branch is for iterating arrays and array-like objects
'<% if (arrayBranch) { %>' + '<% if (arrayBranch) { %>' +
@@ -389,14 +393,14 @@
/** Reusable iterator options for `defaults` and `extend` */ /** Reusable iterator options for `defaults` and `extend` */
var extendIteratorOptions = { var extendIteratorOptions = {
'useHas': false,
'useStrict': false,
'args': 'object', 'args': 'object',
'init': 'object', 'init': 'object',
'top': 'top':
'for (var source, sourceIndex = 1, length = arguments.length; sourceIndex < length; sourceIndex++) {\n' + 'for (var iterateeIndex = 1, length = arguments.length; iterateeIndex < length; iterateeIndex++) {\n' +
' source = arguments[sourceIndex];\n' + ' iteratee = arguments[iterateeIndex];\n' +
(hasDontEnumBug ? ' if (source) {' : ''), (hasDontEnumBug ? ' if (iteratee) {' : ''),
'iteratee': 'source',
'useHas': false,
'inLoop': 'result[index] = iteratee[index]', 'inLoop': 'result[index] = iteratee[index]',
'bottom': (hasDontEnumBug ? ' }\n' : '') + '}' 'bottom': (hasDontEnumBug ? ' }\n' : '') + '}'
}; };
@@ -443,6 +447,12 @@
* @private * @private
* @param {Object} [options1, options2, ...] The compile options objects. * @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 * args - A string of comma separated arguments the iteration function will
* accept. * accept.
* *
@@ -457,12 +467,6 @@
* beforeLoop - A string or object containing an "array" or "object" property * beforeLoop - A string or object containing an "array" or "object" property
* of code to execute before the array or object loops. * 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 * inLoop - A string or object containing an "array" or "object" property
* of code to execute in the array or object loops. * of code to execute in the array or object loops.
* *
@@ -506,14 +510,14 @@
} }
// set additional template `data` values // set additional template `data` values
var args = data.args, var args = data.args,
firstArg = /^[^,]+/.exec(args)[0], firstArg = /^[^,]+/.exec(args)[0];
iteratee = (data.iteratee = data.iteratee || firstArg);
data.firstArg = firstArg; data.firstArg = firstArg;
data.hasDontEnumBug = hasDontEnumBug; data.hasDontEnumBug = hasDontEnumBug;
data.isKeysFast = isKeysFast; data.isKeysFast = isKeysFast;
data.shadowed = shadowed; data.shadowed = shadowed;
data.useHas = data.useHas !== false; data.useHas = data.useHas !== false;
data.useStrict = data.useStrict !== false;
if (!('noCharByIndex' in data)) { if (!('noCharByIndex' in data)) {
data.noCharByIndex = noCharByIndex; data.noCharByIndex = noCharByIndex;
@@ -526,14 +530,14 @@
} }
// create the function factory // create the function factory
var factory = Function( var factory = Function(
'arrayClass, compareAscending, funcClass, hasOwnProperty, identity, ' + 'arrayClass, bind, compareAscending, funcClass, hasOwnProperty, identity, ' +
'iteratorBind, objectTypes, nativeKeys, propertyIsEnumerable, ' + 'iteratorBind, objectTypes, nativeKeys, propertyIsEnumerable, slice, ' +
'slice, stringClass, toString', 'stringClass, toString',
'"use strict"; return function(' + args + ') {\n' + iteratorTemplate(data) + '\n}' 'return function(' + args + ') {\n' + iteratorTemplate(data) + '\n}'
); );
// return the compiled function // return the compiled function
return factory( return factory(
arrayClass, compareAscending, funcClass, hasOwnProperty, identity, arrayClass, bind, compareAscending, funcClass, hasOwnProperty, identity,
iteratorBind, objectTypes, nativeKeys, propertyIsEnumerable, slice, iteratorBind, objectTypes, nativeKeys, propertyIsEnumerable, slice,
stringClass, toString stringClass, toString
); );
@@ -2058,19 +2062,23 @@
* jQuery('#lodash_button').on('click', buttonView.onClick); * jQuery('#lodash_button').on('click', buttonView.onClick);
* // => When the button is clicked, `this.label` will have the correct value * // => When the button is clicked, `this.label` will have the correct value
*/ */
function bindAll(object) { var bindAll = createIterator({
var funcs = arguments, 'useHas': false,
index = 1; 'useStrict': false,
'args': 'object',
if (funcs.length == 1) { 'init': 'object',
index = 0; 'top':
funcs = functions(object); 'var funcs = arguments,\n' +
} ' length = funcs.length;\n' +
for (var length = funcs.length; index < length; index++) { 'if (length > 1) {\n' +
object[funcs[index]] = bind(object[funcs[index]], object); ' for (var index = 1; index < length; index++)\n' +
} ' result[funcs[index]] = bind(result[funcs[index]], result);\n' +
return object; ' 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, * Creates a new function that is the composition of the passed functions,
@@ -2496,9 +2504,9 @@
* // => ['all', 'any', 'bind', 'bindAll', 'clone', 'compact', 'compose', ...] * // => ['all', 'any', 'bind', 'bindAll', 'clone', 'compact', 'compose', ...]
*/ */
var functions = createIterator({ var functions = createIterator({
'useHas': false,
'args': 'object', 'args': 'object',
'init': '[]', 'init': '[]',
'useHas': false,
'inLoop': 'if (toString.call(iteratee[index]) == funcClass) result.push(index)', 'inLoop': 'if (toString.call(iteratee[index]) == funcClass) result.push(index)',
'bottom': 'result.sort()' 'bottom': 'result.sort()'
}); });

View File

@@ -82,12 +82,28 @@
lodash = window.lodash; lodash = window.lodash;
var length = 20, var length = 20,
funcNames = lodash.functions(lodash),
numbers = [], numbers = [],
object = {}, object = {},
fourNumbers = [5, 25, 10, 30], fourNumbers = [5, 25, 10, 30],
nestedNumbers = [1, [2], [3, [[4]]]], nestedNumbers = [1, [2], [3, [[4]]]],
twoNumbers = [12, 21]; 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 ctor = function() { };
var func = function(greeting, punctuation) { var func = function(greeting, punctuation) {
@@ -219,15 +235,6 @@
}; };
var words = _.keys(wordToNumber).slice(0, length); 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( suites.push(
Benchmark.Suite('`_.each` iterating an array') Benchmark.Suite('`_.each` iterating an array')
.add('Lo-Dash', function() { .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( suites.push(
Benchmark.Suite('`_.difference`') Benchmark.Suite('`_.difference`')
.add('Lo-Dash', function() { .add('Lo-Dash', function() {

View File

@@ -21,6 +21,9 @@
_._ || _ _._ || _
); );
/** Shortcut used to make object properties immutable */
var freeze = Object.freeze;
/** Shortcut used to convert array-like objects to arrays */ /** Shortcut used to convert array-like objects to arrays */
var slice = [].slice; var slice = [].slice;
@@ -52,9 +55,10 @@
* Skips a given number of tests with a passing result. * Skips a given number of tests with a passing result.
* *
* @private * @private
* @param {Number} count The number of tests to skip. * @param {Number} [count=1] The number of tests to skip.
*/ */
function skipTest(count) { function skipTest(count) {
count || (count = 1);
while (count--) { while (count--) {
ok(true, 'test skipped'); ok(true, 'test skipped');
} }
@@ -71,7 +75,7 @@
if (window.document && window.require) { if (window.document && window.require) {
equal((lodashModule || {}).moduleName, 'lodash'); equal((lodashModule || {}).moduleName, 'lodash');
} else { } else {
skipTest(1) skipTest()
} }
}); });
@@ -79,7 +83,7 @@
if (window.document && window.require) { if (window.document && window.require) {
equal((underscoreModule || {}).moduleName, 'underscore'); equal((underscoreModule || {}).moduleName, 'underscore');
} else { } else {
skipTest(1) skipTest()
} }
}); });
@@ -87,7 +91,7 @@
if (window.document) { if (window.document) {
notDeepEqual(lodashBadShim.keys({ 'a': 1 }), []); notDeepEqual(lodashBadShim.keys({ 'a': 1 }), []);
} else { } 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'); QUnit.module('lodash.find');
(function() { (function() {