Created
July 6, 2011 16:04
-
-
Save jpcody/1067614 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 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 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)
Author
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?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.