Underscore源码解析(二)


本文同步自我的博客http://jr3.me
最近十几天都在忙毕业论文的事,所以上一次为大家介绍完underscore这个框架的结构(或者说是这个框架的设计思路)之后就一直没动静了,今天我又满血复活了,让我们继续来探索underscore的源码奥秘吧。
没看过上一篇文章的朋友可以戳这里:underscore源码解析(一)
今天的内容是underscore里面封装的一些函数,我将逐个介绍,咱们直接入正题吧

each / _.each / _.forEach

var each = _.each = _.forEach = function(obj, iterator, context) {
    // 不处理空值
    if(obj == null)
        return;
    if(nativeForEach && obj.forEach === nativeForEach) {
        // 如果宿主环境支持, 则优先调用JavaScript 1.6提供的forEach方法
        obj.forEach(iterator, context);
    } else if(obj.length === +obj.length) {
        // 对[数组]中每一个元素执行处理器方法
        for(var i = 0, l = obj.length; i < l; i++) {
            if( i in obj && iterator.call(context, obj[i], i, obj) === breaker)
                return;
        }
    } else {
        // 对{对象}中每一个元素执行处理器方法
        for(var key in obj) {
            if(_.has(obj, key)) {
                if(iterator.call(context, obj[key], key, obj) === breaker)
                    return;
            }
        }
    }
};

这个函数的实现思想其实很简单,如果宿主环境(一般为浏览器或者node.js)支持原生的forEach方法,就调用原生的,否则就遍历该数组或者对象,依次调用处理器方法
值得一提的是在判断是否是数组的时候,这里的代码为

obj.length === +obj.length

这其实是一种鸭式辨型的判定方法,具体可以参见我在SF上提过的一个问题:点我

_.map / _.collect

_.map = _.collect = function(obj, iterator, context) {
    // 用于存放返回值的数组
    var results = [];
    if(obj == null)
        return results;
    // 优先调用宿主环境提供的map方法
    if(nativeMap && obj.map === nativeMap)
        return obj.map(iterator, context);
    // 迭代处理集合中的元素
    each(obj, function(value, index, list) {
        // 将每次迭代处理的返回值存储到results数组
        results[results.length] = iterator.call(context, value, index, list);
    });
    // 返回处理结果
    if(obj.length === +obj.length)
        results.length = obj.length;
    return results;
};

map/collect函数与each的区别在于map/collect会存储每次迭代的返回值, 并作为一个新的数组返回

_.reduce / _.foldl / _.inject

_.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
    // 通过参数数量检查是否存在初始值
    var initial = arguments.length > 2;
    if(obj == null)
        obj = [];
    // 优先调用宿主环境提供的reduce方法
    if(nativeReduce && obj.reduce === nativeReduce && false) {
        if(context)
            iterator = _.bind(iterator, context);
        return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
    }
    // 迭代处理集合中的元素
    each(obj, function(value, index, list) {
        if(!initial) {
            // 如果没有初始值, 则将第一个元素作为初始值; 如果被处理的是对象集合, 则默认值为第一个属性的值
            memo = value;
            initial = true;
        } else {
            // 记录处理结果, 并将结果传递给下一次迭代
            memo = iterator.call(context, memo, value, index, list);
        }
    });
    if(!initial)
        throw new TypeError('Reduce of empty array with no initial value');
    return memo;
};

这个函数的作用是将集合中每个元素放入迭代处理器, 并将本次迭代的返回值作为memo传递到下一次迭代, 一般用于累计结果或连接数据

_.reduceRight / _.foldr

_.reduceRight = _.foldr = function(obj, iterator, memo, context) {
    var initial = arguments.length > 2;
    if(obj == null)
        obj = [];
    // 优先调用宿主环境提供的reduceRight方法
    if(nativeReduceRight && obj.reduceRight === nativeReduceRight) {
        if(context)
            iterator = _.bind(iterator, context);
        return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
    }
    // 逆转集合中的元素顺序
    var reversed = _.toArray(obj).reverse();
    if(context && !initial)
        iterator = _.bind(iterator, context);
    // 通过reduce方法处理数据
    return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator);
};

