Track arity of functions created by _.bind, _.bindKey, _.bindAll, _.partial, _.partialRight, _.curry, and _.wrap.

This commit is contained in:
John-David Dalton
2014-01-21 19:59:13 -08:00
parent 58ce97c0b4
commit c1c92256ea
6 changed files with 577 additions and 439 deletions

239
lodash.js
View File

@@ -11,14 +11,7 @@
/** Used as a safe reference for `undefined` in pre ES5 environments */
var undefined;
/** Used to pool arrays and objects used internally */
var arrayPool = [],
objectPool = [];
/** Used to generate unique IDs */
var idCounter = 0;
/** Used to compose bitmasks for `__bindData__` */
/** Used to compose bitmasks for wrapper metadata */
var BIND_FLAG = 1,
BIND_KEY_FLAG = 2,
CURRY_FLAG = 4,
@@ -26,23 +19,20 @@
PARTIAL_FLAG = 16,
PARTIAL_RIGHT_FLAG = 32;
/** Used as the size when optimizations are enabled for large arrays */
/** Used as the size when optimizations are enabled for arrays */
var LARGE_ARRAY_SIZE = 75;
/** Used as the max size of the `arrayPool` and `objectPool` */
var MAX_POOL_SIZE = 40;
/** Used to detect and test whitespace */
var whitespace = (
// whitespace
' \t\x0B\f\xA0\ufeff' +
/** Used as the semantic version number */
var version = '2.4.1';
// line terminators
'\n\r\u2028\u2029' +
/** Used as the property name for wrapper metadata */
var expando = '__lodash@' + version + '__';
// unicode category "Zs" space separators
'\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000'
);
/** Used to generate unique IDs */
var idCounter = 0;
/** Used to match empty string literals in compiled template source */
var reEmptyStringLeading = /\b__p \+= '';/g,
@@ -82,6 +72,22 @@
/** Used to match unescaped characters in compiled string literals */
var reUnescapedString = /['\n\r\t\u2028\u2029\\]/g;
/** Used to detect and test whitespace */
var whitespace = (
// whitespace
' \t\x0B\f\xA0\ufeff' +
// line terminators
'\n\r\u2028\u2029' +
// unicode category "Zs" space separators
'\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000'
);
/** Used to pool arrays and objects used internally */
var arrayPool = [],
objectPool = [];
/** Used to assign default `context` object properties */
var contextProps = [
'Array', 'Boolean', 'Date', 'Error', 'Function', 'Math', 'Number', 'Object',
@@ -125,7 +131,7 @@
'trailing': false
};
/** Used as the property descriptor for `__bindData__` */
/** Used as the property descriptor for wrapper metadata */
var descriptor = {
'configurable': false,
'enumerable': false,
@@ -1057,13 +1063,13 @@
* sets its meta data.
*
* @private
* @param {Array} bindData The bind data array.
* @param {Array} data The metadata array.
* @returns {Function} Returns the new bound function.
*/
function baseBind(bindData) {
var func = bindData[0],
partialArgs = bindData[2],
thisArg = bindData[4];
function baseBind(data) {
var func = data[0],
thisArg = data[3],
partialArgs = data[4];
function bound() {
// `Function#bind` spec
@@ -1085,7 +1091,7 @@
}
return func.apply(thisArg, args || arguments);
}
setBindData(bound, bindData);
setData(bound, data);
return bound;
}
@@ -1225,26 +1231,26 @@
if (typeof thisArg == 'undefined' || !('prototype' in func)) {
return func;
}
var bindData = func.__bindData__;
if (typeof bindData == 'undefined') {
var data = func[expando];
if (typeof data == 'undefined') {
if (support.funcNames) {
bindData = !func.name;
data = !func.name;
}
bindData = bindData || !support.funcDecomp;
if (!bindData) {
data = data || !support.funcDecomp;
if (!data) {
var source = fnToString.call(func);
if (!support.funcNames) {
bindData = !reFuncName.test(source);
data = !reFuncName.test(source);
}
if (!bindData) {
if (!data) {
// checks if `func` references the `this` keyword and stores the result
bindData = reThis.test(source);
setBindData(func, bindData);
data = reThis.test(source);
setData(func, data);
}
}
}
// exit early if there are no `this` references or `func` is bound
if (bindData === false || (bindData !== true && bindData[1] & BIND_FLAG)) {
if (data === false || (data !== true && data[1] & BIND_FLAG)) {
return func;
}
switch (argCount) {
@@ -1269,16 +1275,16 @@
* sets its meta data.
*
* @private
* @param {Array} bindData The bind data array.
* @param {Array} data The metadata array.
* @returns {Function} Returns the new function.
*/
function baseCreateWrapper(bindData) {
var func = bindData[0],
bitmask = bindData[1],
partialArgs = bindData[2],
partialRightArgs = bindData[3],
thisArg = bindData[4],
arity = bindData[5];
function baseCreateWrapper(data) {
var func = data[0],
bitmask = data[1],
arity = data[2],
thisArg = data[3],
partialArgs = data[4],
partialRightArgs = data[5];
var isBind = bitmask & BIND_FLAG,
isBindKey = bitmask & BIND_KEY_FLAG,
@@ -1297,10 +1303,15 @@
if (partialRightArgs) {
push.apply(args, partialRightArgs);
}
if (isCurry && args.length < arity) {
var argsLength = arguments.length;
if (isCurry && argsLength < arity) {
bitmask |= PARTIAL_FLAG;
bitmask &= ~PARTIAL_RIGHT_FLAG;
return baseCreateWrapper([func, (isCurryBound ? bitmask : bitmask & ~(BIND_FLAG | BIND_KEY_FLAG)), args, null, thisArg, arity]);
bitmask &= ~PARTIAL_RIGHT_FLAG
if (!isCurryBound) {
bitmask &= ~(BIND_FLAG | BIND_KEY_FLAG);
}
var newArity = nativeMax(0, arity - argsLength);
return baseCreateWrapper([func, bitmask, newArity, thisArg, args]);
}
}
args || (args = arguments);
@@ -1314,7 +1325,7 @@
}
return func.apply(thisBinding, args);
}
setBindData(bound, bindData);
setData(bound, data);
return bound;
}
@@ -1793,25 +1804,23 @@
* @param {Function|string} func The function or method name to reference.
* @param {number} bitmask The bitmask of flags to compose.
* The bitmask may be composed of the following flags:
* 1 - `_.bind`
* 2 - `_.bindKey`
* 4 - `_.curry`
* 8 - `_.curry` (bound)
* 1 - `_.bind`
* 2 - `_.bindKey`
* 4 - `_.curry`
* 8 - `_.curry` (bound)
* 16 - `_.partial`
* 32 - `_.partialRight`
* @param {number} [arity] The arity of `func`.
* @param {*} [thisArg] The `this` binding of `func`.
* @param {Array} [partialArgs] An array of arguments to prepend to those
* provided to the new function.
* @param {Array} [partialRightArgs] An array of arguments to append to those
* provided to the new function.
* @param {*} [thisArg] The `this` binding of `func`.
* @param {number} [arity] The arity of `func`.
* @returns {Function} Returns the new function.
*/
function createWrapper(func, bitmask, partialArgs, partialRightArgs, thisArg, arity) {
function createWrapper(func, bitmask, arity, thisArg, partialArgs, partialRightArgs) {
var isBind = bitmask & BIND_FLAG,
isBindKey = bitmask & BIND_KEY_FLAG,
isCurry = bitmask & CURRY_FLAG,
isCurryBound = bitmask & CURRY_BOUND_FLAG,
isPartial = bitmask & PARTIAL_FLAG,
isPartialRight = bitmask & PARTIAL_RIGHT_FLAG;
@@ -1819,50 +1828,69 @@
throw new TypeError;
}
if (isPartial && !partialArgs.length) {
bitmask &= ~16;
bitmask &= ~PARTIAL_FLAG;
isPartial = partialArgs = false;
}
if (isPartialRight && !partialRightArgs.length) {
bitmask &= ~32;
bitmask &= ~PARTIAL_RIGHT_FLAG;
isPartialRight = partialRightArgs = false;
}
var bindData = func && func.__bindData__;
if (bindData && bindData !== true) {
// clone `bindData`
bindData = slice(bindData);
if (bindData[2]) {
bindData[2] = slice(bindData[2]);
var data = !isBindKey && func[expando];
if (data && data !== true) {
// shallow clone `data`
data = slice(data);
// clone partial left arguments
if (data[4]) {
data[4] = slice(data[4]);
}
if (bindData[3]) {
bindData[3] = slice(bindData[3]);
// clone partial right arguments
if (data[5]) {
data[5] = slice(data[5]);
}
// set `thisBinding` is not previously bound
if (isBind && !(bindData[1] & BIND_FLAG)) {
bindData[4] = thisArg;
// set arity if provided
if (typeof arity == 'number') {
data[2] = arity;
}
// set if previously bound but not currently (subsequent curried functions)
if (!isBind && bindData[1] & BIND_FLAG) {
bitmask |= 8;
// set `thisArg` if not previously bound
var bound = data[1] & BIND_FLAG;
if (isBind && !bound) {
data[3] = thisArg;
}
// set curried arity if not yet set
if (isCurry && !(bindData[1] & CURRY_FLAG)) {
bindData[5] = arity;
// set if currying a bound function
if (!isBind && bound) {
bitmask |= CURRY_BOUND_FLAG;
}
// append partial left arguments
if (isPartial) {
push.apply(bindData[2] || (bindData[2] = []), partialArgs);
if (data[4]) {
push.apply(data[4], partialArgs);
} else {
data[4] = partialArgs;
}
}
// append partial right arguments
// prepend partial right arguments
if (isPartialRight) {
unshift.apply(bindData[3] || (bindData[3] = []), partialRightArgs);
if (data[5]) {
unshift.apply(data[5], partialRightArgs);
} else {
data[5] = partialRightArgs;
}
}
// merge flags
bindData[1] |= bitmask;
return createWrapper.apply(null, bindData);
data[1] |= bitmask;
return createWrapper.apply(null, data);
}
if (arity == null) {
arity = isBindKey ? 0 : func.length;
} else if (arity < 0) {
arity = 0;
}
// fast path for `_.bind`
var creater = (bitmask == BIND_FLAG || bitmask == (BIND_FLAG | PARTIAL_FLAG)) ? baseBind : baseCreateWrapper;
return creater([func, bitmask, partialArgs, partialRightArgs, thisArg, arity]);
data = [func, bitmask, arity, thisArg, partialArgs, partialRightArgs];
return (bitmask == BIND_FLAG || bitmask == (BIND_FLAG | PARTIAL_FLAG))
? baseBind(data)
: baseCreateWrapper(data);
}
/**
@@ -1919,15 +1947,15 @@
}
/**
* Sets `this` binding data on a given function.
* Sets wrapper metadata on a given function.
*
* @private
* @param {Function} func The function to set data on.
* @param {Array} value The data array to set.
*/
var setBindData = !defineProperty ? noop : function(func, value) {
var setData = !defineProperty ? noop : function(func, value) {
descriptor.value = value;
defineProperty(func, '__bindData__', descriptor);
defineProperty(func, expando, descriptor);
};
/**
@@ -4516,9 +4544,14 @@
* // => 'hi fred'
*/
function bind(func, thisArg) {
return arguments.length > 2
? createWrapper(func, BIND_FLAG | PARTIAL_FLAG, slice(arguments, 2), null, thisArg)
: createWrapper(func, BIND_FLAG, null, null, thisArg);
if (arguments.length < 3) {
return createWrapper(func, BIND_FLAG, null, thisArg);
}
var arity = func && (func[expando] ? func[expando][2] : func.length),
partialArgs = slice(arguments, 2);
arity -= partialArgs.length;
return createWrapper(func, BIND_FLAG | PARTIAL_FLAG, arity, thisArg, partialArgs);
}
/**
@@ -4554,7 +4587,7 @@
while (++index < length) {
var key = funcs[index];
object[key] = createWrapper(object[key], BIND_FLAG, null, null, object);
object[key] = createWrapper(object[key], BIND_FLAG, null, object);
}
return object;
}
@@ -4595,9 +4628,9 @@
* // => 'hiya fred!'
*/
function bindKey(object, key) {
return arguments.length > 2
? createWrapper(key, BIND_FLAG | BIND_KEY_FLAG | PARTIAL_FLAG, slice(arguments, 2), null, object)
: createWrapper(key, BIND_FLAG | BIND_KEY_FLAG, null, null, object);
return arguments.length < 3
? createWrapper(key, BIND_FLAG | BIND_KEY_FLAG, null, object)
: createWrapper(key, BIND_FLAG | BIND_KEY_FLAG | PARTIAL_FLAG, null, object, slice(arguments, 2));
}
/**
@@ -4681,8 +4714,10 @@
* // => 6
*/
function curry(func, arity) {
arity = typeof arity == 'number' ? arity : (+arity || func.length);
return createWrapper(func, CURRY_FLAG, null, null, null, arity);
if (typeof arity != 'number') {
arity = +arity || null;
}
return createWrapper(func, CURRY_FLAG, arity);
}
/**
@@ -4984,7 +5019,11 @@
* // => 'hi fred'
*/
function partial(func) {
return createWrapper(func, PARTIAL_FLAG, slice(arguments, 1));
var arity = func && (func[expando] ? func[expando][2] : func.length),
partialArgs = slice(arguments, 1);
arity -= partialArgs.length;
return createWrapper(func, PARTIAL_FLAG, arity, null, partialArgs);
}
/**
@@ -5018,7 +5057,11 @@
* // => { '_': _, 'jq': $ }
*/
function partialRight(func) {
return createWrapper(func, PARTIAL_RIGHT_FLAG, null, slice(arguments, 1));
var arity = func && (func[expando] ? func[expando][2] : func.length),
partialRightArgs = slice(arguments, 1);
arity -= partialRightArgs.length;
return createWrapper(func, PARTIAL_RIGHT_FLAG, arity, null, null, partialRightArgs);
}
/**
@@ -5094,7 +5137,7 @@
* // => '<p>fred, barney, &amp; pebbles</p>'
*/
function wrap(value, wrapper) {
return createWrapper(wrapper, PARTIAL_FLAG, [value]);
return createWrapper(wrapper, PARTIAL_FLAG, null, null, [value]);
}
/*--------------------------------------------------------------------------*/
@@ -7466,7 +7509,7 @@
* @memberOf _
* @type string
*/
lodash.VERSION = '2.4.1';
lodash.VERSION = version;
// add "Chaining" functions to the wrapper
lodash.prototype.chain = wrapperChain;