-
-
Save jpcody/1067614 to your computer and use it in GitHub Desktop.
// self-executing function that will leak _ and its method/props into the global scope | |
(function() { | |
// access to global scope throughout function | |
var root = this; | |
var previousUnderscore = root._; | |
var breaker = {}; | |
var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; | |
var | |
slice = ArrayProto.slice, | |
unshift = ArrayProto.unshift, | |
toString = ObjProto.toString, | |
hasOwnProperty = ObjProto.hasOwnProperty; | |
var | |
nativeForEach = ArrayProto.forEach, | |
nativeMap = ArrayProto.map, | |
nativeReduce = ArrayProto.reduce, | |
nativeReduceRight = ArrayProto.reduceRight, | |
nativeFilter = ArrayProto.filter, | |
nativeEvery = ArrayProto.every, | |
nativeSome = ArrayProto.some, | |
nativeIndexOf = ArrayProto.indexOf, | |
nativeLastIndexOf = ArrayProto.lastIndexOf, | |
nativeIsArray = Array.isArray, | |
nativeKeys = Object.keys, | |
nativeBind = FuncProto.bind; | |
var _ = function(obj) { return new wrapper(obj); }; | |
if (typeof module !== 'undefined' && module.exports) { | |
module.exports = _; | |
_._ = _; | |
} else { | |
root['_'] = _; | |
} | |
_.VERSION = '1.1.6'; | |
/** | |
* ITERATOR UTILITIES | |
* | |
* These utilities iterate over a collection of items, executing an iterator each | |
* time, and returning a result. Each method uses the following params, and | |
* additional params and the return values are noted. | |
* | |
* @param obj - the collection we'll be iterating over | |
* @param iterator - the function we'd like to call on each item | |
* @param context - what should this be set to in our iterator? | |
*/ | |
/** | |
* EACH | |
* | |
* A cornerstone function that will be used many other places. Take a collection, and | |
* perform something on each member. | |
*/ | |
// provide a local variable as well as a global variable in a single assignment | |
var each = _.each = _.forEach = function(obj, iterator, context) { | |
// return at the top of the function so you don't have to fiddle with execution | |
if (obj == null) return; | |
if (nativeForEach && obj.forEach === nativeForEach) { | |
obj.forEach(iterator, context); | |
} else if (_.isNumber(obj.length)) { | |
for (var i = 0, l = obj.length; i < l; i++) { | |
// TODO : Er, how is the iterator ever actually called to return a value? | |
if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return; | |
} | |
} else { | |
for (var key in obj) { | |
if (hasOwnProperty.call(obj, key)) { | |
if (iterator.call(context, obj[key], key, obj) === breaker) return; | |
} | |
} | |
} | |
}; | |
/** | |
* MAP | |
* | |
* Take a collection, call an iterator on each item, and return a new collection. | |
* | |
* @return results - the new array that has been generated | |
*/ | |
_.map = function(obj, iterator, context) { | |
var results = []; | |
if (obj == null) return results; | |
if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); | |
each(obj, function(value, index, list) { | |
// take advantage of zero-based arrays in conjunction with length to add to the | |
// end of an array | |
results[results.length] = iterator.call(context, value, index, list); | |
}); | |
return results; | |
}; | |
/** | |
* REDUCE, FOLDL, INJECT | |
* | |
* Take a collection, and one by one, use an iterator to compare each item to the | |
* previous value. Each reduction yields a new value, which will then be compared to | |
* the next value, resulting in a new collection. | |
* | |
* @param memo - initial state of the reduction to be compared with the first item | |
* @return memo - reduced state of initial object | |
*/ | |
_.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { | |
// use void 0 because undefined might be redefined by someone else. if they're an ass. | |
var initial = memo !== void 0; | |
if (obj == null) obj = []; | |
if (nativeReduce && obj.reduce === nativeReduce) { | |
if (context) iterator = _.bind(iterator, context); | |
return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); | |
} | |
each(obj, function(value, index, list) { | |
if (!initial && index === 0) { | |
memo = value; | |
initial = true; | |
} else { | |
memo = iterator.call(context, memo, value, index, list); | |
} | |
}); | |
if (!initial) throw new TypeError("Reduce of empty array with no initial value"); | |
return memo; | |
}; | |
/** | |
* REDUCERIGHT, FOLDR | |
* | |
* The right-to-left version of reduce. | |
* | |
* @return memo - reducded state of initial object | |
*/ | |
_.reduceRight = _.foldr = function(obj, iterator, memo, context) { | |
if (obj == null) obj = []; | |
if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { | |
if (context) iterator = _.bind(iterator, context); | |
return memo !== void 0 ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); | |
} | |
// rather than write a new method to reduce from the right, just reverse our array | |
// and reduce from the left | |
var reversed = (_.isArray(obj) ? obj.slice() : _.toArray(obj)).reverse(); | |
return _.reduce(reversed, iterator, memo, context); | |
}; | |
/** | |
* FIND, DETECT | |
* | |
* Go through a collection and look for the first truthy value. | |
* | |
* @return result - the first item that caused our iterator to return true | |
*/ | |
_.find = _.detect = function(obj, iterator, context) { | |
var result; | |
// it's any on steroids, so use any if you just need to return true or false, or | |
// this if you need to know the value that caused a true result | |
any(obj, function(value, index, list) { | |
// we only care about this if it returns a truthy value, so just do the assign | |
// and return if it evaluates to true | |
if (iterator.call(context, value, index, list)) { | |
result = value; | |
return true; | |
} | |
}); | |
return result; | |
}; | |
/** | |
* FILTER, SELECT | |
* | |
* Go through a collection and filter out all falsy values. | |
* | |
* @return results - new item consisting of all truthy items in original obj | |
*/ | |
_.filter = _.select = function(obj, iterator, context) { | |
var results = []; | |
// at the least, return an empty array | |
if (obj == null) return results; | |
if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context); | |
each(obj, function(value, index, list) { | |
if (iterator.call(context, value, index, list)) results[results.length] = value; | |
}); | |
return results; | |
}; | |
/** | |
* REJECT | |
* | |
* Go through a collection and filter out all truthy values. | |
* @return results - new item consisting of all falsy items in original obj | |
*/ | |
_.reject = function(obj, iterator, context) { | |
var results = []; | |
if (obj == null) return results; | |
each(obj, function(value, index, list) { | |
if (!iterator.call(context, value, index, list)) results[results.length] = value; | |
}); | |
return results; | |
}; | |
/** | |
* EVERY, ALL | |
* | |
* Check if all values in a collection are truthy. | |
* | |
* @return result - boolean whether every result was truthy | |
*/ | |
_.every = _.all = function(obj, iterator, context) { | |
var result = true; | |
if (obj == null) return result; | |
if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context); | |
each(obj, function(value, index, list) { | |
// re-assign result and call our iterator to check its truthiness. during | |
// ._each, if using the native forEach, there is no way to break out of | |
// iteration. so on every iteration, if result is already false, we won't run | |
// our iterator. if our iterator is true, then result will be assigned to the | |
// value of our iterator.call function. var foo = foo && bar() basically uses | |
// foo as a guard for bar() and only executes it if foo is truthy. | |
if (!(result = result && iterator.call(context, value, index, list))) return breaker; | |
}); | |
return result; | |
}; | |
/** | |
* ANY, SOME | |
* | |
* Check if any value in a collection is truthy. | |
* | |
* @return result - boolean whether any result was truthy | |
*/ | |
var any = _.some = _.any = function(obj, iterator, context) { | |
// if we don't have an iterator, then assign a basic function that accepts a value | |
// and returns a value so we have something to call | |
iterator || (iterator = _.identity); | |
var result = false; | |
if (obj == null) return result; | |
if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); | |
each(obj, function(value, index, list) { | |
// here we don't want a fallback of result = result && … because we want to know | |
// if any values are falsy, we we must examine each and every iterator call to | |
// be sure all are truthy | |
if (result = iterator.call(context, value, index, list)) return breaker; | |
}); | |
return result; | |
}; | |
/** | |
* INCLUDE, CONTAINS | |
* | |
* Check if a collection contains a given value. | |
* | |
* @param target - item we are looking for in our object | |
* @return found - boolean whether the target was found | |
*/ | |
_.include = _.contains = function(obj, target) { | |
var found = false; | |
if (obj == null) return found; | |
if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; | |
any(obj, function(value) { | |
// go ahead and assign found so we return the correct value in a few lines | |
if (found = value === target) return true; | |
}); | |
return found; | |
}; | |
/** | |
* INVOKE | |
* | |
* Call the method on each value in the obj collection. It's like _.map, except you | |
* can pass arguments on your function you'd like to invoke on each object, and you | |
* don't really expect a new array in return. | |
* | |
* @param method - method to call on each item in the obj collection | |
* @return - newly mapped collection | |
*/ | |
_.invoke = function(obj, method) { | |
// accept any number of arguments, then slice of obj and method to just get an | |
// array of passed arguments | |
var args = slice.call(arguments, 2); | |
return _.map(obj, function(value) { | |
// TODO : when would method.call ever be falsy? | |
return (method.call ? method || value : value[method]).apply(value, args); | |
}); | |
}; | |
/** | |
* PLUCK | |
* | |
* Yank a single value out of an array at a given key. | |
* | |
* @param key - the key of the value we want to get | |
* @return - newly mapped array of a single key -> value pair | |
*/ | |
_.pluck = function(obj, key) { | |
return _.map(obj, function(value){ return value[key]; }); | |
}; | |
/** | |
* MAX | |
* | |
* Go through either an array or numbers or an array of objects. If an array of | |
* numbers, return the largest number. If an array of objects, specify an index on | |
* which to search, and return the largest object. | |
* | |
* return - the object which contains a max value or the max value in an array | |
*/ | |
_.max = function(obj, iterator, context) { | |
if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj); | |
// we're going to be comparing against result.computed, and we want our first | |
// check to pass, so we need to set this to something really small. | |
var result = {computed : -Infinity}; | |
each(obj, function(value, index, list) { | |
// for each object we're iterating over, call our comparison iterator and assign | |
// its result to computed. | |
var computed = iterator ? iterator.call(context, value, index, list) : value; | |
// use a comparison operator as a guard and assign only if appropriate. for each | |
// object we're iterating over, if our computed result is larger than our | |
// previously computed result, then we assign it to result. | |
computed >= result.computed && (result = {value : value, computed : computed}); | |
}); | |
return result.value; | |
}; | |
/** | |
* MIN | |
* | |
* The opposite of _.max | |
*/ | |
_.min = function(obj, iterator, context) { | |
if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj); | |
// like above, but checking for less than, so initiate to a huge value | |
var result = {computed : Infinity}; | |
each(obj, function(value, index, list) { | |
var computed = iterator ? iterator.call(context, value, index, list) : value; | |
computed < result.computed && (result = {value : value, computed : computed}); | |
}); | |
return result.value; | |
}; | |
/** | |
* SORTBY | |
* | |
* Sort a collection of objects, and provide an optional function to determine how | |
* they get sorted. | |
* | |
* @return - new collection, sorted by given criterion | |
*/ | |
_.sortBy = function(obj, iterator, context) { | |
// map creates an array of objects with their original value and the result of | |
// their iterator. this is important because we eventually need to return their | |
// original value. | |
return _.pluck(_.map(obj, function(value, index, list) { | |
return { | |
value : value, | |
criteria : iterator.call(context, value, index, list) | |
}; | |
}).sort(function(left, right) { | |
var a = left.criteria, b = right.criteria; | |
// if a is less than b return -1. if not, if a is greater than b, return 1, if | |
// not, return 0. just some order of operations trickery going on here that | |
// emulates native sort behavior for arrays because we are sorting properties of | |
// arrays while needing to preserve the original values so we can pluck them. | |
return a < b ? -1 : a > b ? 1 : 0; | |
}), 'value'); | |
}; | |
/** | |
* GROUPBY | |
* | |
* Group a collection's objects by the criteria passed in an iterator. | |
* | |
* @return - new collection, arranged into groups | |
*/ | |
_.groupBy = function(obj, iterator) { | |
var result = {}; | |
each(obj, function(value, index) { | |
var key = iterator(value, index); | |
// check if we've already created our key that gets returned, and if so, push to | |
// it. else create it and push to it. these form groups. | |
(result[key] || (result[key] = [])).push(value); | |
}); | |
return result; | |
}; | |
/** | |
* SORTEDINDEX | |
* | |
* If we were to sort our collection, then stick this item in it, in what position | |
* would we need to do the insert? | |
* | |
* @param array - the collection we want to check in | |
* @param obj - the item we want to check with | |
*/ | |
_.sortedIndex = function(array, obj, iterator) { | |
// TODO : i understand what is going on in this method, but not why. is it | |
// expecting a pre-sorted index? | |
// _.sortedIndex([5, 10, 15, 25, 30], 20) yields expected results | |
// _.sortedIndex([5, 10, 15, 25, 30].reverse(), 20) does not | |
// what is the real value of this method? | |
iterator || (iterator = _.identity); | |
var low = 0, high = array.length; | |
while (low < high) { | |
// a bitwise shift of 1 to the right result in a halving of the value, that is: | |
// 0110 (6) becomes 1100 (12), so low + high and a shift results in the mean, | |
// rounded down to the next lowest integer | |
var mid = (low + high) >> 1; | |
// conditionally narrow down our array to hone in on the precise index our | |
// object should be inserted at | |
iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid; | |
} | |
return low; | |
}; | |
/** | |
* TOARRAY | |
* | |
* Try a bunch of different ways to see if something is an array or is a group of | |
* arguments. If not, just map that sucker. | |
* | |
* @param iterator - collection of iterable objects | |
*/ | |
_.toArray = function(iterable) { | |
if (!iterable) return []; | |
// TODO : s this a check for whether prototype.js is available with its toArray | |
// method? Seems risky that someone could have implemented a crappy toArray on | |
// their own. | |
if (iterable.toArray) return iterable.toArray(); | |
if (_.isArray(iterable)) return iterable; | |
if (_.isArguments(iterable)) return slice.call(iterable); | |
// map the object to itself | |
return _.values(iterable); | |
}; | |
/** | |
* SIZE | |
* | |
* Check the size of a thing. | |
*/ | |
_.size = function(obj) { | |
return _.toArray(obj).length; | |
}; | |
/** | |
* FIRST | |
* | |
* Get the first n arguments of an array. | |
*/ | |
_.first = _.head = function(array, n, guard) { | |
// if we received nothing or we have a guard, return the first item, else slice | |
// our array to the index. interesting here is order of operations. | |
return (n != null) && !guard ? slice.call(array, 0, n) : array[0]; | |
}; | |
/** | |
* REST | |
* | |
* Get the rest of the values from a point onward, defaults to 1. | |
*/ | |
_.rest = _.tail = function(array, index, guard) { | |
return slice.call(array, (index == null) || guard ? 1 : index); | |
}; | |
/** | |
* LAST | |
* | |
* Get the last value in an array. | |
*/ | |
_.last = function(array) { | |
return array[array.length - 1]; | |
}; | |
/** | |
* COMPACT | |
* | |
* Get rid of all the falsy values | |
*/ | |
_.compact = function(array) { | |
// double bang results in in a boolean version of the value | |
return _.filter(array, function(value){ return !!value; }); | |
}; | |
/** | |
* FLATTEN | |
* | |
* Given a multi-dimensional array, reduce it to a single array. | |
*/ | |
_.flatten = function(array) { | |
// this reduction is passing its third argument to the first argument of its | |
// own iterator function | |
return _.reduce(array, function(memo, value) { | |
// use a recursive function to flatten any internal arrays | |
if (_.isArray(value)) return memo.concat(_.flatten(value)); | |
memo[memo.length] = value; | |
return memo; | |
}, []); | |
}; | |
/** | |
* WITHOUT | |
* | |
* Return an array without the given values. | |
*/ | |
_.without = function(array) { | |
// overload the function with extra arguments that should be omitted | |
var values = slice.call(arguments, 1); | |
// the filter iterator will return false if the value **is** include in the | |
// argument of value being iterated over, then that value will be omitted. | |
return _.filter(array, function(value){ return !_.include(values, value); }); | |
}; | |
/** | |
* UNIQUE | |
* | |
* Return an array that strips out duplicate values. | |
*/ | |
_.uniq = _.unique = function(array, isSorted) { | |
return _.reduce(array, function(memo, el, i) { | |
// crazy nested ternaries ahead. | |
// do a loose type check that will capture if the index is either #0 or falsy | |
// if it's the first item in the array, go ahead and stick it in our new array | |
// if the array's sorted, just make sure the last item doesn't equal the new one | |
// otherwise, make sure the new array doesn't include the old value | |
if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) memo[memo.length] = el; | |
return memo; | |
}, []); | |
}; | |
/** | |
* INTERSECT | |
* | |
* Return only the values that exist in all given arrays. | |
* | |
* @param array - a splat of arrays | |
*/ | |
_.intersect = function(array) { | |
var rest = slice.call(arguments, 1); | |
// set each item in our original array to true if in each of the rest of our | |
// arrays its index is greater than or equal to zero, then filter falsy values | |
return _.filter(_.uniq(array), function(item) { | |
return _.every(rest, function(other) { | |
return _.indexOf(other, item) >= 0; | |
}); | |
}); | |
}; | |
/** | |
* ZIP | |
* | |
* Take an array of arrays, and transmute them so result[i] contains the values | |
* from arr1[i], arr2[i], arr3[i], etc… | |
*/ | |
_.zip = function() { | |
var args = slice.call(arguments); | |
var length = _.max(_.pluck(args, 'length')); | |
var results = new Array(length); | |
// TODO : why concat an empty string and integer in the pluck? | |
// results[i] should contain arr1[i], arr2[i], arr3[i], etc… | |
for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i); | |
return results; | |
}; | |
/** | |
* INDEXOF | |
* | |
* Get the position of an item in an array. | |
* | |
* @param return integer - position in array or -1 if not present | |
*/ | |
_.indexOf = function(array, item, isSorted) { | |
if (array == null) return -1; | |
var i, l; | |
// sorted index is faster, so if we've already got a sorted arary, use that. | |
if (isSorted) { | |
i = _.sortedIndex(array, item); | |
return array[i] === item ? i : -1; | |
} | |
if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item); | |
for (i = 0, l = array.length; i < l; i++) if (array[i] === item) return i; | |
return -1; | |
}; | |
/** | |
* LASTINDEXOF | |
* | |
* Get the last occurrence of an item in an array. | |
*/ | |
_.lastIndexOf = function(array, item) { | |
if (array == null) return -1; | |
if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item); | |
var i = array.length; | |
// count down since we're looking for the last item | |
while (i--) if (array[i] === item) return i; | |
return -1; | |
}; | |
/** | |
* RANGE | |
* | |
* Create an array starting at a number, stopping at a number, with a step increment | |
*/ | |
_.range = function(start, stop, step) { | |
// reassign our variables based on how many are given, as we want the first and | |
// third to be optional, but this isn't really provided in JS | |
if (arguments.length <= 1) { | |
stop = start || 0; | |
start = 0; | |
} | |
// another hack for unsupported JS to provide a default argument | |
step = arguments[2] || 1; | |
// TODO : why not comma-separate these variables? | |
// how many characters in our range is going to be the highest number minus the | |
// lowest number divided by the step | |
var len = Math.max(Math.ceil((stop - start) / step), 0); | |
var idx = 0; | |
var range = new Array(len); | |
while(idx < len) { | |
// set the index and increment the value in one fell swoop, and overwrite the | |
// start value so next time its assignment will be correct | |
range[idx++] = start; | |
start += step; | |
} | |
return range; | |
}; | |
/** | |
* BIND | |
* | |
* Bind a function to an object. | |
*/ | |
_.bind = function(func, obj) { | |
if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); | |
// overload our method with the arguments to pass to the bound function | |
var args = slice.call(arguments, 2); | |
return function() { | |
return func.apply(obj, args.concat(slice.call(arguments))); | |
}; | |
}; | |
/** | |
* BINDALL | |
* | |
* Bind a group of methods to the object they belong to. If a second argument splat | |
* is passed, then only those methods will be bound. | |
*/ | |
_.bindAll = function(obj) { | |
var funcs = slice.call(arguments, 1); | |
if (funcs.length == 0) funcs = _.functions(obj); | |
each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); | |
return obj; | |
}; | |
/** | |
* MEMOIZE | |
* | |
* Remember a function's input and output so next time we just have its result ready | |
* instead of needing to run it again. | |
*/ | |
_.memoize = function(func, hasher) { | |
// TODO - is this behaving as expected? it seems to store only the io for a given | |
// function and not do any memoization on recursion. compare with | |
// http://www.nczonline.net/blog/2009/01/20/speed-up-your-javascript-part-2/ | |
var memo = {}; | |
hasher || (hasher = _.identity); | |
// this function will be assigned to a variable and called recursively by the else | |
// in the converse of the conditional on line 707 | |
return function() { | |
var key = hasher.apply(this, arguments); | |
return hasOwnProperty.call(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); | |
}; | |
}; | |
/** | |
* DELAY | |
* | |
* Run a function after a specified delay. | |
*/ | |
_.delay = function(func, wait) { | |
console.log(func); | |
console.log(wait); | |
var args = slice.call(arguments, 2); | |
return setTimeout(function(){ return func.apply(func, args); }, wait); | |
}; | |
/** | |
* DEFER | |
* | |
* Wait and run this sucker when the callstack is clear. Like using a setTimeout of | |
* 0. cf. http://ejohn.org/blog/how-javascript-timers-work/ | |
*/ | |
_.defer = function(func) { | |
// apply a delay with the arguments of func, wait, and any more passed args | |
return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); | |
}; | |
/** | |
* LIMIT | |
* | |
* This is an internal function only that we use to limit function calls in throttle | |
* and debounce. | |
*/ | |
var limit = function(func, wait, debounce) { | |
// ONE. this part is going to get called over and over and over whenever the | |
// original function is called. | |
var timeout; | |
return function() { | |
var context = this, args = arguments; | |
var throttler = function() { | |
timeout = null; | |
func.apply(context, args); | |
}; | |
console.log('hi'); | |
// THREE. but if our function tries to execute itself again before the wait is | |
// over, it needs to just CTFO. | |
if (debounce) clearTimeout(timeout); | |
// timeout returns some sort of crazy integer that appears to be an unique | |
// identifier for timers, incremented each time the browser uses a timer. so | |
// what is important here isn't the return value of the setTimeout, but the fact | |
// that it gets assigned so it can be cleared. | |
// TWO. then our throttler is going to get queued up to run in N ms. over and | |
// over it will get queued. | |
if (debounce || !timeout) timeout = setTimeout(throttler, wait); | |
}; | |
}; | |
/** | |
* THROTTLE | |
* | |
* Only allow a function to be called once every N ms. | |
*/ | |
_.throttle = function(func, wait) { | |
return limit(func, wait, false); | |
}; | |
/** | |
* DEBOUNCE | |
* | |
* Only allow a function to be called once its been inactive for N ms. | |
*/ | |
_.debounce = function(func, wait) { | |
return limit(func, wait, true); | |
}; | |
/** | |
* ONCE | |
* | |
* Run a function once and only once, and memorize its output. | |
*/ | |
_.once = function(func) { | |
// when we wrap a function in something like this, then via closure, it gets | |
// reassigned via func.apply and has access to the ran variable. it's kind of like | |
// injecting a layer between your function and the global scope. | |
var ran = false, memo; | |
return function() { | |
// no need to apply our function again, we just want its old value. | |
if (ran) return memo; | |
ran = true; | |
return memo = func.apply(this, arguments); | |
}; | |
}; | |
/** | |
* WRAP | |
* | |
* Take a function, then pass it as an argument to a second function so you can | |
* do fancy things with the original function such as executing code before or | |
* after or executing the function conditionally. | |
*/ | |
_.wrap = function(func, wrapper) { | |
return function() { | |
// take the function and all of its arguments, then apply them as arguments to | |
// the first function | |
var args = [func].concat(slice.call(arguments)); | |
return wrapper.apply(this, args); | |
}; | |
}; | |
/** | |
* COMPOSE | |
* | |
* Execute a function and use its return value as the input for the next function, | |
* that is, f() and g() and h() compose to f(g(h())) | |
*/ | |
_.compose = function() { | |
// allow our functions to be passed in as a comma-separated list of arguments | |
var funcs = slice.call(arguments); | |
return function() { | |
var args = slice.call(arguments); | |
for (var i = funcs.length - 1; i >= 0; i--) { | |
// return the result of the applying our previous function's value as | |
// arguments to our next function | |
args = [funcs[i].apply(this, args)]; | |
} | |
return args[0]; | |
}; | |
}; | |
/** | |
* AFTER | |
* | |
* Execute a function only after it's been called n times. | |
*/ | |
_.after = function(times, func) { | |
return function() { | |
// the original times value is captured and overwritten bia closure | |
if (--times < 1) { return func.apply(this, arguments); } | |
}; | |
}; | |
_.keys = nativeKeys || function(obj) { | |
if (obj !== Object(obj)) throw new TypeError('Invalid object'); | |
var keys = []; | |
for (var key in obj) if (hasOwnProperty.call(obj, key)) keys[keys.length] = key; | |
return keys; | |
}; | |
_.values = function(obj) { | |
return _.map(obj, _.identity); | |
}; | |
_.functions = _.methods = function(obj) { | |
// only show truthy methods that are functions and serve as keys of an object, and | |
// sort them alphabetically | |
return _.filter(_.keys(obj), function(key){ return _.isFunction(obj[key]); }).sort(); | |
}; | |
_.extend = function(obj) { | |
each(slice.call(arguments, 1), function(source) { | |
for (var prop in source) { | |
if (source[prop] !== void 0) obj[prop] = source[prop]; | |
} | |
}); | |
return obj; | |
}; | |
_.defaults = function(obj) { | |
each(slice.call(arguments, 1), function(source) { | |
for (var prop in source) { | |
if (obj[prop] == null) obj[prop] = source[prop]; | |
} | |
}); | |
return obj; | |
}; | |
_.clone = function(obj) { | |
return _.isArray(obj) ? obj.slice() : _.extend({}, obj); | |
}; | |
_.tap = function(obj, interceptor) { | |
interceptor(obj); | |
return obj; | |
}; | |
_.isEqual = function(a, b) { | |
if (a === b) return true; | |
var atype = typeof(a), btype = typeof(b); | |
if (atype != btype) return false; | |
if (a == b) return true; | |
if ((!a && b) || (a && !b)) return false; | |
if (a._chain) a = a._wrapped; | |
if (b._chain) b = b._wrapped; | |
if (a.isEqual) return a.isEqual(b); | |
if (b.isEqual) return b.isEqual(a); | |
if (_.isDate(a) && _.isDate(b)) return a.getTime() === b.getTime(); | |
if (_.isNaN(a) && _.isNaN(b)) return false; | |
if (_.isRegExp(a) && _.isRegExp(b)) | |
return a.source === b.source && | |
a.global === b.global && | |
a.ignoreCase === b.ignoreCase && | |
a.multiline === b.multiline; | |
if (atype !== 'object') return false; | |
if (a.length && (a.length !== b.length)) return false; | |
var aKeys = _.keys(a), bKeys = _.keys(b); | |
if (aKeys.length != bKeys.length) return false; | |
for (var key in a) if (!(key in b) || !_.isEqual(a[key], b[key])) return false; | |
return true; | |
}; | |
_.isEmpty = function(obj) { | |
if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; | |
for (var key in obj) if (hasOwnProperty.call(obj, key)) return false; | |
return true; | |
}; | |
_.isElement = function(obj) { | |
return !!(obj && obj.nodeType == 1); | |
}; | |
_.isArray = nativeIsArray || function(obj) { | |
return toString.call(obj) === '[object Array]'; | |
}; | |
_.isObject = function(obj) { | |
return obj === Object(obj); | |
}; | |
_.isArguments = function(obj) { | |
return !!(obj && hasOwnProperty.call(obj, 'callee')); | |
}; | |
_.isFunction = function(obj) { | |
return !!(obj && obj.constructor && obj.call && obj.apply); | |
}; | |
_.isString = function(obj) { | |
return !!(obj === '' || (obj && obj.charCodeAt && obj.substr)); | |
}; | |
_.isNumber = function(obj) { | |
return !!(obj === 0 || (obj && obj.toExponential && obj.toFixed)); | |
}; | |
_.isNaN = function(obj) { | |
return obj !== obj; | |
}; | |
_.isBoolean = function(obj) { | |
return obj === true || obj === false; | |
}; | |
_.isDate = function(obj) { | |
return !!(obj && obj.getTimezoneOffset && obj.setUTCFullYear); | |
}; | |
_.isRegExp = function(obj) { | |
return !!(obj && obj.test && obj.exec && (obj.ignoreCase || obj.ignoreCase === false)); | |
}; | |
_.isNull = function(obj) { | |
return obj === null; | |
}; | |
_.isUndefined = function(obj) { | |
return obj === void 0; | |
}; | |
_.noConflict = function() { | |
root._ = previousUnderscore; | |
return this; | |
}; | |
_.identity = function(value) { | |
return value; | |
}; | |
_.times = function (n, iterator, context) { | |
for (var i = 0; i < n; i++) iterator.call(context, i); | |
}; | |
_.mixin = function(obj) { | |
each(_.functions(obj), function(name){ | |
addToWrapper(name, _[name] = obj[name]); | |
}); | |
}; | |
var idCounter = 0; | |
_.uniqueId = function(prefix) { | |
var id = idCounter++; | |
return prefix ? prefix + id : id; | |
}; | |
_.templateSettings = { | |
evaluate : /<%([\s\S]+?)%>/g, | |
interpolate : /<%=([\s\S]+?)%>/g | |
}; | |
_.template = function(str, data) { | |
var c = _.templateSettings; | |
var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' + | |
'with(obj||{}){__p.push(\'' + | |
str.replace(/\\/g, '\\\\') | |
.replace(/'/g, "\\'") | |
.replace(c.interpolate, function(match, code) { | |
return "'," + code.replace(/\\'/g, "'") + ",'"; | |
}) | |
.replace(c.evaluate || null, function(match, code) { | |
return "');" + code.replace(/\\'/g, "'") | |
.replace(/[\r\n\t]/g, ' ') + "__p.push('"; | |
}) | |
.replace(/\r/g, '\\r') | |
.replace(/\n/g, '\\n') | |
.replace(/\t/g, '\\t') | |
+ "');}return __p.join('');"; | |
var func = new Function('obj', tmpl); | |
return data ? func(data) : func; | |
}; | |
var wrapper = function(obj) { this._wrapped = obj; }; | |
_.prototype = wrapper.prototype; | |
var result = function(obj, chain) { | |
return chain ? _(obj).chain() : obj; | |
}; | |
var addToWrapper = function(name, func) { | |
wrapper.prototype[name] = function() { | |
var args = slice.call(arguments); | |
unshift.call(args, this._wrapped); | |
return result(func.apply(_, args), this._chain); | |
}; | |
}; | |
_.mixin(_); | |
each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { | |
var method = ArrayProto[name]; | |
wrapper.prototype[name] = function() { | |
method.apply(this._wrapped, arguments); | |
return result(this._wrapped, this._chain); | |
}; | |
}); | |
each(['concat', 'join', 'slice'], function(name) { | |
var method = ArrayProto[name]; | |
wrapper.prototype[name] = function() { | |
return result(method.apply(this._wrapped, arguments), this._chain); | |
}; | |
}); | |
wrapper.prototype.chain = function() { | |
this._chain = true; | |
return this; | |
}; | |
wrapper.prototype.value = function() { | |
return this._wrapped; | |
}; | |
})(); |
Line 227 - why assign result to result?
Essentially what he's trying to write is something like this -
if (!result) {
return breaker;
}
result = iterator.call(context, value, index, list);
if (!result) {
return breaker;
}
So it's setting result to false if result is false or the rest of the calls are false. I am guessing he does it that way because he doesn't want to run iterator ever again after it returns false once.
Line 242 - what is _.identity?
Just in case, someone calls the method w/out passing one, instead of having to do something like this every time:
if (_iterator && typeof _iterator === 'function') _iterator()
he just defaults it to an empty function. (see line 676)
Line 248 - why no result = result && etc?
Refer back to the result = result comment above, except this time, for this method, he already assigned result as false (line 244). so he only needs to check for iterator.call.
Line 268 - why the assignment again?
Not sure what you meant by "the assignment again", but I read this "found = value === target" and understand it as this
if (value === target) then (found = true) else (found = false)
So I don't think line 231 does what we thought. So my browser doesn't execute it because it supports the native .every method. If I comment out 223, then what 231 actually does is assign the result of the iterator call to result if result is true. In result = result && blahblah()
, the first result just acts as a guard. So instead of true being returned at the bottom, the actual result is returned. It seems line 233 should actually read return !!result to convert the result to a boolean. Thoughts?
Line 114 - Why void 0 here?
If you go on your console and do a log on void 0, it returns "undefined". Now you probably might ask, "why not just do var initial = memo !== undefined?". The reason for that is because you can actually redefine undefined to be a value. So "void 0" is the safest way to get an "undefined".