// Underscore.js 1.3.3
// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Underscore is freely distributable under the MIT license.
// Portions of Underscore are inspired or borrowed from Prototype,
// Oliver Steele's Functional, and John Resig's Micro-Templating.
// For all details and documentation:
// http://documentcloud.github.com/underscore
(function() {
// create 1 Global object , Is represented in the browser as window object , in Node.js In the said global object
var root = this;
// save "_"( Underscore variable ) The value before being overridden
// If a naming conflict occurs or specification is considered , through _.noConflict() Methods to recover "_" be Underscore Take the previous value , And return Underscore Object to rename
var previousUnderscore = root._;
// create 1 Empty object constants , Easy to share internally
var breaker = {};
// Cache the prototype chain of the built-in object in a local variable , Easy to call quickly
var ArrayProto = Array.prototype, //
ObjProto = Object.prototype, //
FuncProto = Function.prototype;
// Cache common methods in the built-in object prototype in local variables , Easy to call quickly
var slice = ArrayProto.slice, //
unshift = ArrayProto.unshift, //
toString = ObjProto.toString, //
hasOwnProperty = ObjProto.hasOwnProperty;
// It's defined here 1 some JavaScript 1.6 Provides a new approach
// If these methods are supported in the host environment, they are called first , If not provided in the host environment , Will be by Underscore implementation
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;
// Create an object-like invocation , Will return 1 a Underscore The wrapper , Included in the prototype of the wrapper object Underscore All the methods ( Similar to the DOM The object is wrapped as 1 a jQuery object )
var _ = function(obj) {
// all Underscore Objects pass internally wrapper Object construction
return new wrapper(obj);
};
// For different host environments , will Undersocre A named variable is stored in a different object
if( typeof exports !== 'undefined') {// Node.js The environment
if( typeof module !== 'undefined' && module.exports) {
exports = module.exports = _;
}
exports._ = _;
} else {// In the browser environment Underscore The named variable is hung at window In the object
root['_'] = _;
}
// Versions.
_.VERSION = '1.3.3';
// Collection related methods ( General handling of data and objects )
// --------------------
// Iterative processor , For each of the sets 1 The element executes the processor method
var each = _.each = _.forEach = function(obj, iterator, context) {
// Null values are not processed
if(obj == null)
return;
if(nativeForEach && obj.forEach === nativeForEach) {
// If the host environment supports it , Priority call JavaScript 1.6 To provide the forEach methods
obj.forEach(iterator, context);
} else if(obj.length === +obj.length) {
// right < An array of > Each of the 1 The element executes the processor method
for(var i = 0, l = obj.length; i < l; i++) {
if( i in obj && iterator.call(context, obj[i], i, obj) === breaker)
return;
}
} else {
// right < object > Each of the 1 The element executes the processor method
for(var key in obj) {
if(_.has(obj, key)) {
if(iterator.call(context, obj[key], key, obj) === breaker)
return;
}
}
}
};
// Iterative processor , with each The difference in approach is map The return value for each iteration is stored , And as a 1 Returns a new array
_.map = _.collect = function(obj, iterator, context) {
// An array to hold the return value
var results = [];
if(obj == null)
return results;
// Take precedence over calls provided by the host environment map methods
if(nativeMap && obj.map === nativeMap)
return obj.map(iterator, context);
// Iterating through the elements in the collection
each(obj, function(value, index, list) {
// Stores the return value processed by each iteration to results An array of
results[results.length] = iterator.call(context, value, index, list);
});
// Return processing results
if(obj.length === +obj.length)
results.length = obj.length;
return results;
};
// Put each element in the collection into the iteration handler , And take the return value of this iteration as "memo" Pass to the next 1 iteration , 1 Generally used to aggregate results or join data
_.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
// Check for an initial value by the number of arguments
var initial = arguments.length > 2;
if(obj == null)
obj = [];
// Take precedence over calls provided by the host environment reduce methods
if(nativeReduce && obj.reduce === nativeReduce && false) {
if(context)
iterator = _.bind(iterator, context);
return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
}
// Iterating through the elements in the collection
each(obj, function(value, index, list) {
if(!initial) {
// If there is no initial value , Will be the first 1 Three elements as the initial value ; If you're dealing with a collection of objects , The default value is the value of 1 The value of an attribute
memo = value;
initial = true;
} else {
// Record processing results , And pass the result to the next one 1 iteration
memo = iterator.call(context, memo, value, index, list);
}
});
if(!initial)
throw new TypeError('Reduce of empty array with no initial value');
return memo;
};
// with reduce Role similar , The elements in the collection will be iterated backwards ( That is, from the last 1 The number of elements starts at number 1 1 An element )
_.reduceRight = _.foldr = function(obj, iterator, memo, context) {
var initial = arguments.length > 2;
if(obj == null)
obj = [];
// Take precedence over calls provided by the host environment reduceRight methods
if(nativeReduceRight && obj.reduceRight === nativeReduceRight) {
if(context)
iterator = _.bind(iterator, context);
return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
}
// Reverses the order of elements in a collection
var reversed = _.toArray(obj).reverse();
if(context && !initial)
iterator = _.bind(iterator, context);
// through reduce Methods To process data
return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator);
};
// Iterate over the elements in the collection , Returns the first 1 Three elements that can be validated by the processor
_.find = _.detect = function(obj, iterator, context) {
// result For the first 1 Three elements that can be validated
var result;
// through any Method iterates over the data , And record the elements that pass the validation
// ( If you are checking the processor return status in an iteration , Used here each The method will be more appropriate )
any(obj, function(value, index, list) {
// If the result returned by the processor is converted to Boolean The post-type value is true, The current record and returns the current element
if(iterator.call(context, value, index, list)) {
result = value;
return true;
}
});
return result;
};
// with find Method action is similar , but filter Method records all elements in the collection that pass validation
_.filter = _.select = function(obj, iterator, context) {
// Used to store an array of elements that have passed validation
var results = [];
if(obj == null)
return results;
// Take precedence over calls provided by the host environment filter methods
if(nativeFilter && obj.filter === nativeFilter)
return obj.filter(iterator, context);
// Iterate over the elements in the collection , And put the processor-validated elements into an array and return them
each(obj, function(value, index, list) {
if(iterator.call(context, value, index, list))
results[results.length] = value;
});
return results;
};
// with filter The method does the opposite , Returns a list of elements that have not been validated by the processor
_.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;
};
// If all elements in the collection are processor-validated , It returns true
_.every = _.all = function(obj, iterator, context) {
var result = true;
if(obj == null)
return result;
// Take precedence over calls provided by the host environment every methods
if(nativeEvery && obj.every === nativeEvery)
return obj.every(iterator, context);
// Iterate over the elements in the collection
each(obj, function(value, index, list) {
// This is understood as result = (result && iterator.call(context, value, index, list))
// The result of the validation processor is converted to Boolean Is after type true value
if(!( result = result && iterator.call(context, value, index, list)))
return breaker;
});
return !!result;
};
// Check any in the collection 1 The elements are being converted to Boolean type , Whether it is true value ? Or through the processor processing , If a value of true?
var any = _.some = _.any = function(obj, iterator, context) {
// If no processor parameters are specified , The default handler function returns the element itself , And at iteration time by converting the element to Boolean Type to determine if it is true value
iterator || ( iterator = _.identity);
var result = false;
if(obj == null)
return result;
// Take precedence over calls provided by the host environment some methods
if(nativeSome && obj.some === nativeSome)
return obj.some(iterator, context);
// Iterate over the elements in the collection
each(obj, function(value, index, list) {
if(result || ( result = iterator.call(context, value, index, list)))
return breaker;
});
return !!result;
};
// Check to see if any of the values in the collection exactly match the target parameter ( The data type will also be matched )
_.include = _.contains = function(obj, target) {
var found = false;
if(obj == null)
return found;
// Take precedence over calls provided by the host environment Array.prototype.indexOf methods
if(nativeIndexOf && obj.indexOf === nativeIndexOf)
return obj.indexOf(target) != -1;
// through any Method iterates through the elements in the collection , Verify that the value and type of the element exactly match the target
found = any(obj, function(value) {
return value === target;
});
return found;
};
// The method of the same name for all elements in the collection is called in turn , From the first 3 Parameter start , This will be passed into the element's calling method
// return 1 An array , The results of all the methods are stored
_.invoke = function(obj, method) {
// Parameters passed when a method with the same name is called ( From the first 3 Parameter start )
var args = slice.call(arguments, 2);
// Call the methods of each element in turn , And returns the result in an array
return _.map(obj, function(value) {
return (_.isFunction(method) ? method || value : value[method]).apply(value, args);
});
};
// traverse 1 An array of objects , And returns a list of values for the specified property in each object
_.pluck = function(obj, key) {
// If a 1 The attribute does not exist in the object , It returns undefined
return _.map(obj, function(value) {
return value[key];
});
};
// Returns the maximum value in the collection , If there are no comparable values , It returns undefined
_.max = function(obj, iterator, context) {
// If the set is 1 An array , And no processor is used , Use the Math.max Get the maximum
// 1 Will be in 1 An array is stored 1 A series of Number Type of data
if(!iterator && _.isArray(obj) && obj[0] === +obj[0])
return Math.max.apply(Math, obj);
// For a null value , Just go back to negative infinity
if(!iterator && _.isEmpty(obj))
return -Infinity;
// 1 A temporary object , computed Used to store maximum values during comparison ( temporary )
var result = {
computed : -Infinity
};
// Iterate over the elements in the collection
each(obj, function(value, index, list) {
// If processor parameters are specified , Is the value returned by the processor , Otherwise, just use it each Default value for traversal
var computed = iterator ? iterator.call(context, value, index, list) : value;
// If you compare the values 1 A value , Puts the current value in result.value
computed >= result.computed && ( result = {
value : value,
computed : computed
});
});
// Return maximum value
return result.value;
};
// Returns the minimum value in the collection , Treatment process and max methods 1 to
_.min = function(obj, iterator, context) {
if(!iterator && _.isArray(obj) && obj[0] === +obj[0])
return Math.min.apply(Math, obj);
if(!iterator && _.isEmpty(obj))
return Infinity;
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;
};
// By random number , Make arrays non-permutation
_.shuffle = function(obj) {
// shuffled Variables store processing and final result data
var shuffled = [], rand;
// Iterate over the elements in the collection
each(obj, function(value, index, list) {
// generate 1 A random number , The random number in <0- The amount currently processed > between
rand = Math.floor(Math.random() * (index + 1));
// I'm going to put the element that I got at random shuffled At the end of the array
shuffled[index] = shuffled[rand];
// Insert the latest value in the position of the previous random number
shuffled[rand] = value;
});
// return 1 An array , The array stores random mix-and-match collection elements
return shuffled;
};
// For the elements in the set , Sort by a specific field or value
// Compared with the Array.prototype.sort methods , sortBy Method supports sorting objects
_.sortBy = function(obj, val, context) {
// val It should be an object 1 A property , or 1 10 processor functions , If it is 1 A processor , The data that needs to be compared should be returned
var iterator = _.isFunction(val) ? val : function(obj) {
return obj[val];
};
// Call to order : _.pluck(_.map().sort());
// call _.map() Method traverses the collection , And put the elements in the set value node , Place the data in the element to be compared to criteria Properties of the
// call sort() Method to represent the elements in a collection criteria The data in the property is sorted sequentially
// call pluck Gets the sorted collection of objects and returns
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 ===
void 0)
return 1;
if(b ===
void 0)
return -1;
return a < b ? -1 : a > b ? 1 : 0;
}), 'value');
};
// Will be an element in the collection , Press the processor to return key Divided into multiple arrays
_.groupBy = function(obj, val) {
var result = {};
// val The processor functions that will be converted to be grouped , if val not 1 a Function Type of data , Will be used as a filter element key value
var iterator = _.isFunction(val) ? val : function(obj) {
return obj[val];
};
// Iterate over the elements in the collection
each(obj, function(value, index) {
// Takes the return value of the processor as key, And will be the same key Elements in the 1 A new array
var key = iterator(value, index);
(result[key] || (result[key] = [])).push(value);
});
// Returns grouped data
return result;
};
_.sortedIndex = function(array, obj, iterator) {
iterator || ( iterator = _.identity);
var low = 0, high = array.length;
while(low < high) {
var mid = (low + high) >> 1;
iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid;
}
return low;
};
// will 1 Individual set transformation 1 Array and returns
// 1 As used to arguments Convert to array , Or transform an unordered collection of objects into an ordered collection of data
_.toArray = function(obj) {
if(!obj)
return [];
if(_.isArray(obj))
return slice.call(obj);
// will arguments Convert to array
if(_.isArguments(obj))
return slice.call(obj);
if(obj.toArray && _.isFunction(obj.toArray))
return obj.toArray();
// Converts an object to an array , The array contains a list of values for all properties in the object ( Contains no properties in the object prototype chain )
return _.values(obj);
};
// Calculate the number of elements in the collection
_.size = function(obj) {
// If the set is 1 An array , Counts the number of array elements
// If the set is 1 An object , Counts the number of attributes in the object ( Contains no properties in the object prototype chain )
return _.isArray(obj) ? obj.length : _.keys(obj).length;
};
// Array dependent methods
// ---------------
// return 1 The first digit of an array 1 Or specified by order n An element
_.first = _.head = _.take = function(array, n, guard) {
// If no parameters are specified n, Returns the first 1 An element
// If you specify n, It returns 1 A new array , Contains a specified number in order n An element
// guard The parameter is used to determine that only the values are returned 1 An element , when guard for true when , Specify the number of n invalid
return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
};
// return 1 A new array , Contains in addition to the first 1 The other elements , Or exclude from the end 1 The element is specified forward n An element
// with first The method is different , first Determine the location of the desired element before the array , initial Determines the last position of the excluded elements in the array
_.initial = function(array, n, guard) {
// If no parameters are passed n, The default is to return the last division 1 The other elements
// If you pass a parameter n, Returns from the last 1 The elements start moving forward n The other elements
// guard Used to determine return only 1 An element , when guard for true when , Specify the number of n invalid
return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));
};
// Returns the end of the array 1 Specified in or reverse order n An element
_.last = function(array, n, guard) {
if((n != null) && !guard) {
// Calculate and specify the location of the element to get n, All the way to the end of the array , As a 1 Returns a new array
return slice.call(array, Math.max(array.length - n, 0));
} else {
// If no quantity is specified , or guard for true when , Just return the last 1 An element
return array[array.length - 1];
}
};
// Get exception control 1 Or before specified n The other elements
_.rest = _.tail = function(array, index, guard) {
// To calculate slice The first 2 Position parameter , All the way to the end of the array
// If not specified index, or guard A value of true, Returns a divisor 1 The other elements
// (index == null) A value of true when , Passed as a parameter to slice The function is automatically converted to 1
return slice.call(array, (index == null) || guard ? 1 : index);
};
// Returns all values in the array that can be converted to true The elements of the , return 1 A new array
// Values that cannot be converted include false, 0, '', null, undefined, NaN, These values will be converted to false
_.compact = function(array) {
return _.filter(array, function(value) {
return !!value;
});
};
// will 1 A combination of several dimensions 1 Dimensional array , Support for deep merging
// shallow Parameter is used to control the merge depth , when shallow for true when , Incorporate only the first 1 layer , Deep merge is performed by default
_.flatten = function(array, shallow) {
// Iterate through each of the arrays 1 An element , And return the value as demo Passed to the 1 iteration
return _.reduce(array, function(memo, value) {
// If the element is still 1 An array , Make the following judgment :
// - If you don't do a deep merge , Use the Array.prototype.concat Joins the current array with the previous data
// - If deep merge is supported , Then iterative call flatten methods , Until the underlying element is no longer an array type
if(_.isArray(value))
return memo.concat( shallow ? value : _.flatten(value));
// data (value) Already at the bottom , It's no longer an array type , The data is merged into memo And in return
memo[memo.length] = value;
return memo;
}, []);
};
// Filters and returns the difference data in the current array that is not equal to the specified data ( May refer to difference Methods the annotation )
_.without = function(array) {
return _.difference(array, slice.call(arguments, 1));
};
// De-weight the data in the array ( use === To compare the )
// when isSorted Parameters for false when , The elements in the array are called in turn include methods , Check if the same element has been added to the return value ( An array of ) In the
// Make sure the data in the array is sorted before calling , You can isSorted Set to true, It will pass with the last 1 The elements are compared to exclude the same value , use isSorted It will be more efficient than the default include way
// uniq Method defaults to comparing data in an array , If the statement iterator The processor , It is created based on the processor 1 A comparison array , The comparison is based on the data in the array , But in the end the only return 1 The data is still the original array
_.uniq = _.unique = function(array, isSorted, iterator) {
// If you use iterator The processor , The data in the current array is processed by iterator first , And return 1 A new array after processing
// The new array is used as a benchmark for comparison
var initial = iterator ? _.map(array, iterator) : array;
// A temporary array used to record the results of processing
var results = [];
// If there's only one in the array 2 A value , You don't need to use it include Methods To compare , will isSorted Set to true Can improve the operation efficiency
if(array.length < 3)
isSorted = true;
// use reduce Methods Iterate and accumulate the results
// initial Variables are the baseline data to be compared , It could be the original array , It could also be the result set of the processor ( If it's set iterator)
_.reduce(initial, function(memo, value, index) {
// if isSorted Parameters for true, Use it directly === Compare the last in the record 1 A data
// if isSorted Parameters for false, Use the include Method and each in the collection 1 Data for comparison
if( isSorted ? _.last(memo) !== value || !memo.length : !_.include(memo, value)) {
// memo No duplicate data has been compared
// According to the iterator State of parameter , memo The data recorded in May be raw data , It could also be data processed by the processor
memo.push(value);
// The processing result array always holds the data from the original array
results.push(array[index]);
}
return memo;
}, []);
// Return processing results , It contains only data that has no duplicates in the array
return results;
};
// union The methods and uniq Methods effect 1 to , The difference is that union Allows multiple arrays to be passed in as parameters
_.union = function() {
// union Multiple arrays in the parameter are shallowly merged as 1 An array object is passed to uniq Method processing
return _.uniq(_.flatten(arguments, true));
};
// Gets the current array and others 1 The intersection elements of two or more arrays
// From the first 2 The parameters start as those to be compared 1 One or more arrays
_.intersection = _.intersect = function(array) {
// rest A variable records other array objects that need to be compared
var rest = slice.call(arguments, 1);
// use uniq Method to remove duplicate data from the current array , Avoid double counting
// The data for the current array is filtered through the processor , And returns a condition that meets the criteria ( Compare the same elements ) The data of
return _.filter(_.uniq(array), function(item) {
// use every Method validation per 1 Each of the arrays contains the data that needs to be compared
// If all arrays contain comparison data , Then return all true, If any 1 An array does not contain this element , It returns false
return _.every(rest, function(other) {
// other Parameters are stored for each 1 An array that needs to be compared
// item Stores the data in the current array that needs to be compared
// use indexOf Method searches for the presence of the element in the array ( May refer to indexOf Methods the annotation )
return _.indexOf(other, item) >= 0;
});
});
};
// Filters and returns the difference data in the current array that is not equal to the specified data
// This function 1 Generally used to delete data specified in an array , And get the deleted array
// The function of this method is without equal , without Method parameters do not formally allow data to be contained in an array , while difference The method parameter is formally recommended as an array ( Can also and without Use the same form of parameters )
_.difference = function(array) {
// For the first 2 All of the parameters at the beginning of an argument , As a 1 The array is merged ( Only merge the first 1 layer , It's not a deep merger )
// rest Variables store validation data , This method is used for comparison with the original data
var rest = _.flatten(slice.call(arguments, 1), true);
// Filter the merged array data , A filter condition is the contents of the current array that does not contain validation data specified by the parameter
// Combine the data that meets the filtering criteria as 1 Returns a new array
return _.filter(array, function(value) {
return !_.include(rest, value);
});
};
// Take the data at the same location in each array as 1 A new one 2 Dimension array return , The length of the array returned is the maximum length of the array passed in , Blank Spaces for other arrays are used undefined fill
// zip Methods should contain multiple parameters , And each parameter should be an array
_.zip = function() {
// Converts parameters to arrays , At this time args is 1 a 2 Dimensional array
var args = slice.call(arguments);
// Calculate each 1 The length of an array , And returns the maximum length value
var length = _.max(_.pluck(args, 'length'));
// Create according to the maximum length value 1 A new empty array , This array is used to store the results of the processing
var results = new Array(length);
// Maximum cycle length , Each loop will be called pluck Method gets data at the same location in each array ( In turn, from 0 To the last position )
// Store the obtained data in 1 A new array , In the results And return
for(var i = 0; i < length; i++)
results[i] = _.pluck(args, "" + i);
// The result returned is 1 a 2 Dimensional array
return results;
};
// search 1 The first occurrence of an element in an array , Returns if the element does not exist -1
// Use when searching === Matches elements
_.indexOf = function(array, item, isSorted) {
if(array == null)
return -1;
var i, l;
if(isSorted) {
i = _.sortedIndex(array, item);
return array[i] === item ? i : -1;
}
// Take precedence over calls provided by the host environment indexOf methods
if(nativeIndexOf && array.indexOf === nativeIndexOf)
return array.indexOf(item);
// Loop and return the first occurrence of the element
for( i = 0, l = array.length; i < l; i++)
if( i in array && array[i] === item)
return i;
// No element found , return -1
return -1;
};
// return 1 Five elements are at the end of the array 1 Secondary position , Returns if the element does not exist -1
// Use when searching === Matches elements
_.lastIndexOf = function(array, item) {
if(array == null)
return -1;
// Take precedence over calls provided by the host environment lastIndexOf methods
if(nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf)
return array.lastIndexOf(item);
var i = array.length;
// Loop and return the last occurrence of the element
while(i--)
if( i in array && array[i] === item)
return i;
// No element found , return -1
return -1;
};
// Depending on the interval and the step size , generate 1 Series of integer , And returned as an array
// start The parameter represents the minimum number
// stop The parameter represents the largest number
// step Parameter represents the step size value between generating multiple values
_.range = function(start, stop, step) {
// Parameter control
if(arguments.length <= 1) {
// If there are no parameters , the start = 0, stop = 0, No data is generated in the loop , Will return 1 An empty array
// If you have 1 A parameter , The parameter is specified to stop, start = 0
stop = start || 0;
start = 0;
}
// The step value that generates an integer , The default is 1
step = arguments[2] || 1;
// Calculate the maximum value that will be generated based on the interval and step size
var len = Math.max(Math.ceil((stop - start) / step), 0);
var idx = 0;
var range = new Array(len);
// Generate integer list , And stored in the range An array of
while(idx < len) {
range[idx++] = start;
start += step;
}
// Return list result
return range;
};
// Function correlation method
// ------------------
// create 1 Three for setting prototype Public function object
var ctor = function() {
};
// for 1 A function binding execution context , Call this function in any case , In the function this All point to the context object
// Binding function , You can also pass the call parameter to the function
_.bind = function bind(func, context) {
var bound, args;
// Take precedence over calls provided by the host environment bind methods
if(func.bind === nativeBind && nativeBind)
return nativeBind.apply(func, slice.call(arguments, 1));
// func The parameter must be 1 A function (Function) type
if(!_.isFunction(func))
throw new TypeError;
// args Variables are stored bind Methods the first 3 A starting argument list , Will be passed to each invocation func function
args = slice.call(arguments, 2);
return bound = function() {
if(!(this instanceof bound))
return func.apply(context, sargs.concat(slice.call(arguments)));
ctor.prototype = func.prototype;
var self = new ctor;
var result = func.apply(self, args.concat(slice.call(arguments)));
if(Object(result) === result)
return result;
return self;
};
};
// The function that will be specified , Or all functions of the object itself bind to the object itself , When the bound function is called , The context object always points to the object itself
// The method 1 Generally used when dealing with object events , For example, :
// _(obj).bindAll(); // or _(obj).bindAll('handlerClick');
// document.addEventListener('click', obj.handlerClick);
// in handlerClick In the method , The context is still obj object
_.bindAll = function(obj) {
// The first 2 The parameters begin to indicate the name of the function to bind to
var funcs = slice.call(arguments, 1);
// If a specific function name is not specified , Then the default binding object itself is of all types Function The properties of the
if(funcs.length == 0)
funcs = _.functions(obj);
// Loop through and set all function Settings to obj The object itself
// each Methods themselves do not traverse methods in the object prototype chain , But here the funcs The list is through _.functions Method-Acquired , It already contains the methods in the prototype chain
each(funcs, function(f) {
obj[f] = _.bind(obj[f], obj);
});
return obj;
};
// memoize Method will return 1 A function , This function integrates caching capabilities , Cache the calculated value into a local variable and return it directly on the next call
// So if I compute theta 1 A large number of objects or data , Memory usage should be taken into account
_.memoize = function(func, hasher) {
// Used to store cached results memo object
var memo = {};
// hasher The parameter should be 1 a function, It's used to return 1 a key, the key As the identity of the read cache
// If not specified key, The default is to use the column of the function 1 2 parameters as key, If the order of the function 1 The parameters are composite data types , It might return something like that [Object object] the key, this key It may cause incorrect data in subsequent calculations
hasher || ( hasher = _.identity);
// return 1 A function , This function starts by checking the cache , The call is then made to data that has not been cached
return function() {
var key = hasher.apply(this, arguments);
return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
};
};
// Delay to perform 1 A function
// wait The unit is ms, The first 3 The starting arguments are passed to the execution function in turn
_.delay = function(func, wait) {
var args = slice.call(arguments, 2);
return setTimeout(function() {
return func.apply(null, args);
}, wait);
};
// Deferred execution function
// JavaScript In the setTimeout Will be put into 1 A separate function stack , Execution time is after all functions called on the current stack have been executed
// defer Set the function in 1ms After the implementation , The purpose is to func Functions are placed on a separate stack , Wait until the current function completes
// defer methods 1 Generally used for processing DOM The priority of the operation , Implement the right logic flow and a smoother interaction experience
_.defer = function(func) {
return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
};
// Function throttling method , throttle Method is mainly used to control the execution frequency of the function , Within a controlled time interval , Frequently called functions are not executed more than once
// If the function is called multiple times in the interval , The interval is automatically called when it ends 1 time , There is no need to wait until the deadline to call manually ( There will be no return value when it is called automatically )
// throttle function 1 Generally used to handle complex and frequently invoked functions , Control the frequency of function calls through throttling , Save processing resources
// For example, window.onresize Bound event function , or element.onmousemove Bound event function , You can use throttle For packaging
// throttle Method returns 1 A function , This function is called automatically func And throttling control
_.throttle = function(func, wait) {
var context, args, timeout, throttling, more, result;
// whenDone The variable called debounce methods , So when you call a function multiple times in a row , The last 1 The call overrides the timer from the previous call , The clear state function is only executed 1 time
// whenDone The function is at the end 1 Call when the time interval for the second function execution has expired , Clears records during throttling and invocation 1 Some state
var whenDone = _.debounce(function() {
more = throttling = false;
}, wait);
// return 1 A function , And throttling control within the function
return function() {
// Saves the execution context and arguments of the function
context = this;
args = arguments;
// later On the function in the 1 Executes when the time interval for the second function call ends
var later = function() {
// remove timeout handle , Under the convenient 1 Secondary function call
timeout = null;
// more It was recorded on 1 Between the call and the time interval cutoff , Whether the function was repeatedly called
// If the function is called repeatedly , The function is automatically called again at the end of the time interval
if(more)
func.apply(context, args);
// call whenDone, Used to clear throttled state after an interval
whenDone();
};
// timeout Recorded on the 1 Handle to the interval between second function executions
// timeout Called when the time interval is cut off later function , later Will be cleared timeout, And check to see if the function needs to be called again
if(!timeout)
timeout = setTimeout(later, wait);
// throttling The variable records whether the time interval for the last call has ended , Whether or not it is throttling
// throttling Set to for each function call true, Indicates that throttling is required , Set to at the end of the interval false( in whenDone Implementation in function )
if(throttling) {
// Multiple calls are made during throttling , in more Recorded in the 1 A state of , Indicates that the function needs to be called automatically again when the time interval expires
more = true;
} else {
// Not in the throttling process , May be the first 1 Secondary function , Or more than that 1 The interval between calls , You can call the function directly
result = func.apply(context, args);
}
// call whenDone, Used to clear throttled state after an interval
whenDone();
// throttling Variables record the throttling state of a function call
throttling = true;
// Return the call result
return result;
};
};
// debounce with throttle Methods the similar , For function throttling , The difference is that :
// -- throttle Focus on how often the function executes , Functions are executed only at the specified frequency 1 time ;
// -- debounce The function is more concerned with the interval between functions , That is, the function cannot be called twice in less than the specified time ;
// If the interval between two functions is less than wait, The timer is cleared and recreated , This means calling the function frequently and continuously , function 1 Will not be executed , Until a 1 Call and up 1 The time of the call is not less than wait ms
// debounce function 1 Generally used for control needs 1 An operation that can not be performed until some time later , For example, at the end of user input 200ms Post prompt user , You can use debounce packaging 1 A function , Bound to the onkeyup The event
// ----------------------------------------------------------------
// @param {Function} func Represents the function being executed
// @param {Number} wait Represents the allowed time interval , Repeated calls within this time range are rescheduled wait ms
// @param {Boolean} immediate Indicates whether the function is executed immediately after the call , true For immediate call , false Is called at the time cutoff
// debounce Method returns 1 A function , This function is called automatically func And throttling control
_.debounce = function(func, wait, immediate) {
// timeout Used for recording functions 1 The execution state of the call ( Timer handle )
// when timeout for null when , In the said 1 The call has ended
var timeout;
// return 1 A function , And throttling control within the function
return function() {
// Holds the context object and arguments of the function
var context = this, args = arguments;
var later = function() {
// Set up the timeout for null
// later The function is called at the end of the allowed time
// When the function is called , Indicates that the 1 The sub-function execution time has exceeded the agreed time interval , Any subsequent calls are allowed
timeout = null;
if(!immediate)
func.apply(context, args);
};
// If the function is set to execute immediately , And on the 1 The time interval for the call has passed , The function is called immediately
if(immediate && !timeout)
func.apply(context, args);
// create 1 A timer is used to check and set the state of a function call
// Clear the timer before you create it 1 time setTimeout handle , No matter on 1 Whether the subbound function has been executed
// If this function is called , on 1 Secondary function execution has not yet begun (1 As is immediate Set to false when ), The execution time of the function is delayed , so timeout The handle is recreated
clearTimeout(timeout);
// Called when the allowed time has expired later function
timeout = setTimeout(later, wait);
};
};
// create 1 One will only be executed 1 A function of time , If the function is called repeatedly , Returns the first 1 The result of the execution
// This function is used to obtain and calculate the logic of fixed data , For example, get the type of browser the user is using
_.once = function(func) {
// ran Records whether the function has been executed
// memo Recording function 1 The result of the execution
var ran = false, memo;
return function() {
// If the function has been executed , Returns the value directly 1 The result of the execution
if(ran)
return memo;
ran = true;
return memo = func.apply(this, arguments);
};
};
// return 1 A function , This function passes the current function as an argument to 1 A package function
// In the package function, you can pass the 1 An argument to the current function , And return the result
// 1 Typically low coupled composite calls for multiple process handler functions
_.wrap = function(func, wrapper) {
return function() {
// Takes the current function as the control 1 A parameter , Passed to the wrapper function
var args = [func].concat(slice.call(arguments, 0));
// return wrapper The result of processing the function
return wrapper.apply(this, args);
};
};
// Combine multiple functions into 1 since , In the order in which the parameters are passed , after 1 The return value of each function will be 1 Pass to before as a parameter 1 Continue processing of functions as parameters
// _.compose(A, B, C); Is equivalent to A(B(C()));
// The disadvantage of this method is that the number of arguments processed by the associated function can only be 1 a , If more than one parameter needs to be passed , Can be achieved by Array or Object Composite data types are assembled
_.compose = function() {
// Get a list of functions , All parameters must be Function type
var funcs = arguments;
// return 1 A handle to a function to call
return function() {
// Execute the function from back to front , The return value of the record is passed to the previous as a parameter 1 A function continues processing
var args = arguments;
for(var i = funcs.length - 1; i >= 0; i--) {
args = [funcs[i].apply(this, args)];
}
// Returns the last 1 The return value of the called function
return args[0];
};
};
// return 1 A function , This function ACTS as a call counter , When the function is called times time ( Or more than times time ) after , func The function will be executed
// after methods 1 Generally used as an asynchronous counter , For example in multiple AJAX When all requests are completed, they need to be executed 1 A function , Then you can use after At the end of each AJAX Invoked when the request is complete
_.after = function(times, func) {
// If no specified or invalid number specified , the func Be called directly
if(times <= 0)
return func();
// return 1 Counter function
return function() {
// Each time the counter function is called times Reduction of 1, call times After execution func Function and returns func The return value of the function
if(--times < 1) {
return func.apply(this, arguments);
}
};
};
// Object correlation method
// ----------------
// To obtain 1 A list of property names for an object ( Does not contain attributes in the stereotype chain )
_.keys = nativeKeys ||
function(obj) {
if(obj !== Object(obj))
throw new TypeError('Invalid object');
var keys = [];
// Records and returns all property names of the object
for(var key in obj)
if(_.has(obj, key))
keys[keys.length] = key;
return keys;
};
// return 1 A list of values for all attributes in an object ( Does not contain attributes in the stereotype chain )
_.values = function(obj) {
return _.map(obj, _.identity);
};
// To obtain 1 The value of all attributes in the object is Function The type of key The list of , And according to the key Sort by name ( Contains attributes in the stereotype chain )
_.functions = _.methods = function(obj) {
var names = [];
for(var key in obj) {
if(_.isFunction(obj[key]))
names.push(key);
}
return names.sort();
};
// will 1 Properties of one or more objects ( Contains attributes in the stereotype chain ), Copied to the obj object , Overrides if an attribute with the same name exists
_.extend = function(obj) {
// each In the cycle parameter 1 Two or more objects
each(slice.call(arguments, 1), function(source) {
// Copies or overrides all properties in an object obj object
for(var prop in source) {
obj[prop] = source[prop];
}
});
return obj;
};
// return 1 A new object , And from the obj Copies the specified property to the new object
// The first 2 Starts with the specified property name to be copied ( Support for multiple parameters and deep arrays )
_.pick = function(obj) {
// create 1 An object , Store the specified properties for replication
var result = {};
// From the first 2 The parameters start to merge as 1 An array of property names
each(_.flatten(slice.call(arguments, 1)), function(key) {
// A list of cyclic attribute names , if obj The attribute exists in , Copies it to result object
if( key in obj)
result[key] = obj[key];
});
// Return copy result
return result;
};
// will obj Does not exist in or convert to Boolean The post-type value is false The properties of the , Specified from the argument 1 Copies to in one or more objects obj
// 1 Generally used to specify a default value for an object
_.defaults = function(obj) {
// From the first 2 Multiple objects can be specified at the beginning of an argument , The properties in these objects will be copied in turn to obj In the object ( if obj Does not exist in the object )
each(slice.call(arguments, 1), function(source) {
// Iterate through all the properties in each object
for(var prop in source) {
// if obj Does not exist or attribute values are converted to Boolean The post-type value is false, Copies the property to obj In the
if(obj[prop] == null)
obj[prop] = source[prop];
}
});
return obj;
};
// create 1 a obj A copy of the , return 1 A new object , The object contains obj State of all attributes and values in
// clone The function does not support deep replication , For example, obj One of the properties in 1 An object , The object will not be copied
// if obj is 1 An array , Will create 1 The same array object
_.clone = function(obj) {
// Data of non-array and object types is not supported
if(!_.isObject(obj))
return obj;
// Copies and returns an array or object
return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
};
// perform 1 A function , And will be obj Passed to the function as a parameter , The function finally returns after execution obj object
// 1 As you create 1 It will be used when you have a method chain tap methods , For example, :
// _(obj).chain().tap(click).tap(mouseover).tap(mouseout);
_.tap = function(obj, interceptor) {
interceptor(obj);
return obj;
};
// eq Function only in isEqual Call in a method , Used to compare two data values for equality
// with === Difference is that , eq Focus more on the value of the data
// If you are comparing two compound data types , It's not just comparing whether it comes from the same 1 A reference , And deep comparisons will be made ( Compare the structure and data of the two objects )
function eq(a, b, stack) {
// Check that the values of the two simple data types are equal
// For composite data types , If they come from the same source 1 A reference , And they say it's the same
// If the value being compared contains 0, Check the other 1 Is the value of -0, because 0 === -0 Is true
// while 1 / 0 == 1 / -0 It doesn't make sense (1 / 0 A value of Infinity, 1 / -0 A value of -Infinity, while Infinity Is not equal to -Infinity)
if(a === b)
return a !== 0 || 1 / a == 1 / b;
// If the value is after converting the data to a Boolean type false, The data types of the two values are determined to be equal ( because null with undefined, false, 0, An empty string , Values are equal under non-strict comparisons )
if(a == null || b == null)
return a === b;
// If the data you're comparing is 1 a Underscore Encapsulated object ( with _chain The object of the attribute is considered to be Underscore object )
// The object is unsealed to obtain its own data ( through _wrapped access ), And then compare their own data
// They have a similar relationship 1 a jQuery encapsulated DOM object , And the browser itself DOM object
if(a._chain)
a = a._wrapped;
if(b._chain)
b = b._wrapped;
// If the object provides custom isEqual methods ( Here the isEqual Method is not Undersocre The object's isEqual methods , Because in the 1 Step has been to Undersocre Object to understand )
// Use object custom isEqual Methods with the other 1 I'm going to compare them
if(a.isEqual && _.isFunction(a.isEqual))
return a.isEqual(b);
if(b.isEqual && _.isFunction(b.isEqual))
return b.isEqual(a);
// Validate the data types of the two data
// Access to the object a Data type of ( through Object.prototype.toString methods )
var className = toString.call(a);
// If the object a Data type and object b Don't match , The two data values are also considered to be mismatched
if(className != toString.call(b))
return false;
// Execute here , You can ensure that the two data types you want to compare are composite data types , And the data types are the same
// through switch Check the data type of the data , Make different comparisons for different data types
// ( Arrays and object types are not included here , Because they may contain deeper levels of data , A deeper comparison will be made later )
switch (className) {
case '[object String]':
// If the string type is being compared ( Among them a Is through new String() Created string )
// will B convert String Object is then matched ( Matches are not strictly checked for data types , Because they don't come from the same source 1 A reference to an object )
// In the call == When comparing , Object will be called automatically toString() methods , Returns a string of two simple data types
return a == String(b);
case '[object Number]':
// through +a will a into 1 a Number, if a Not before and after conversion , Is that a is 1 a NaN type
// because NaN with NaN It's not the same , So when a A value of NaN when , Can't be used simply a == b matching , I'm going to check it the same way b Whether it is NaN( namely b != +b)
// when a The value is 1 A non NaN When the data , The check a Whether it is 0, Because when b for -0 when , 0 === -0 Is true ( In fact, they logically belong to two different pieces of data )
return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);
case '[object Date]':
// No date type is used return or break, So it's going to go down 1 step ( Whether the data type is Boolean type , Because of the 1 Step will be to Boolean Type check )
case '[object Boolean]':
// Converts a date or Boolean type to a number
// The date type is converted to a timestamp of the numeric type ( Invalid date format will be converted to NaN)
// Boolean type , true Is converted to 1, false Is converted to 0
// Compares whether two date or Boolean types are equal after being converted to Numbers
return +a == +b;
case '[object RegExp]':
// Regular expression type , through source Access the string form of an expression
// Checks if the string form of two expressions is equal
// Check that the global properties of both expressions are the same ( including g, i, m)
// If it's exactly the same , The two data are considered equal
return a.source == b.source && a.global == b.global && a.multiline == b.multiline && a.ignoreCase == b.ignoreCase;
}
// When executed at this point , ab The two data types should be of the same object or array type
if( typeof a != 'object' || typeof b != 'object')
return false;
// stack( The heap ) Is in the isEqual call eq Function is an empty array passed internally , Called later in an internal iteration that compares objects and data eq The method will also pass
// length Record the length of the heap
var length = stack.length;
while(length--) {
// If an object and data in the heap a matching , Is considered equal to
if(stack[length] == a)
return true;
}
// The data a Add to the heap
stack.push(a);
// define 1 Some local variables
var size = 0, result = true;
// Deep comparison of objects and arrays through recursion
if(className == '[object Array]') {
// The data being compared is of type array
// size Record the length of the array
// result Compare the length of two arrays 1 to , If the length is not 1 to , The end of the method returns result( namely false)
size = a.length;
result = size == b.length;
// If the length of two arrays 1 to
if(result) {
// call eq Method to iterate through the elements of an array ( If the array contains 2 Dimensional arrays or objects , eq The method makes a deep comparison )
while(size--) {
// When ensuring that both arrays have elements of the current index , call eq Method deep comparison ( Pass the heap data to eq methods )
// Stores the results of the comparison to result variable , if result for false( That is, to get the data of an element in the comparison 1 to ), Then stop iteration
if(!( result = size in a == size in b && eq(a[size], b[size], stack)))
break;
}
}
} else {
// The data being compared is the object type
// If two objects are not the same 1 An instance of a class ( through constructor Attributes are ), The two objects are not equal
if('constructor' in a != 'constructor' in b || a.constructor != b.constructor)
return false;
// Deep comparison of data between two objects
for(var key in a) {
if(_.has(a, key)) {
// size Used to record the number of properties compared , Because what I'm doing here is I'm traversing a Properties of an object , And compare the b Object. The data for this property
// when b There are too many attributes in the object a When the object , The logic here holds , But the two objects are not equal
size++;
// Iterative invoke eq methods , Deep comparison of property values between two objects
// Record the results of the comparison result variable , Stop iterating when you compare unequal data
if(!( result = _.has(b, key) && eq(a[key], b[key], stack)))
break;
}
}
// End of deep comparison , Here you can already make sure in the object a All the data in , object b The same data exists in
// According to the size( Object attribute length ) Check the object b Is the number of attributes in the a equal
if(result) {
// Traverse object b All attributes in
for(key in b) {
// when size Have been to 0 when ( The object a The number of attributes in has been traversed ), The object b There are also properties , The object b Contains more properties than objects a
if(_.has(b, key) && !(size--))
break;
}
// When object b Contains more properties than objects a, The two objects are not equal
result = !size;
}
}
// When the function finishes executing , Removes the control from the heap 1 A data ( When comparing objects or arrays , Will the iteration eq methods , There may be more than one data in the heap )
stack.pop();
// The returned result The final comparison results are recorded
return result;
}
// Compare the values of the two data ( Support for composite data types ), Internal function eq External methods of
_.isEqual = function(a, b) {
return eq(a, b, []);
};
// Check that the data is null , contains '', false, 0, null, undefined, NaN, An empty array ( The array length is 0) And empty object ( The object itself has no properties )
_.isEmpty = function(obj) {
// obj Is converted to Boolean The post-type value is false
if(obj == null)
return true;
// Check whether the object or string length is 0
if(_.isArray(obj) || _.isString(obj))
return obj.length === 0;
// Check the object ( use for in When looping, the properties of the object itself are first looped , Then there are the attributes in the prototype chain ), So if the 1 Attributes belong to the object itself , So the object is not 1 An empty object
for(var key in obj)
if(_.has(obj, key))
return false;
// None of the data types passed validation , is 1 An empty data
return true;
};
// Verify that the object is 1 a DOM object
_.isElement = function(obj) {
return !!(obj && obj.nodeType == 1);
};
// Verify that the object is 1 Array types , Take precedence over calls provided by the host environment isArray methods
_.isArray = nativeIsArray ||
function(obj) {
return toString.call(obj) =