There is one aspect that was not mentioned in the each method earlier, which is $break and $continue in the source code. These two variables are predefined, and their functions are equivalent to the break and continue statements in ordinary loops. For efficiency reasons, in some operations it is not necessary to completely traverse a collection (not limited to an array), so break and continue are still necessary.
For a loop, compare the following ways to exit the loop:
var array_1 = [1,2,3];
var array_2 = ['a','b','c'];
(function(){
for( var i = 0, len = array_1.length; i < len; i ){
for(var j = 0, len_j = array_1.length; i < len_j; j ){
if('c ' === array_2[j]){
break;
}
console.log(array_2[j]);
}
}
})();// a,b,a,b,a,b
(function(){
for(var i = 0, len = array_1.length; i < len; i ){
try{
for(var j = 0, len_j = array_1.length; i < len_j; j ){
if('c' === array_2[j]){
throw new Error();
}
console.log(array_2[j]);
}
}catch(e){
console.log('Exit one level of loop');
}
}
})();//a,b,'Exit one level of loop',a,b,'Exit one level of loop',a,b,'Exit one level of loop'
(function() {
try{
for(var i = 0, len = array_1.length; i < len; i ){
for(var j = 0, len_j = array_1.length; i < len_j ; j ){
if('c' === array_2[j]){
throw new Error();
}
console.log(array_2[j]);
}
}
}catch(e){
console.log('Exit one level of loop');
}
})(); //a,b,'Exit one level of loop' Layer loop'
When we put error trapping at the corresponding loop level, we can interrupt the corresponding loop. The functions of break and break label (goto) can be realized. One such application requirement is that interrupts can be moved externally, which exactly meets the requirements of Enumerable.
Back to Enumerable, since the essence of each (each = function(iterator, context){}) method is a loop, its first parameter iterator does not contain a loop, so the break statement is called directly A syntax error will be reported, so the second method above is used in the Prototype source code.
Enumerable.each = function(iterator, context) {
var index = 0;
try{
this._each(function(value){
iterator.call(context, value, index );
});
}catch( e){
if(e != $break){
throw e;
}
}
return this;
};
Once If a $break is thrown during iterator execution, the loop will be interrupted. If it is not $break, then the corresponding error will be thrown and the program will be more stable. There are no special requirements for the definition of $break here. You can change it according to your own preferences, but it doesn't mean much.
Some methods in Enumerable have been implemented in some modern browsers (see the array of chrome native methods). Here is a comparison chart:
When implementing these methods, you can borrow native methods to improve efficiency. However, the source code does not borrow the native part, probably because Enumerable needs to be mixed into other objects in addition to the Array part.
Looking at the above diagram, we can clearly see the importance of each and map. The essence of map is still each, but each processes each item of the collection in sequence, and map is based on each. Also returns the processed results. Within Enumerable, map is an alias of the collect method, and the other alias is select, and the name collect is used internally.
Detection: all | any | include
These three methods do not involve processing the original collection, and the return values are all of boolean type.
all : If all the elements in the Enumerable are equivalent to true, return true, otherwise return false
function all(iterator, context) {
var result = true;
this.each(function(value, index) {
result = result && !!iterator.call(context, value, index);
});
return result;
}
For the all method, the two parameters inside are not necessary, so a function is provided internally to replace the iterator without actual parameters and directly return the original value. The name is Prototype.K. Prototype.K is defined at the beginning of the library and is a function that returns parameter values. Prototype.K = function(x){return x;}. In addition, in the all method, as long as the processing result of one item is false, the entire process can be abandoned (break), so the method of interrupting the loop at the beginning of this article is used. The final form is:
Prototype.K = function() {};
Enumerable.all = function(iterator, context) {
iterator = iterator || Prototype.K;
var result = true;
this.each(function(value, index) {
result = result && !!iterator.call(context, value, index);
if (!result) throw $break;
});
return result;
}
The final result returned is a boolean type. If it deviates from all, let’s change the result:
function collect(iterator, context) {
iterator = iterator || Prototype.K;
var results = [];
this.each( function(value, index) {
results.push(iterator.call(context, value, index));
});
return results;
}
At this time, results are an array. We do not interrupt the processing, save all the results and return them. Well, this is the collect method, or map method.
any: If one or more elements in the Enumerable are equivalent to true, then return true, otherwise return false. The principle is similar to all. all means to stop working when false is found, and any means to find false. If true, call it a day.
function any(iterator, context) {
iterator = iterator || Prototype.K;
var result = false;
this.each(function(value, index) {
if (result = !!iterator.call(context, value, index))
throw $break;
});
return result;
}
include: Determine whether the specified object exists in the Enumerable, and compare based on the == operator One step of optimization for this method is to call the indexOf method. For arrays, if indexOf returns -1, it means that the corresponding element does not exist. If the collection does not have an indexOf method, it can only be searched and compared. There is no algorithm for the search and sum here, it is just traversal one by one. It is easy to rewrite it, but it is not commonly used, so it is estimated that no effort has been spent on optimizing this. So if the result is true, the efficiency is higher than when the result is false, it depends on luck.
function include(object) {
if (Object .isFunction(this.indexOf))//This judgment function should be very familiar
if (this.indexOf(object) != -1) return true;//If there is indexOf, just call
var directly found = false;
this.each(function(value) {//Efficiency issue here
if (value == object) {
found = true;
throw $break;
}
});
return found;
}
The following is a set of methods to filter data: Return a single element: max | min | detect returns an array : grep | findAll | reject | partition where max and min are not limited to comparison of numbers, comparison of characters is also possible. max(iterator, context) can still have two parameters. You can use iterator to process it first and then compare the values. The advantage of this is that it does not have to be limited to specific data types. For example, an object array takes the maximum value according to certain rules:
console.dir([{value : 3},{value : 1 },{value : 2}].max(function(item){
return item.value;
}));//3
So the implementation of the source code can be imagined , when compared directly, the implementation can be as follows:
function max() {
var result;
this.each(function(value) {
if (result == null || value >= result) //result==null is the first comparison
result = value;
});
return result;
}
After expansion, value will further become value = (return value after iterator processing):
function max(iterator, context) {
iterator = iterator || Prototype.K;
var result;
this.each(function(value, index) {
value = iterator.call(context, value, index);
if (result == null || value >= result)
result = value;
});
return result;
}
min has the same principle. The principles of detect and any are similar. any returns true when it finds a true, and detect returns the value that satisfies the true condition when it finds a true. The source code will not be posted. grep looks familiar. It is a unix/linux tool, and its function is also very familiar - it returns all elements that match the specified regular expression. However, unix/linux can only handle strings. The scope is expanded here, but the basic form remains unchanged. If each item in the collection is a string, then the implementation is as follows:
Enumerable.grep = function(filter) {
if(typeof filter == 'string'){
filter = new RegExp(filter);
}
var results = [];
this.each(function(value,index){
if(value.match(filter)){
results.push(value);
}
})
return results;
};
However, there is a collection to be processed that may not all be strings. In order to achieve a wider range of applications, the first thing to consider is the calling form. Looking at the above implementation, pay attention to this sentence:
if(value.match(filter))
where value is a string, and match is a method of String. Now we need to expand the supported types, or give each value Add the match method, or convert the form. Obviously the first type of noise was too loud, so the author changed his mind:
if (filter.match(value))
In this way, no matter what the value is, as long as the filter has a corresponding match method, the above is The RegExp object does not have a match method, so in the source code, the author extended the RegExp object:
RegExp.prototype.match = RegExp.prototype.test;
Note that the above match is essentially different from the String match. In this way, if value is an object, our filter only needs to provide the corresponding match method to detect the object. So there is:
function grep(filter, iterator, context ) {
iterator = iterator || Prototype.K;
var results = [];
if (Object.isString(filter))
filter = new RegExp(RegExp.escape( filter));
this.each(function(value, index) {
if (filter.match(value))//The native filter has no match method.
results.push( iterator.call(context, value, index));
});
return results;
}
For matching results, you can process them and then return them. This is The role of the iterator parameter. Different from the max method, grep uses an iterator to process the results when performing the main operation. Max uses an iterator to process the source data before performing the main operation. Because the filter in grep replaces the iterator in max. As for findAll, it is an enhanced version of grep. After reading grep, findAll is very simple. Reject is the twin version of findAll, which has exactly the opposite effect. partition is findAll reject, combined parent-child version. Please indicate when reprinting from Xiaoxi Shanzi [http://www.cnblogs.com/xesam/]