本文同步自我得博客:http://www.joeray61.com
前两天在微博上看到SF的微博推荐了我的前两篇文章,有点意外和惊喜。作为一个菜鸟,真的是倍受鼓舞,我写博客的动力也更充足了。
没看过前两篇博客的朋友可以戳这里:Underscore源码解析(一)、Underscore源码解析(二)
上一篇文章介绍了underscore的10个函数的具体实现细节,今天将继续介绍其他的函数。
_.invoke
_.invoke = function(obj, method) {
// 调用同名方法时传递的参数(从第3个参数开始)
var args = slice.call(arguments, 2);
// 依次调用每个元素的方法, 并将结果放入数组中返回
return _.map(obj, function(value) {
return (_.isFunction(method) ? method || value : value[method]).apply(value, args);
});
};
这个函数依次调用集合中所有元素的同名方法,从第3个参数开始的所有参数将被传入到元素的调用方法中,最后返回一个数组,该数组存储了所有方法的处理结果
_.pluck
_.pluck = function(obj, key) {
// 如果某一个对象中不存在该属性, 则返回undefined
return _.map(obj, function(value) {
return value[key];
});
};
这个函数遍历了一个由对象列表组成的集合,并返回每个对象中的指定属性的值列表
_.max
_.max = function(obj, iterator, context) {
// 如果集合是一个数组, 且没有使用处理器, 则使用Math.max获取最大值
// 一般会是在一个数组存储了一系列Number类型的数据
if(!iterator && _.isArray(obj) && obj[0] === +obj[0])
return Math.max.apply(Math, obj);
// 对于空值, 直接返回负无穷大
if(!iterator && _.isEmpty(obj))
return -Infinity;
// 一个临时的对象, computed用于在比较过程中存储最大值(临时的)
var result = {
computed : -Infinity
};
// 迭代集合中的元素
each(obj, function(value, index, list) {
// 如果指定了处理器参数, 则比较的数据为处理器返回的值, 否则直接使用each遍历时的默认值
var computed = iterator ? iterator.call(context, value, index, list) : value;
// 如果比较值相比上一个值要大, 则将当前值放入result.value
computed >= result.computed && ( result = {
value : value,
computed : computed
});
});
// 返回最大值
return result.value;
};
顾名思义,这个函数用来返回集合中的最大值, 如果不存在可比较的值, 则返回undefined
_.min
_.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;
};
这个函数没有加注释,因为实现过程与max基本相同,用于返回集合中的最小值
_.shuffle
_.shuffle = function(obj) {
// shuffled变量存储处理过程及最终的结果数据
var shuffled = [], rand;
// 迭代集合中的元素
each(obj, function(value, index, list) {
// 生成一个随机数, 随机数在<0-当前已处理的数量>之间
rand = Math.floor(Math.random() * (index + 1));
// 将已经随机得到的元素放到shuffled数组末尾
shuffled[index] = shuffled[rand];
// 在前面得到的随机数的位置插入最新值
shuffled[rand] = value;
});
// 返回一个数组, 该数组中存储了经过随机混排的集合元素
return shuffled;
};
这个函数是通过随机数, 让数组无须排列,实际上是实现了一个模拟洗牌过程的算法
_.sortBy
_.sortBy = function(obj, val, context) {
// val应该是对象的一个属性, 或一个处理器函数, 如果是一个处理器, 则应该返回需要进行比较的数据
var iterator = _.isFunction(val) ? val : function(obj) {
return obj[val];
};
// 调用顺序: _.pluck(_.map().sort());
// 调用_.map()方法遍历集合, 并将集合中的元素放到value节点, 将元素中需要进行比较的数据放到criteria属性中
// 调用sort()方法将集合中的元素按照criteria属性中的数据进行顺序排序
// 调用pluck获取排序后的对象集合并返回
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');
};
这个函数对集合中元素, 按照特定的字段或值进行排列,相比Array.prototype.sort方法, sortBy方法支持对对象排序
_.groupBy
_.groupBy = function(obj, val) {
var result = {};
// val将被转换为进行分组的处理器函数, 如果val不是一个Function类型的数据, 则将被作为筛选元素时的key值
var iterator = _.isFunction(val) ? val : function(obj) {
return obj[val];
};
// 迭代集合中的元素
each(obj, function(value, index) {
// 将处理器的返回值作为key, 并将相同的key元素放到一个新的数组
var key = iterator(value, index);
(result[key] || (result[key] = [])).push(value);
});
// 返回已分组的数据
return result;
};
这个函数将集合中的元素, 按处理器返回的key分为多个数组
_.sortedIndex
_.sortedIndex = function(array, obj, iterator) {
// 如果没有指定处理器参数, 则使用默认的处理器函数,该函数会返回参数本身
iterator || ( iterator = _.identity);
var low = 0, high = array.length;
// 不断与中间值对比,寻找obj的正确插入点
while(low < high) {
// (low + high) >> 1 相当于 Math.floor((low + high) / 2)
var mid = (low + high) >> 1;
iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid;
}
// 返回obj插入array之后的索引号
return low;
};
这个函数的作用是将obj插入已经排序的array中,返回obj在array中的索引号
_.toArray
_.toArray = function(obj) {
if(!obj)
return [];
if(_.isArray(obj))
return slice.call(obj);
// 将arguments转换为数组
if(_.isArguments(obj))
return slice.call(obj);
if(obj.toArray && _.isFunction(obj.toArray))
return obj.toArray();
// 将对象转换为数组, 数组中包含对象中所有属性的值列表(不包含对象原型链中的属性)
return _.values(obj);
};
这个函数很简单,作用是将一个集合转换一个数组并返回
_.size
_.size = function(obj) {
// 如果集合是一个数组, 则计算数组元素数量
// 如果集合是一个对象, 则计算对象中的属性数量(不包含对象原型链中的属性)
return _.isArray(obj) ? obj.length : _.keys(obj).length;
};
这个函数用于计算集合中元素的数量,isArray和keys函数后面会介绍到
_.first / _.head / _.take
_.first = _.head = _.take = function(array, n, guard) {
// 如果没有指定参数n, 则返回第一个元素
// 如果指定了n, 则返回一个新的数组, 包含顺序指定数量n个元素
// guard参数用于确定只返回第一个元素, 当guard为true时, 指定数量n无效
return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
};
这个函数用于返回一个数组的第一个或順序指定的n个元素
_.initial
_.initial = function(array, n, guard) {
// 如果没有传递参数n, 则默认返回除最后一个元素外的其它元素
// 如果传递参数n, 则返回从最后一个元素开始向前的n个元素外的其它元素
// guard用于确定只返回一个元素, 当guard为true时, 指定数量n无效
return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));
};
这个函数返回一个新数组, 包含除最后一个元素外的其它元素, 或排除从最后一个元素开始向前指定n个元素
_.last
_.last = function(array, n, guard) {
if((n != null) && !guard) {
// 计算并指定获取的元素位置n, 直到数组末尾, 作为一个新的数组返回
return slice.call(array, Math.max(array.length - n, 0));
} else {
// 如果没有指定数量, 或guard为true时, 只返回最后一个元素
return array[array.length - 1];
}
};
这个函数与first相反,返回数组的最后一个或倒序指定的n个元素
_.rest / _.tail
_.rest = _.tail = function(array, index, guard) {
// 计算slice的第二个位置参数, 直到数组末尾
// 如果没有指定index, 或guard值为true, 则返回除第一个元素外的其它元素
// (index == null)值为true时, 作为参数传递给slice函数将被自动转换为1
return slice.call(array, (index == null) || guard ? 1 : index);
};
这个函数与initial相反,用于获取除了第一个或指定前n个元素外的其它元素
_.campact
_.compact = function(array) {
return _.filter(array, function(value) {
return !!value;
});
};
这个函数借助filter函数,返回数组中所有值能被转换为true的元素, 返回一个新的数组,不能被转换的值包括 false, 0, ”, null, undefined, NaN, 这些值将被转换为false
_.flatten
_.flatten = function(array, shallow) {
// 迭代数组中的每一个元素, 并将返回值作为demo传递给下一次迭代
return _.reduce(array, function(memo, value) {
// 如果元素依然是一个数组, 进行以下判断:
// - 如果不进行深层合并, 则使用Array.prototype.concat将当前数组和之前的数据进行连接
// - 如果支持深层合并, 则迭代调用flatten方法, 直到底层元素不再是数组类型
if(_.isArray(value))
return memo.concat( shallow ? value : _.flatten(value));
// 数据(value)已经处于底层, 不再是数组类型, 则将数据合并到memo中并返回
memo[memo.length] = value;
return memo;
}, []);
};
这个函数用于将一个多维数组合成为一维数组, 支持深层合并,其中第二个参数shallow用于控制合并深度, 当shallow为true时, 只合并第一层, 默认进行深层合并
_.without
_.without = function(array) {
return _.difference(array, slice.call(arguments, 1));
};
这个函数用于筛选并返回当前数组中与指定数据不相等的差异数据,具体可以参看我后续对difference函数的介绍
_.uniq/_.unique
_.uniq = _.unique = function(array, isSorted, iterator) {
// 如果使用了iterator处理器, 则先将当前数组中的数据会先经过按迭代器处理, 并返回一个处理后的新数组
// 新数组用于作为比较的基准
var initial = iterator ? _.map(array, iterator) : array;
// 用于记录处理结果的临时数组
var results = [];
// 如果数组中只有2个值, 则不需要使用include方法进行比较, 将isSorted设置为true能提高运行效率
if(array.length < 3)
isSorted = true;
// 使用reduce方法迭代并累加处理结果
// initial变量是需要进行比较的基准数据, 它可能是原始数组, 也可能是处理器的结果集合(如果设置过iterator)
_.reduce(initial, function(memo, value, index) {
// 如果isSorted参数为true, 则直接使用===比较记录中的最后一个数据
// 如果isSorted参数为false, 则使用include方法与集合中的每一个数据进行对比
if( isSorted ? _.last(memo) !== value || !memo.length : !_.include(memo, value)) {
// memo记录了已经比较过的无重复数据
// 根据iterator参数的状态, memo中记录的数据可能是原始数据, 也可能是处理器处理后的数据
memo.push(value);
// 处理结果数组中保存的始终为原始数组中的数据
results.push(array[index]);
}
return memo;
}, []);
// 返回处理结果, 它只包含数组中无重复的数据
return results;
};
这个函数用于对数组中的数据进行去重(使用===进行比较),当isSorted参数不为false时, 将依次对数组中的元素调用include方法, 检查相同元素是否已经被添加到返回值(数组)中,如果调用之前确保数组中数据按顺序排列, 则可以将isSorted设为true, 它将通过与最后一个元素进行对比来排除相同值, 使用isSorted效率会高于默认的include方式,uniq方法默认将以数组中的数据进行对比, 如果声明iterator处理器, 则会根据处理器创建一个对比数组, 比较时以该数组中的数据为准, 但最终返回的唯一数据仍然是原始数组
_.union
_.union = function() {
// union对参数中的多个数组进行浅层合并为一个数组对象传递给uniq方法进行处理
return _.uniq(_.flatten(arguments, true));
};
这个函数与uniq作用一致, 不同之处在于union允许在参数中传入多个数组
_.intersection
_.intersection = _.intersect = function(array) {
// rest变量记录需要进行比较的其它数组对象
var rest = slice.call(arguments, 1);
// 使用uniq方法去除当前数组中的重复数据, 避免重复计算
// 对当前数组的数据通过处理器进行过滤, 并返回符合条件(比较相同元素)的数据
return _.filter(_.uniq(array), function(item) {
// 使用every方法验证每一个数组中都包含了需要对比的数据
// 如果所有数组中均包含对比数据, 则全部返回true, 如果任意一个数组没有包含该元素, 则返回false
return _.every(rest, function(other) {
// other参数存储了每一个需要进行对比的数组
// item存储了当前数组中需要进行对比的数据
// 使用indexOf方法搜索数组中是否存在该元素(可参考indexOf方法注释)
return _.indexOf(other, item) >= 0;
});
});
};
这个函数用于获取当前数组与其它一个或多个数组的交集元素,从第二个参数开始为需要进行比较的一个或多个数组
_.difference
_.difference = function(array) {
// 对第2个参数开始的所有参数, 作为一个数组进行合并(仅合并第一层, 而并非深层合并)
// rest变量存储验证数据, 在本方法中用于与原数据对比
var rest = _.flatten(slice.call(arguments, 1), true);
// 对合并后的数组数据进行过滤, 过滤条件是当前数组中不包含参数指定的验证数据的内容
// 将符合过滤条件的数据组合为一个新的数组并返回
return _.filter(array, function(value) {
return !_.include(rest, value);
});
};
这个函数会筛选并返回当前数组中与指定数据不相等的差异数据,一般用于删除数组中指定的数据, 并得到删除后的新数组
小结
今天一共介绍了21个函数的具体实现,我都写累了,大家可能也看累了吧,我觉得写太多也不利于大家消化这些知识,今天就到这儿吧。thx for reading, hope u enjoy