-
-
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".