// 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 a global object, represented as a window object in the browser and a global object in Node.js
var root = this;
//Save the value before "_" (underscore variable) is overwritten
// If there is a naming conflict or considering the specification, the _.noConflict() method can be used to restore the value of "_" before it was occupied by Underscore, and return the Underscore object for renaming
var previousUnderscore = root._;
//Create an empty object constant for internal sharing and use
var breaker = {};
//Cache the prototype chain of the built-in object in a local variable to facilitate quick calling
var ArrayProto = Array.prototype, //
ObjProto = Object.prototype, //
FuncProto = Function.prototype;
//Cache common methods in built-in object prototypes in local variables for quick calling
var slice = ArrayProto.slice, //
unshift = ArrayProto.unshift, //
toString = ObjProto.toString, //
hasOwnProperty = ObjProto.hasOwnProperty;
// This defines some new methods provided by JavaScript 1.6
// If the host environment supports these methods, they will be called first. If the host environment does not provide them, they will be implemented by Underscore.
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-style calling method, which will return an Underscore wrapper. The prototype of the wrapper object contains all methods of Underscore (similar to wrapping a DOM object into a jQuery object)
var _ = function(obj) {
// All Underscore objects are constructed internally through wrapper objects
return new wrapper(obj);
};
// For different host environments, store Undersocre's named variables in different objects.
if( typeof exports !== 'undefined') {// Node.js environment
if( typeof module !== 'undefined' && module.exports) {
exports = module.exports = _;
}
exports._ = _;
} else {//The named variable of Underscore in the browser environment is hung in the window object
root['_'] = _;
}
// version statement
_.VERSION = '1.3.3';
// Collection-related methods (general processing methods for data and objects)
//--------------------
// Iterate the processor and execute the processor method on each element in the collection
var each = _.each = _.forEach = function(obj, iterator, context) {
//Do not handle null values
if(obj == null)
return;
if(nativeForEach && obj.forEach === nativeForEach) {
// If the host environment supports it, the forEach method provided by JavaScript 1.6 will be called first.
obj.forEach(iterator, context);
} else if(obj.length === obj.length) {
//Execute the processor method for each element in the <array>
for(var i = 0, l = obj.length; i < l; i ) {
if( i in obj && iterator.call(context, obj[i], i, obj) === breaker)
return;
}
} else {
// Execute the processor method for each element in <object>
for(var key in obj) {
if(_.has(obj, key)) {
if(iterator.call(context, obj[key], key, obj) === breaker)
return;
}
}
}
};
// Iteration processor, the difference from each method is that map will store the return value of each iteration and return it as a new array
_.map = _.collect = function(obj, iterator, context) {
//Array used to store return values
var results = [];
if(obj == null)
return results;
// Prioritize calling the map method provided by the host environment
if(nativeMap && obj.map === nativeMap)
return obj.map(iterator, context);
// Iterate through the elements in the collection
each(obj, function(value, index, list) {
// Store the return value of each iteration into the results array
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 processor, and pass the return value of this iteration as "memo" to the next iteration, generally used to accumulate results or connect data
_.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
// Check whether there is an initial value by the number of parameters
var initial = arguments.length > 2;
if(obj == null)
obj = [];
// Prioritize calling the reduce method provided by the host environment
if(nativeReduce && obj.reduce === nativeReduce && false) {
if(context)
iterator = _.bind(iterator, context);
return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
}
// Iterate through the elements in the collection
each(obj, function(value, index, list) {
if(!initial) {
// If there is no initial value, the first element is used as the initial value; if the object collection is processed, the default value is the value of the first attribute
memo = value;
initial = true;
} else {
// Record the processing results and pass the results to the next iteration
memo = iterator.call(context, memo, value, index, list);
}
});
if(!initial)
throw new TypeError('Reduce of empty array with no initial value');
return memo;
};
// Similar to reduce, it will iterate the elements in the collection in reverse direction (that is, starting from the last element to the first element)
_.reduceRight = _.foldr = function(obj, iterator, memo, context) {
var initial = arguments.length > 2;
if(obj == null)
obj = [];
// Prioritize calling the reduceRight method provided by the host environment
if(nativeReduceRight && obj.reduceRight === nativeReduceRight) {
if(context)
iterator = _.bind(iterator, context);
return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
}
//Reverse the order of elements in the collection
var reversed = _.toArray(obj).reverse();
if(context && !initial)
iterator = _.bind(iterator, context);
//Process data through reduce method
return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator);
};
// Traverse the elements in the collection and return the first element that can pass the processor verification
_.find = _.detect = function(obj, iterator, context) {
// result stores the first element that can pass verification
var result;
// Traverse the data through the any method and record the elements that pass verification
// (If you are checking the processor return status during iteration, it would be more appropriate to use the each method here)
any(obj, function(value, index, list) {
// If the result returned by the processor is converted to Boolean type and the value is true, then the current element is recorded and returned
if(iterator.call(context, value, index, list)) {
result = value;
return true;
}
});
return result;
};
// Similar to the find method, but the filter method will record all verified elements in the collection
_.filter = _.select = function(obj, iterator, context) {
// Used to store an array of elements that pass validation
var results = [];
if(obj == null)
return results;
// Give priority to calling the filter method provided by the host environment
if(nativeFilter && obj.filter === nativeFilter)
return obj.filter(iterator, context);
// Iterate over the elements in the collection and put the elements verified by the processor into the array and return
each(obj, function(value, index, list) {
if(iterator.call(context, value, index, list))
results[results.length] = value;
});
return results;
};
// The opposite effect of the filter method, that is, returning a list of elements that have not passed the processor verification
_.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 can pass the processor verification, return true
_.every = _.all = function(obj, iterator, context) {
var result = true;
if(obj == null)
return result;
// Give priority to calling every method provided by the host environment
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))
// Verify whether the result of the processor is a true value after being converted to Boolean type
if(!( result = result && iterator.call(context, value, index, list)))
return breaker;
});
return !!result;
};
// Check whether any element in the collection has a true value when it is converted to a Boolean type? Or whether it has a true value after being processed by the processor?
var any = _.some = _.any = function(obj, iterator, context) {
// If no processor parameters are specified, the default processor function will return the element itself, and determine whether it is a true value by converting the element to a Boolean type during iteration
iterator || ( iterator = _.identity);
var result = false;
if(obj == null)
return result;
// Give priority to calling some methods provided by the host environment
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 if there is a value in the collection that exactly matches the target parameter (the data type will also be matched)
_.include = _.contains = function(obj, target) {
var found = false;
if(obj == null)
return found;
// Prioritize calling the Array.prototype.indexOf method provided by the host environment
if(nativeIndexOf && obj.indexOf === nativeIndexOf)
return obj.indexOf(target) != -1;
// Iterate over the elements in the collection through the any method and verify whether the value and type of the element completely match the target.
found = any(obj, function(value) {
return value === target;
});
return found;
};
// Call the same-named methods of all elements in the collection in sequence, starting from the third parameter, which will be passed into the calling method of the element.
// Return an array that stores the processing results of all methods
_.invoke = function(obj, method) {
// Parameters passed when calling the method with the same name (starting from the 3rd parameter)
var args = slice.call(arguments, 2);
// Call the method of each element in turn and put the result into the array and return it
return _.map(obj, function(value) {
return (_.isFunction(method) ? method || value : value[method]).apply(value, args);
});
};
// Traverse an array consisting of a list of objects and return a list of values for the specified attribute in each object
_.pluck = function(obj, key) {
// If the property does not exist in an object, return undefined
return _.map(obj, function(value) {
return value[key];
});
};
//Return the maximum value in the collection, if there is no comparable value, return undefined
_.max = function(obj, iterator, context) {
// If the collection is an array and no processor is used, use Math.max to get the maximum value
// Generally, a series of Number type data is stored in an array.
if(!iterator && _.isArray(obj) && obj[0] === obj[0])
return Math.max.apply(Math, obj);
// For null values, return negative infinity directly
if(!iterator && _.isEmpty(obj))
return -Infinity;
// A temporary object, computed is used to store the maximum value during the comparison process (temporary)
var result = {
computed : -Infinity
};
//Iterate over the elements in the collection
each(obj, function(value, index, list) {
// If the processor parameter is specified, the compared data is the value returned by the processor, otherwise the default value during each traversal is used directly.
var computed = iterator ? iterator.call(context, value, index, list) : value;
// If the comparison value is greater than the previous value, put the current value into result.value
computed >= result.computed && ( result = {
value: value,
computed : computed
});
});
// Return the maximum value
return result.value;
};
//Return the minimum value in the set, the processing process is consistent with the max method
_.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;
};
// Use random numbers so that the array does not need to be arranged
_.shuffle = function(obj) {
// shuffled variables store the processing process and final result data
var shuffled = [], rand;
//Iterate over the elements in the collection
each(obj, function(value, index, list) {
// Generate a random number, the random number is between <0-the currently processed number>
rand = Math.floor(Math.random() * (index 1));
// Put the randomly obtained elements at the end of the shuffled array
shuffled[index] = shuffled[rand];
//Insert the latest value at the position of the previously obtained random number
shuffled[rand] = value;
});
// Return an array that stores randomly shuffled collection elements.
return shuffled;
};
// Arrange the elements in the collection according to specific fields or values
// Compared to the Array.prototype.sort method, the sortBy method supports sorting objects.
_.sortBy = function(obj, val, context) {
// val should be a property of the object, or a processor function. If it is a processor, it should return the data that needs to be compared.
var iterator = _.isFunction(val) ? val : function(obj) {
return obj[val];
};
// Calling sequence: _.pluck(_.map().sort());
// Call the _.map() method to traverse the collection, put the elements in the collection into the value node, and put the data that needs to be compared in the elements into the criteria attribute
//Call the sort() method to sort the elements in the collection according to the data in the criteria attribute.
// Call pluck to obtain the sorted object collection and return it
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');
};
// Divide the elements in the collection into multiple arrays according to the keys returned by the processor
_.groupBy = function(obj, val) {
var result = {};
// val will be converted into a processor function for grouping. If val is not a Function type data, it will be used as the key value when filtering elements.
var iterator = _.isFunction(val) ? val : function(obj) {
return obj[val];
};
//Iterate over the elements in the collection
each(obj, function(value, index) {
// Use the return value of the processor as the key, and put the same key elements into a new array
var key = iterator(value, index);
(result[key] || (result[key] = [])).push(value);
});
// Return 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;
};
//Convert a collection to an array and return
// Generally used to convert arguments into arrays, or convert unordered collections of objects into ordered collections in the form of data
_.toArray = function(obj) {
if(!obj)
return [];
if(_.isArray(obj))
return slice.call(obj);
//Convert arguments to array
if(_.isArguments(obj))
return slice.call(obj);
if(obj.toArray && _.isFunction(obj.toArray))
return obj.toArray();
//Convert the object to an array, which contains the value list of all properties in the object (excluding properties in the object's prototype chain)
return _.values(obj);
};
// Count the number of elements in the collection
_.size = function(obj) {
// If the collection is an array, count the number of array elements
// If the collection is an object, count the number of properties in the object (excluding properties in the object's prototype chain)
return _.isArray(obj) ? obj.length : _.keys(obj).length;
};
// Array related methods
// ---------------
// Return the first or n elements of an array specified in order
_.first = _.head = _.take = function(array, n, guard) {
// If parameter n is not specified, return the first element
// If n is specified, return a new array containing the specified number of n elements in sequence
//The guard parameter is used to determine that only the first element is returned. When guard is true, the specified number n is invalid.
return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
};
//Return a new array, containing other elements except the first element, or excluding n elements specified forward starting from the last element
// The difference from the first method is that first determines the position of the required element before the array, and initial determines the position of the excluded element at the end of the array.
_.initial = function(array, n, guard) {
// If parameter n is not passed, other elements except the last element will be returned by default
// If parameter n is passed, other elements except n elements forward starting from the last element are returned.
// guard is used to ensure that only one element is returned. When guard is true, the specified number n is invalid.
return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));
};
//Return the last n elements of the array or the specified n elements in reverse order
_.last = function(array, n, guard) {
if((n != null) && !guard) {
// Calculate and specify the obtained element position n, until the end of the array, and return it as a new array
return slice.call(array, Math.max(array.length - n, 0));
} else {
// If the number is not specified, or guard is true, only the last element is returned
return array[array.length - 1];
}
};
// Get other elements except the first or the specified first n elements
_.rest = _.tail = function(array, index, guard) {
// Calculate the second positional parameter of slice until the end of the array
// If index is not specified, or the guard value is true, return other elements except the first element.
// When the (index == null) value is true, the parameter passed to the slice function will be automatically converted to 1
return slice.call(array, (index == null) || guard ? 1 : index);
};
// Return all elements in the array whose values can be converted to true, and return 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;
});
};
// Combine a multi-dimensional number into a one-dimensional array, supporting deep merging
// The shallow parameter is used to control the depth of merging. When shallow is true, only the first layer is merged, and deep merging is performed by default.
_.flatten = function(array, shallow) {
// Iterate over each element in the array and pass the return value as demo to the next iteration
return _.reduce(array, function(memo, value) {
// If the element is still an array, make the following judgment:
// - If deep merging is not performed, use Array.prototype.concat to connect the current array and previous data.
// - If deep merging is supported, the flatten method is called iteratively until the underlying element is no longer an array type
if(_.isArray(value))
return memo.concat( shallow ? value : _.flatten(value));
// The data (value) is already at the bottom and is no longer an array type, then merge the data into memo and return
memo[memo.length] = value;
return memo;
}, []);
};
// Filter and return the difference data in the current array that is not equal to the specified data (please refer to the difference method comment)
_.without = function(array) {
return _.difference(array, slice.call(arguments, 1));
};
// Deduplicate the data in the array (use === for comparison)
// When the isSorted parameter is not false, the include method will be called on the elements in the array in sequence to check whether the same elements have been added to the return value (array)
// If you make sure the data in the array is arranged in order before calling, you can set isSorted to true. It will exclude the same value by comparing it with the last element. Using isSorted will be more efficient than the default include method.
// The uniq method will compare the data in the array by default. If the iterator processor is declared, a comparison array will be created based on the processor. The data in the array will prevail during comparison, but the only data returned in the end is still the original array
_.uniq = _.unique = function(array, isSorted, iterator) {
// If the iterator processor is used, the data in the current array will be processed by the iterator first, and a new processed array will be returned.
// The new array is used as a basis for comparison
var initial = iterator ? _.map(array, iterator) : array;
// Temporary array used to record processing results
var results = [];
// If there are only 2 values in the array, there is no need to use the include method for comparison. Setting isSorted to true can improve operating efficiency.
if(array.length < 3)
isSorted = true;
// Use the reduce method to iterate and accumulate the processing results
//The initial variable is the baseline data that needs to be compared. It may be an original array or a result set of the processor (if an iterator is set)
_.reduce(initial, function(memo, value, index) {
// If the isSorted parameter is true, directly use === to compare the last data in the record
// If the isSorted parameter is false, use the include method to compare with each data in the collection
if( isSorted ? _.last(memo) !== value || !memo.length : !_.include(memo, value)) {
//memo records the non-duplicate data that has been compared
// Depending on the status of the iterator parameter, the data recorded in memo may be original data or data processed by the processor
memo.push(value);
// The data stored in the processing result array is always the data in the original array
results.push(array[index]);
}
return memo;
}, []);
//Return the processing result, which only contains non-duplicate data in the array
return results;
};
// The union method has the same effect as the uniq method. The difference is that union allows multiple arrays to be passed in as parameters.
_.union = function() {
// union shallowly merges multiple arrays in the parameters into an array object and passes it to the uniq method for processing
return _.uniq(_.flatten(arguments, true));
};
// Get the intersection element of the current array and one or more other arrays
//Starting from the second parameter is one or more arrays that need to be compared
_.intersection = _.intersect = function(array) {
// The rest variable records other array objects that need to be compared.
var rest = slice.call(arguments, 1);
// Use the uniq method to remove duplicate data in the current array to avoid repeated calculations
// Filter the data of the current array through the processor and return data that meets the conditions (comparing the same elements)
return _.filter(_.uniq(array), function(item) {
// Use the every method to verify that each array contains the data that needs to be compared.
// If all arrays contain comparison data, then all return true, if any array does not contain the element, then return false
return _.every(rest, function(other) {
// The other parameter stores each array that needs to be compared
// item stores the data that needs to be compared in the current array
// Use the indexOf method to search whether the element exists in the array (please refer to the indexOf method comment)
return _.indexOf(other, item) >= 0;
});
});
};
// Filter and return the difference data in the current array that is not equal to the specified data
// This function is generally used to delete the specified data in the array and get the new array after deletion
// The function of this method is equivalent to without. The without method parameter does not allow data to be included in an array, while the difference method parameter is recommended to be an array (you can also use the same parameters as without).
_.difference = function(array) {
// Merge all parameters starting from the second parameter as an array (only merge the first level, not deep merge)
// The rest variable stores the verification data, which is used in this method to compare with the original data.
var rest = _.flatten(slice.call(arguments, 1), true);
// Filter the merged array data. The filter condition is that the current array does not contain the verification data specified by the parameter.
// Combine the data that meets the filter conditions into a new array and return it
return _.filter(array, function(value) {
return !_.include(rest, value);
});
};
//Return the data at the same position of each array as a new two-dimensional array. The length of the returned array is based on the maximum array length in the passed parameters. The blank positions of other arrays are filled with undefined.
// The zip method should contain multiple parameters, and each parameter should be an array
_.zip = function() {
//Convert the parameters into an array, at this time args is a two-dimensional array
var args = slice.call(arguments);
// Calculate the length of each array and return the maximum length value
var length = _.max(_.pluck(args, 'length'));
//Create a new empty array according to the maximum length value, which is used to store the processing results
var results = new Array(length);
//The maximum length of the loop, in each loop the pluck method will be called to obtain the data at the same position in each array (from 0 to the last position in sequence)
// Store the obtained data in a new array, put it into results and return
for(var i = 0; i < length; i )
results[i] = _.pluck(args, "" i);
//The returned result is a two-dimensional array
return results;
};
// Search for the first occurrence of an element in the array, and return -1 if the element does not exist
// Use === to match elements when searching
_.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;
}
// Prioritize calling the indexOf method provided by the host environment
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, returns -1
return -1;
};
//Returns the position of the last occurrence of an element in the array, or -1 if the element does not exist
// Use === to match elements when searching
_.lastIndexOf = function(array, item) {
if(array == null)
return -1;
// Prioritize calling the lastIndexOf method provided by the host environment
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, returns -1
return -1;
};
// Based on the interval and step size, generate a series of integers and return them as an array
// The start parameter represents the minimum number
// The stop parameter indicates the maximum number
//The step parameter represents the step value between generating multiple values.
_.range = function(start, stop, step) {
// Parameter control
if(arguments.length <= 1) {
// If there are no parameters, start = 0, stop = 0, no data will be generated in the loop, and an empty array will be returned.
// If there is 1 parameter, the parameter is assigned to stop, start = 0
stop = start || 0;
start = 0;
}// Generate the step value of the 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);
varidx = 0;
var range = new Array(len);
// Generate a list of integers and store it in the range array
while(idx < len) {
range[idx] = start;
start = step;
}
//Return list results
return range;
};
//Function related methods
// ------------------
//Create a public function object for setting the prototype
var ctor = function() {
};
//Bind the execution context for a function. Whenever the function is called, this in the function points to the context object.
// When binding a function, you can pass the calling parameters to the function at the same time
_.bind = function bind(func, context) {
var bound, args;
// Prioritize calling the bind method provided by the host environment
if(func.bind === nativeBind && nativeBind)
return nativeBind.apply(func, slice.call(arguments, 1));
// The func parameter must be a function type
if(!_.isFunction(func))
throw new TypeError;
// The args variable stores the third parameter list starting from the bind method, which will be passed to the func function each time it is called.
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;
};
};
// Bind the specified function, or all functions of the object itself, to the object itself. When the bound function is called, the context object always points to the object itself.
// This method is generally used when handling object events, for example:
// _(obj).bindAll(); // or _(obj).bindAll('handlerClick');
// document.addEventListener('click', obj.handlerClick);
// In the handlerClick method, the context is still the obj object
_.bindAll = function(obj) {
//The second parameter starts to indicate the name of the function that needs to be bound
var funcs = slice.call(arguments, 1);
// If no specific function name is specified, all properties of type Function are bound to the object itself by default.
if(funcs. length == 0)
funcs = _.functions(obj);
// Loop and set all function contexts to the obj object itself
//The each method itself does not traverse the methods in the object's prototype chain, but the funcs list here is obtained through the _.functions method, which already contains the methods in the prototype chain
each(funcs, function(f) {
obj[f] = _.bind(obj[f], obj);
});
return obj;
};
// The memoize method will return a function, which integrates the caching function, caches the calculated value into a local variable and returns it directly the next time it is called.
// If the calculation result is a huge object or data, the memory usage should be considered when using it.
_.memoize = function(func, hasher) {
//memo object used to store cached results
var memo = {};
// The hasher parameter should be a function, which is used to return a key, which is used as an identifier for reading the cache
// If the key is not specified, the first parameter of the function is used as the key by default. If the first parameter of the function is a composite data type, a key similar to [Object object] may be returned. This key may cause subsequent calculation errors. Data is incorrect
hasher || ( hasher = _.identity);
// Return a function that first checks the cache and then calls the 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 execution of a function
// The unit of wait is ms, and the third parameter will be passed to the execution function in sequence.
_.delay = function(func, wait) {
var args = slice.call(arguments, 2);
return setTimeout(function() {
return func.apply(null, args);
}, wait);
};
// Delay execution function
// setTimeout in JavaScript will be executed in a separate function stack. The execution time is after all functions called in the current stack have been executed.
// defer sets the function to be executed after 1ms. The purpose is to put the func function into a separate stack and wait for the current function to complete before executing it.
// The defer method is generally used to handle the priority of DOM operations to achieve correct logical flow and a smoother interactive experience.
_.defer = function(func) {
return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
};
// Function throttling method. The throttle method is mainly used to control the execution frequency of functions. Within the controlled time interval, frequently called functions will not be executed multiple times.
// If the function is called multiple times within the time interval, it will be automatically called once when the time interval expires. There is no need to wait until the time expires before calling it manually (there will be no return value when automatically called)
// The throttle function is generally used to handle complex and frequently called functions. It controls the calling frequency of functions by throttling and saves processing resources.
// For example, the event function bound to window.onresize or the event function bound to element.onmousemove can be packaged with throttle.
//The throttle method returns a function, which automatically calls func and performs throttling control
_.throttle = function(func, wait) {
var context, args, timeout, throttling, more, result;
// The whenDone variable calls the debounce method, so when the function is called multiple times, the last call will overwrite the previously called timer, and the clear status function will only be executed once
//The whenDone function is called when the time interval of the last function execution expires, clearing the throttling and some states recorded during the calling process
var whenDone = _.debounce(function() {
more = throttling = false;
}, wait);
// Return a function and perform throttling control within the function
return function() {
//Save the execution context and parameters of the function
context = this;
args = arguments;
//The later function is executed when the time interval of the last function call expires
var later = function() {
// Clear the timeout handle to facilitate the next function call
timeout = null;
// more records whether the function was called repeatedly between the last call and the time interval expiration
// If the function is called repeatedly, the function will be automatically called again when the time interval expires.
if(more)
func.apply(context, args);
// Call whenDone, used to clear the throttling state after the time interval
whenDone();
};
// timeout records the time interval handle of the last function execution
// Call the later function when the timeout interval expires. The timeout will be cleared later and check whether the function needs to be called again.
if(!timeout)
timeout = setTimeout(later, wait);
// The throttling variable records whether the time interval of the last call has ended, that is, whether it is in the throttling process
// throttling is set to true every time the function is called, indicating the need for throttling, and is set to false when the time interval expires (implemented in the whenDone function)
if(throttling) {
// Multiple calls are made during the throttling process, and a status is recorded in more, indicating that the function needs to be automatically called again when the time interval expires.
more = true;
} else {
// It is not in the throttling process. It may be the first time to call the function, or the interval of the last call has exceeded. You can call the function directly.
result = func.apply(context, args);
}
// Call whenDone, used to clear the throttling state after the time interval
whenDone();
// The throttling variable records the throttling status when the function is called.
throttling = true;
//Return the call result
return result;
};
};
// The debounce method is similar to the throttle method, used for function throttling. The difference between them is:
// -- throttle focuses on the execution frequency of the function. The function will only be executed once within the specified frequency;
// -- The debounce function pays more attention to the interval between function executions, that is, the time between two function calls cannot be less than the specified time;
// If the execution interval between two functions is less than wait, the timer will be cleared and re-created, which means that if the function is called continuously and frequently, the function will not be executed until the time between a certain call and the previous call is not less than wait. millisecond
// The debounce function is generally used to control operations that take a period of time to be executed. For example, to prompt the user 200ms after the user completes input, you can use debounce to wrap a function and bind it to the onkeyup event.
//------------------------------------------------ ----------------
// @param {Function} func represents the executed function
// @param {Number} wait represents the allowed time interval. Repeated calls within this time range will be re-postponed by wait milliseconds.
// @param {Boolean}immediate indicates whether the function will be executed immediately after it is called, true means it will be called immediately, false means it will be called when the time expires.
// The debounce method returns a function, which automatically calls func and performs throttling control
_.debounce = function(func, wait, immediate) {
// timeout is used to record the execution status of the last function call (timer handle)
// When timeout is null, it means that the last call has ended
var timeout;
// Return a function and perform throttling control within the function
return function() {
// Keep the context object and parameters of the function
var context = this, args = arguments;
var later = function() {
//Set timeout to null
// The later function will be called when the allowed time expires
// When calling this function, it indicates that the last function execution time has exceeded the agreed time interval, and subsequent calls after this time are allowed.
timeout = null;
if(!immediate)
func.apply(context, args);
};
// If the function is set to execute immediately and the time interval of the last call has passed, call the function immediately
if(immediate && !timeout)
func.apply(context, args);
//Create a timer to check and set the calling status of the function
// Before creating a timer, clear the last setTimeout handle, regardless of whether the last bound function has been executed.
// If when this function is called, the execution of the previous function has not yet started (usually when immediate is set to false), the execution time of the function will be delayed, so the timeout handle will be re-created
clearTimeout(timeout);
// Call the later function when the allowed time expires
timeout = setTimeout(later, wait);
};
};
// Create a function that will only be executed once. If the function is called repeatedly, the result of the first execution will be returned.
// This function is used to obtain and calculate the logic of fixed data, such as obtaining the browser type used by the user
_.once = function(func) {
// ran records whether the function has been executed
//memo records the result of the last execution of the function
var ran = false, memo;
return function() {
// If the function has been executed before, directly return the result of the first execution
if(ran)
return memo;
ran = true;
return memo = func.apply(this, arguments);
};
};
// Returns a function that passes the current function as a parameter to a wrapper function
// In the wrapped function, you can call the current function through the first parameter and return the result
// Generally used for low-coupling combined calls of multiple process processing functions
_.wrap = function(func, wrapper) {
return function() {
// Pass the current function as the first parameter to the wrapper function
var args = [func].concat(slice.call(arguments, 0));
// Return the processing result of the wrapper function
return wrapper.apply(this, args);
};
};
// Combine multiple functions together. According to the order of parameter passing, the return value of the latter function will be passed as a parameter to the previous function as a parameter to continue processing.
// _.compose(A, B, C); is equivalent to A(B(C()));
// The disadvantage of this method is that the number of parameters processed by the associated function can only be one. If multiple parameters need to be passed, they can be assembled through the Array or Object composite data type.
_.compose = function() {
// Get the function list, all parameters must be of Function type
var funcs = arguments;
//Return a function handle for calling
return function() {
// Execute the functions in sequence from back to front, and pass the recorded return value as a parameter to the previous function to continue processing.
var args = arguments;
for(var i = funcs.length - 1; i >= 0; i--) {
args = [funcs[i].apply(this, args)];
}
//Return the return value of the last function call
return args[0];
};
};
// Return a function that serves as a call counter. When the function is called times times (or exceeds times times), the func function will be executed.
// The after method is generally used as an asynchronous counter. For example, if you need to execute a function after multiple AJAX requests are completed, you can use after to call it after each AJAX request is completed.
_.after = function(times, func) {
// If no or invalid times are specified, func is called directly
if(times <= 0)
return func();
// Return a counter function
return function() {
// Each time the counter function times is called, decrement it by 1. After calling times times, execute the func function and return the return value of the func function.
if(--times < 1) {
return func.apply(this, arguments);
}
};
};
// Object related methods
//----------------
// Get a list of attribute names of an object (excluding attributes in the prototype chain)
_.keys = nativeKeys ||
function(obj) {
if(obj !== Object(obj))
throw new TypeError('Invalid object');
var keys = [];
//Record and return all property names of the object
for(var key in obj)
if(_.has(obj, key))
keys[keys.length] = key;
return keys;
};
// Return a value list of all properties in an object (excluding properties in the prototype chain)
_.values = function(obj) {
return _.map(obj, _.identity);
};
// Get a key list of all property values in an object that are Function type, and sort them by key name (including properties in the prototype chain)
_.functions = _.methods = function(obj) {
var names = [];
for(var key in obj) {
if(_.isFunction(obj[key]))
names.push(key);
}
return names.sort();
};
// Copy the properties of one or more objects (including properties in the prototype chain) to the obj object, and overwrite them if there are properties with the same name.
_.extend = function(obj) {
// One or more objects in each loop parameter
each(slice.call(arguments, 1), function(source) {
//Copy or overwrite all properties in the object to the obj object
for(var prop in source) {
obj[prop] = source[prop];
}
});
return obj;
};
// Return a new object and copy the specified properties from obj to the new object
//The second parameter starts with the specified attribute name that needs to be copied (supports multiple parameters and deep arrays)
_.pick = function(obj) {
//Create an object to store the copied specified attributes
var result = {};
// Starting from the second parameter, merge it into an array to store the list of attribute names.
each(_.flatten(slice.call(arguments, 1)), function(key) {
// Loop through the list of attribute names. If the attribute exists in obj, copy it to the result object.
if(key in obj)
result[key] = obj[key];
});
//Return the copied result
return result;
};
// Copy attributes that do not exist in obj or have a value of false after conversion to Boolean type from one or more objects specified in the parameter to obj
// Generally used to assign default values to objects
_.defaults = function(obj) {
// Multiple objects can be specified starting from the second parameter, and the properties in these objects will be copied to the obj object in turn (if the property does not exist in the obj object)
each(slice.call(arguments, 1), function(source) {
// Iterate through all properties in each object
for(var prop in source) {
// If it does not exist in obj or the attribute value is false after being converted to Boolean type, copy the attribute to obj
if(obj[prop] == null)
obj[prop] = source[prop];
}
});
return obj;
};
//Create a copy of obj and return a new object that contains the status of all properties and values in obj
// The clone function does not support deep copying. For example, if a property in obj stores an object, the object will not be copied.
// If obj is an array, an identical array object will be created
_.clone = function(obj) {
//Does not support non-array and object type data
if(!_.isObject(obj))
return obj;
//Copy and return array or object
return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
};
//Execute a function and pass obj as a parameter to the function. After the function is executed, the obj object is finally returned.
// Generally, the tap method is used when creating a method chain, for example:
// _(obj).chain().tap(click).tap(mouseover).tap(mouseout);
_.tap = function(obj, interceptor) {
interceptor(obj);
return obj;
};
// The eq function is only called in the isEqual method and is used to compare whether the values of two data are equal.
//The difference from === is that eq pays more attention to the value of the data
// If the comparison is between two composite data types, it will not only compare whether they come from the same reference, but also perform a deep comparison (comparing the structure and data of the two objects)
function eq(a, b, stack) {
// Check whether the values of two simple data types are equal
// For composite data types, they are considered equal if they come from the same reference
// If the value being compared contains 0, check whether the other value is -0, because 0 === -0 is true
// And 1 / 0 == 1 / -0 is not true (1 / 0 value is Infinity, 1 / -0 value is -Infinity, and Infinity is not equal to -Infinity)
if(a === b)
return a !== 0 || 1 / a == 1 / b;
// After converting the data to a Boolean type, if the value is false, it will be judged whether the data types of the two values are equal (because null is equal to undefined, false, 0, and empty string under non-strict comparison)
if(a == null || b == null)
return a === b;
// If the data being compared is an Underscore encapsulated object (objects with the _chain attribute are considered Underscore objects)
// Then unwrap the object and obtain its own data (accessed through _wrapped), and then compare its own data.
// Their relationship is similar to a DOM object encapsulated by jQuery, and a DOM object created by the browser itself
if(a._chain)
a = a._wrapped;
if(b._chain)
b = b._wrapped;
// If the object provides a custom isEqual method (the isEqual method here is not the isEqual method of the Undersocre object, because the Undersocre object has been unblocked in the previous step)
// Then use the object's custom isEqual method to compare with another object
if(a.isEqual && _.isFunction(a.isEqual))
return a.isEqual(b);
if(b.isEqual && _.isFunction(b.isEqual))
return b.isEqual(a);
//Verify the data types of the two data
// Get the data type of object a (via Object.prototype.toString method)
var className = toString.call(a);
// If the data type of object a does not match object b, the two data values are considered to also not match.
if(className != toString.call(b))
return false;
// After executing here, you can ensure that the two data that need to be compared are composite data types and the data types are equal.
// Check the data type of the data through switch, and perform different comparisons for different data types.
// (Arrays and object types are not included here, because they may contain deeper data, which will be compared later)
switch (className) {
case '[object String]':
// If what is being compared is a string type (where a is a string created through new String())
// Then convert B into a String object and then match (the matching here does not perform strict data type checking, because they are not references from the same object)
// When calling == for comparison, the toString() method of the object will be automatically called, returning two simple data type strings
return a == String(b);
case '[object Number]':
// Convert a into a Number through a. If a is not equal before and after conversion, it is considered that a is a NaN type.
// Because NaN and NaN are not equal, when the value of a is NaN, you cannot simply use a == b to match, but use the same method to check whether b is NaN (i.e. b != b)
// When the value of a is a non-NaN data, check whether a is 0, because when b is -0, 0 === -0 is established (in fact, they logically belong to two different data )
return a != a ? b != b : (a == 0 ? 1 / a == 1 / b : a == b);
case '[object Date]':
// There is no return or break used for the date type, so the execution will continue to the next step (regardless of whether the data type is a Boolean type, because the Boolean type will be checked in the next step)
case '[object Boolean]':
//Convert date or boolean type to number
// The date type will be converted to a numeric timestamp (invalid date format will be converted to NaN)
// In Boolean type, true is converted to 1, false is converted to 0
// Compare two dates or Boolean types to see if they are equal after being converted to numbers.
return a == b;
case '[object RegExp]':
// Regular expression type, access the string form of the expression through source
// Check whether the string forms of two expressions are equal
// Check whether the global properties of the two expressions are the same (including g, i, m)
// If they are completely equal, 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 to this point, the two data ab should be of the same type of object or array type
if( typeof a != 'object' || typeof b != 'object')
return false;
// stack (heap) is the empty array passed internally when isEqual calls the eq function. It will also be passed when the eq method is called in the internal iteration of comparing objects and data later.
//length records the length of the heap
var length = stack.length;
while(length--) {
// If an object in the heap matches data a, it is considered equal
if(stack[length] == a)
return true;
}
//Add data a to the heap
stack.push(a);
//Define 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 array type
// size records the length of the array
// result compares the lengths of the two arrays to see if they are consistent. If the lengths are inconsistent, the result (ie false) will be returned at the end of the method.
size = a.length;
result = size == b.length;
// If the lengths of the two arrays are the same
if(result) {
//Call the eq method to iteratively compare the elements in the array (if the array contains a two-dimensional array or object, the eq method will perform a deep comparison)
while(size--) {
// When ensuring that both arrays contain elements at the current index, call the eq method for deep comparison (pass the heap data to the eq method)
// Store the comparison result in the result variable. If result is false (that is, the data of a certain element obtained during the comparison is inconsistent), stop iteration
if(!( result = size in a == size in b && eq(a[size], b[size], stack)))
break;
}
}
} else {
//The data being compared is of object type
// If the two objects are not instances of the same class (compared through constructor properties), the two objects are considered not equal.
if('constructor' in a != 'constructor' in b || a.constructor != b.constructor)
return false;
// Deeply compare the data in two objects
for(var key in a) {
if(_.has(a, key)) {
// size is used to record the number of compared attributes, because what is traversed here is the attributes of the a object, and compares the data of the attribute in the b object
// When the number of attributes in object b exceeds object a, the logic here holds, but the two objects are not equal.
size ;
// Iteratively call the eq method to deeply compare the attribute values in the two objects
// Record the comparison result to the result variable, and stop iteration when unequal data is compared.
if(!( result = _.has(b, key) && eq(a[key], b[key], stack)))
break;
}
}
// The deep comparison is completed. It can be ensured that all the data in object a and the same data in object b also exist.
// Check whether the number of attributes in object b is equal to object a based on size (object attribute length)
if(result) {
// Traverse all properties in object b
for(key in b) {
// When size has reached 0 (that is, the number of attributes in object a has been traversed), and there are still attributes in object b, then object b has more attributes than object a.
if(_.has(b, key) && !(size--))
break;
}
// When object b has more attributes than object a, the two objects are considered not equal.
result = !size;
}
}
// When the function completes execution, remove the first data from the heap (when comparing objects or arrays, the eq method will be iterated, and there may be multiple data in the heap)
stack.pop();
//The returned result records the final comparison result
return result;
}
// Compare the values of two data (supports composite data types), external method of internal function eq
_.isEqual = function(a, b) {
return eq(a, b, []);
};
// Check whether the data is empty, including '', false, 0, null, undefined, NaN, empty array (array length is 0) and empty object (the object itself has no properties)
_.isEmpty = function(obj) {
// obj is converted to Boolean type and the 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 (when using a for in loop, the properties of the object itself will be looped first, followed by the properties in the prototype chain), so if the first property belongs to the object itself, then the object is not an empty object
for(var key in obj)
if(_.has(obj, key))
return false;
// All data types have not passed verification and are empty data
return true;
};
// Verify whether the object is a DOM object
_.isElement = function(obj) {
return !!(obj && obj.nodeType == 1);
};
// Verify whether the object is an array type, first call the isArray method provided by the host environment
_.isArray = nativeIsArray ||
function(obj) {
return toString.call(obj) == '[object Array]';
};
// Verify whether the object is a composite data type object (i.e. non-basic data type String, Boolean, Number, null, undefined)
// If the basic data type is created through new, it also belongs to the object type
_.isObject = function(obj) {
return obj === Object(obj);
};
// Check whether a data is an arguments parameter object
_.isArguments = function(obj) {
return toString.call(obj) == '[object Arguments]';
};
// Verify the isArguments function. If the running environment cannot verify the arguments type data normally, redefine the isArguments method.
if(!_.isArguments(arguments)) {
// If the environment cannot verify the argument type through toString, verify it by calling the unique callee method of arguments.
_.isArguments = function(obj) {
// callee is an attribute of arguments, pointing to a reference to the function itself to which arguments belongs.
return !!(obj && _.has(obj, 'callee'));
};
}
// Verify whether the object is a function type
_.isFunction = function(obj) {
return toString.call(obj) == '[object Function]';
};
// Verify whether the object is a string type
_.isString = function(obj) {
return toString.call(obj) == '[object String]';
};
// Verify whether the object is a numeric type
_.isNumber = function(obj) {
return toString.call(obj) == '[object Number]';
};
// Check whether a number is a valid number and has a valid range (Number type, value between negative infinity - positive infinity)
_.isFinite = function(obj) {
return _.isNumber(obj) && isFinite(obj);
};
// Check whether the data is of type NaN (only NaN and NaN are not equal to all data)
_.isNaN = function(obj) {
return obj !== obj;
};
// Check whether the data is of type Boolean
_.isBoolean = function(obj) {
//Supports Boolean data in literal and object form
return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
};
// Check whether the data is a Date type
_.isDate = function(obj) {
return toString.call(obj) == '[object Date]';
};
// Check if the data is a regular expression type
_.isRegExp = function(obj) {
return toString.call(obj) == '[object RegExp]';
};
// Check if the data is a Null value
_.isNull = function(obj) {
return obj === null;
};
// Check whether the data is an Undefined value
_.isUndefined = function(obj) {
return obj ===
void 0;
};
// Check whether a property belongs to the object itself, not in the prototype chain
_.has = function(obj, key) {
return hasOwnProperty.call(obj, key);
};
// Utility function
// ------------------
// Discard the Underscore object named by _ (underscore) and return the Underscore object, which is generally used to avoid naming conflicts or standardize naming methods.
// For example:
// var us = _.noConflict(); // Cancel the _ (underscore) naming and store the Underscore object in the us variable
// console.log(_); // _ (underscore) can no longer access the Underscore object and returns to the value before Underscore was defined.
_.noConflict = function() {
// The previousUnderscore variable records the value of _ (underscore) before Underscore is defined.
root._ = previousUnderscore;
return this;
};
// Return the same value as the parameter, generally used to convert a data acquisition method into a function acquisition method (used internally as the default processor function when building methods)
_.identity = function(value) {
return value;
};
// Make the specified function iteratively execute n times (no parameters)
_.times = function(n, iterator, context) {
for(var i = 0; i < n; i )
iterator.call(context, i);
};
// Convert special characters in HTML strings to HTML entities, including & < > " '
_.escape = function(string) {
return ('' string).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/ "/g, '"').replace(/'/g, ''').replace(///g, '/');
};
//Specify the attribute of an object and return the value corresponding to the attribute. If the attribute corresponds to a function, the function will be executed and the result will be returned.
_.result = function(object, property) {
if(object == null)
return null;
// Get the value of the object
var value = object[property];
// If the value is a function, execute it and return it, otherwise it will return directly
return _.isFunction(value) ? value.call(object) : value;
};
//Add a series of custom methods to the Underscore object to extend the Underscore plug-in
_.mixin = function(obj) {
//obj is an object that collects a series of custom methods. Here, the method of traversing the object is through each
each(_.functions(obj), function(name) {
//Add custom methods to objects built by Underscore through the addToWrapper function to support object-based calling
// Also add methods to _ itself to support functional calls
addToWrapper(name, _[name] = obj[name]);
});
};
// Get a globally unique identifier, the identifier starts from 0 and accumulates
var idCounter = 0;
// prefix represents the prefix of the identifier. If no prefix is specified, the identifier will be returned directly. It is generally used to create a unique ID for an object or DOM.
_.uniqueId = function(prefix) {
var id = idCounter;
return prefix ? prefix id : id;
};
// Define the delimiting symbol of the template, used in the template method
_.templateSettings = {
//Delimiter of JavaScript executable code
evaluate : /<%([sS] ?)%>/g,
// Directly output the delimiter of the variable
interpolate : /<%=([sS] ?)%>/g,
//Delimiter required to output HTML as a string (convert special symbols into string form)
escape : /<%-([sS] ?)%>/g
};
var noMatch = /.^/;
// The escapes object records the correspondence between special symbols and string forms that need to be converted to each other, and is used as an index when the two are converted to each other.
// First define special characters according to string form
var escapes = {
'\' : '\',
"'" : "'",
'r' : 'r',
'n' : 'n',
't' : 't',
'u2028' : 'u2028',
'u2029' : 'u2029'
};
// Traverse all special character strings and record the string form using special characters as keys
for(var p in escapes)
escapes[escapes[p]] = p;
// Define the special symbols that need to be replaced in the template, including backslash, single quote, carriage return, line feed, tab, line separator, paragraph separator
// Used when converting special symbols in a string to string form
var escaper = /\|'|r|n|t|u2028|u2029/g;
// Used when reversing (replacing) special symbols in string form
var unescaper = /\(\|'|r|n|t|u2028|u2029)/g;
//Reverse special symbols in string
// The JavaScript source code that needs to be executed in the template needs to be reversed with special symbols, otherwise if it appears in the form of HTML entities or strings, a syntax error will be thrown
var unescape = function(code) {
return code.replace(unescaper, function(match, escape) {
return escapes[escape];
});
};
// Underscore template parsing method, used to fill data into a template string
// Template parsing process:
// 1. Convert special symbols in the template to strings
// 2. Parse the escape form tag and parse the content into HTML entities
// 3. Parse interpolate form tags and output variables
// 4. Parse the evaluate form tag and create executable JavaScript code
// 5. Generate a processing function that can directly fill in the template after getting the data and return the filled string
// 6. Return the filled string or the handle of the processing function according to the parameters
//------------------
//In the template body, two parameters can be obtained through argments, namely the filling data (named obj) and the Underscore object (named _)
_.template = function(text, data, settings) {
// Template configuration, if no configuration item is specified, the configuration item specified in templateSettings is used.
settings = _.defaults(settings || {}, _.templateSettings);
// Start parsing the template into executable source code
var source = "__p ='" text.replace(escaper, function(match) {
//Convert special symbols to string form
return '\' escapes[match];
}).replace(settings.escape || noMatch, function(match, code) {
// Parse the escape form tag <%- %>, convert the HTML contained in the variable into an HTML entity through the _.escape function
return "' n_.escape(" unescape(code) ") n'";
}).replace(settings.interpolate || noMatch, function(match, code) {
// Parse the interpolate form tag <%= %>, connect the template content as a variable with other strings, and it will be output as a variable
return "' n(" unescape(code) ") n'";
}).replace(settings.evaluate || noMatch, function(match, code) {
// Parse the evaluate form tag <% %>. The JavaScript code that needs to be executed is stored in the evaluate tag. The current string splicing ends here and is executed as JavaScript syntax in a new line, and the following content is used as characters again. The beginning of the string, so the JavaScript code in the evaluate tag can be executed normally.
return "';n" unescape(code) "n;__p ='";
}) "';n";
if(!settings.variable)
source = 'with(obj||{}){n' source '}n';
source = "var __p='';" "var print=function(){__p =Array.prototype.join.call(arguments, '')};n" source "return __p;n";
// Create a function, use the source code as the function execution body, and pass obj and Underscore as parameters to the function
var render = new Function(settings.variable || 'obj', '_', source);
// If the fill data of the template is specified, replace the template content and return the replaced result
if(data)
return render(data, _);
// If no fill data is specified, returns a function that replaces the received data into the template
// If the same template will be filled multiple times in the program, it is recommended not to specify the filling data in the first call. After obtaining the reference of the processing function, calling it directly will improve the operating efficiency.
var template = function(data) {
return render.call(this, data, _);
};
//Add the created source code string to the function object, generally used for debugging and testing
template.source = 'function(' (settings.variable || 'obj') '){n' source '}';
//채우는 데이터가 지정되지 않은 경우 처리 함수 핸들을 반환합니다.
반환 템플릿;
};
// Underscore 객체의 메소드 체인 작업을 지원합니다. Wrapper.prototype.chain을 참조하세요.
_.체인 = 함수(obj) {
return _(obj).chain();
};
// Underscore 객체는 관련 메소드를 캡슐화합니다.
// ---------------
//원시 데이터를 래핑하기 위한 래퍼 생성
// 모든 undersocre 객체는 래퍼 함수를 통해 내부적으로 생성되고 캡슐화됩니다.
//Underscore와 래퍼 사이의 내부 관계:
// - 내부적으로 _ 변수를 정의하고, _에 Underscore 관련 메소드를 추가하여 _.bind() 등의 함수 호출을 지원할 수 있도록 합니다.
// - 래퍼 클래스를 내부적으로 정의하고 _의 프로토타입 객체가 래퍼 클래스의 프로토타입을 가리키도록 합니다.
// - 래퍼 프로토타입에 Underscore 관련 메서드를 추가하면 생성된 _ 객체에 Underscore 메서드가 포함됩니다.
// - 래퍼 프로토타입에 Array.prototype 관련 메서드를 추가하면 생성된 _ 객체는 Array.prototype의 메서드를 갖게 됩니다.
// -new _()는 실제로 래퍼() 객체를 생성 및 반환하고, 원래 배열을 _wrapped 변수에 저장하고, 원래 값을 첫 번째 매개변수로 사용하여 해당 메서드를 호출합니다.
var 래퍼 = function(obj) {
//원래 데이터는 래핑된 객체의 _wrapped 속성에 저장됩니다.
this._wrapped = obj;
};
// Underscore의 프로토타입 개체가 래퍼의 프로토타입을 가리키도록 하므로 래퍼 프로토타입과 같은 메서드를 추가하면 Underscore 개체도 동일한 메서드를 갖게 됩니다.
_.prototype = 래퍼.프로토타입;
//객체를 반환합니다. 현재 Underscore가 chain() 메서드를 호출하면(즉, _chain 속성이 true) 래핑된 Underscore 객체가 반환되고, 그렇지 않으면 객체 자체가 반환됩니다.
// 결과 함수
Copy after login