Skip to content

Instantly share code, notes, and snippets.

@jpcody
Created July 6, 2011 16:04
Show Gist options
  • Save jpcody/1067614 to your computer and use it in GitHub Desktop.
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;
};
})();
@koesbong
Copy link

koesbong commented Jul 6, 2011

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

@koesbong
Copy link

koesbong commented Jul 6, 2011

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.

@koesbong
Copy link

koesbong commented Jul 6, 2011

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)

@koesbong
Copy link

koesbong commented Jul 6, 2011

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.

@koesbong
Copy link

koesbong commented Jul 6, 2011

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)

@jpcody
Copy link
Author

jpcody commented Jul 7, 2011

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