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',
'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

View File

@@ -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');

View File

@@ -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 */

View File

@@ -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()'
});

View File

@@ -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() {

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 */
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() {