打造属于自己的underscore系列(四)- 迭代器(下)

705 阅读4分钟

接上一节迭代器的话题,在打造underscore系列的第三篇中,我们引入了迭代器的感念,并实现了reduce, map, times等常用的迭代器方法,由于篇幅过长,我们将其他迭代器的实现放在了这一小节中。

3.5 _.each - _.each(list, iteratee, [context])

_.each 和 _.foreach 方法本质上是定义的同一个方法,在打造属于自己的underscore系列 ( 一 )的框架原理中,我们对 _.each的方法做了实现,其中call改变this指向的代码我们是这样实现的。

if (_.isArray(target)) {
    var length = target.length;
    for (; i < length; i++) {
        callback.call(target, target[i], i);
    }
} else {
    for (key in target) {
        callback.call(target, key, target[key]);
    }
}

而在系列三小节内容中,我们优化了this指向这部分代码,具体封装为optimizeCb 函数,详见打造属于自己的underscore系列 ( 三 ),这里不做重复赘述。经过迭代器优化后,each的实现如下, 其中迭代器参数类型为3,即 func.call(context, value, index)

// 遍历 数组  对象
    _.each = _.forEach =  function (target, callback, context) {
        iteratee = optimizeCb(callback, context, 3) // 迭代器优化
        var key, i = 0;
        if (_.isArray(target)) {
            var length = target.length;
            for (; i < length; i++) {
                iteratee(target[i], i);
            }
        } else {
            for (key in target) {
                iteratee(key, itarget[key])
            }
        }
    }
3.6 _.some() - _.some(list, [predicate], [context])

与原生js中some的方法相同,some的迭代器会遍历元素,同时进行真值检测,一旦有元素通过真值检测,则返回true。因此,我们首先需要了解什么是真值检测? 简单的理解,真值检测就是条件判断,即将检测的值转换Boolean类型,判断转换后的值是否为true。而在条件判断中,我们做了一个总结,除了 undefined, null, false, NaN, '', 0, -0,其他值都会转换为true, 包括空对象。eg

if(null) {console.log('null')}
if(0) {console.log(2)}
if('222'){ console.log('string')}
if(false) { console.log(false)}
if(NaN) { console.log('nan')}
if(undefined) { console.log('undefined')}
if('') { console.log('空')}
if(-0) { console.log('-0')}
if({}) { console.log('object')}

// 'string', 'object'

同样遍历的list可以为对象或者数组,因此类似的_some的实现可以类比之前的例子。

_.some = _.any = function(list, iteratee, context) {
    var keys = !isArrayLike(list) && _.keys(list);
    var lengths = (keys || list).length;
    for(var index = 0; index < length; index++ ) {
        var currentKey = keys? keys[index] : index;
        if(currentKey) return true
    }
    return false
}

some方法和map方法在使用方式上的相同点在于,函数的第二个参数iteratee不仅仅局限于函数,还可以不传值,或者传递对象等,因此同样的处理方式我们可以用前一节总结的回调函数进行优化。

// 系列的第三节总结的cb方法
var cb = function (iteratee, context, args) {
    if (iteratee == null) return _.identity;
    if (_.isFunction(iteratee)) return optimizeCb(iteratee, context, args);  //optimizeCb优化迭代器
    if (_.isObject(iteratee) && !_.isArray(value)) return _.matcher(value);
    //其他
}
_.some = _.any = function(list, iteratee, context) {
    predicate = cb(iteratee, context, 3)
    var keys = !isArrayLike(list) && _.keys(list);
    var lengths = (keys || list).length;
    for(var index = 0; index < lengths; index++ ) {
        var currentKey = keys? keys[index] : index;
        if(predicate(list[currentKey], currentKey, list)) return true // 只要某一项的真值检测通过,则停止遍历,返回true,所有都不通过时,结果才为false
    }
    return false
}
3.7 _.every() - _.every(list, [predicate], [context])

和some方法相反,every方法会遍历list并进行真值检测,只有当所有检测值返回true时,结果才会返回true。有了some实现为基础,every方法的实现显得格外简单

_.every = _.all = function(list, iteratee, context) {
    predicate = cb(iteratee, context, 3)
    var keys = !isArrayLike(list) && _.keys(list);
    var lengths = (keys || list).length;
    for(var index = 0; index < lengths; index++ ) {
        var currentKey = keys? keys[index] : index;
        if(!predicate(list[currentKey], currentKey, list)) return false;// 当某一项真值检测不通过时,则停止遍历,返回false,只有所有值都通过时,结果才为true
    }
    return true
}
3.8 _.filter() - _.filter(list, predicate, [context])

filter方法同样会对list进行遍历,并对每个遍历元素进行真值判断,和every,some不同的是,filter会将所有通过真值检测的元素组成一个新的数组返回。因此,只要简单的处理真值判断后的语句即可完成filter的功能

_.filter = _.select = function(list, iteratee, context) {
    predicate = cb(iteratee, context, 3)
    var keys = !isArrayLike(list) && _.keys(list);
    var lengths = (keys || list).length;
    var results = [];
    for(var index = 0; index < lengths; index++ ) {
        var currentKey = keys? keys[index] : index;
        if(predicate(list[currentKey], currentKey, list)) results.push(list[currentKey])
    }
    return results
}

至此,underscore中关于迭代器相关方法的应用场景和实现思路基本介绍完毕。我们知道,迭代器在日常代码逻辑编写中必不可少,且使用频率较高。熟练掌握迭代器的应用场景并深入理解其中实现思路有利于现实复杂的业务功能的实现。