这个函数与reduce相似,不过它是逆向迭代集合中的元素

_.find / _.detect

_.find = _.detect = function(obj, iterator, context) {
    // result存放第一个能够通过验证的元素
    var result;
    // 通过any方法遍历数据, 并记录通过验证的元素
    any(obj, function(value, index, list) {
        // 如果处理器返回的结果被转换为Boolean类型后值为true, 则记录当前值并返回当前元素
        if(iterator.call(context, value, index, list)) {
            result = value;
            return true;
        }
    });
    return result;
};

这个方法的作用是遍历集合中的元素, 返回能够通过处理器验证的第一个元素

_.filter / _.select

_.filter = _.select = function(obj, iterator, context) {
    // 用于存储通过验证的元素数组
    var results = [];
    if(obj == null)
        return results;
    // 优先调用宿主环境提供的filter方法
    if(nativeFilter && obj.filter === nativeFilter)
        return obj.filter(iterator, context);
    // 迭代集合中的元素, 并将通过处理器验证的元素放到数组中并返回
    each(obj, function(value, index, list) {
        if(iterator.call(context, value, index, list))
            results[results.length] = value;
    });
    return results;
};

这个方法与find作用类似, 但它会记录下集合中所有通过验证的元素

_.reject

_.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;
};

这个方法的代码里面我没有加注释,因为整个代码与filter/select方法几乎一样,不同点在于向results数组里添加元素的时候判断条件是相反的,也就是说这个方法的作用是返回没有通过处理器验证的元素列表

_.every / _.all

_.every = _.all = function(obj, iterator, context) {
    var result = true;
    if(obj == null)
        return result;
    // 优先调用宿主环境提供的every方法
    if(nativeEvery && obj.every === nativeEvery)
        return obj.every(iterator, context);
    // 迭代集合中的元素
    each(obj, function(value, index, list) {
        // 这里我不太理解,为什么药写成 result = (result && iterator.call(context, value, index, list)) 而不是 result = iterator.call(context, value, index, list)
        if(!( result = result && iterator.call(context, value, index, list)))
            return breaker;
    });
    return !!result;
};

这个方法的作用是如果集合中所有元素均能通过处理器验证, 则返回true

any / _.some / _.any

var any = _.some = _.any = function(obj, iterator, context) {
    // 如果没有指定处理器参数, 则使用默认的处理器函数,该函数会返回参数本身
    iterator || ( iterator = _.identity);
    var result = false;
    if(obj == null)
        return result;
    // 优先调用宿主环境提供的some方法
    if(nativeSome && obj.some === nativeSome)
        return obj.some(iterator, context);
    // 迭代集合中的元素
    each(obj, function(value, index, list) {
        if(result || ( result = iterator.call(context, value, index, list)))
            return breaker;
    });
    return !!result;
};

该函数的作用是检查集合中是否有任何一个元素在被转换成Boolean类型时是否为true

_.include / _.contains

_.include = _.contains = function(obj, target) {
    var found = false;
    if(obj == null)
        return found;
    // 优先调用宿主环境提供的Array.prototype.indexOf方法
    if(nativeIndexOf && obj.indexOf === nativeIndexOf)
        return obj.indexOf(target) != -1;
    // 通过any方法迭代集合中的元素, 验证元素的值和类型与目标是否完全匹配
    found = any(obj, function(value) {
        return value === target;
    });
    return found;
};

这个函数用于检查集合中是否有值与目标参数完全匹配,包括数据类型

小结

今天先介绍以上10个函数的实现细节,之后还会继续带来其他函数的介绍,欢迎大家提出指正和建议,thx for reading, hope u enjoy


2 responses on “Underscore源码解析(二)

发表评论

电子邮件地址不会被公开。 必填项已用*标注

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>