返回

在迭代器系列学习Underscore.js 1.7.0 框架使用

前端

正文

在打造属于自己的underscore系列 ( 一 )的框架原理中,我们对 _.each的方法做了实现,其中call改变this指向的代码我们是这样实现的。 而在系列三小节内容中,我们优化了this指向这部分代码。

    var boundEach = collection.bind(each);

这里的bind方法与underscore的实现并不相同,在系列一我们提到过使用原生的bind方法来解决this指向问题会比underscore中的实现有更好的性能。这里我们之所以会这样做,其实是出于兼容考虑。

if (!Function.prototype.bind) {
        Function.prototype.bind = function (context) {
            var fn = this;
            return function () {
                return fn.apply(context, arguments);
            };
        };
    }

对于无法使用bind方法的浏览器,需要一个替代实现来解决这个问题,而underscore提供了这一套替代方案。
让我们先用一个简单的方法来深入underscore,当我们实现一个 _.each() 方法时,如果我们仅仅使用迭代来实现如下:

_.each = function (obj, iterator) {
    if (obj == null) return;
    if (obj.length === +obj.length) {
        for (var i = 0, length = obj.length; i < length; i++) {
            iterator(obj[i], i, obj);
        }
    } else {
        for (var key in obj) {
            if (hasOwnProperty.call(obj, key)) {
                iterator(obj[key], key, obj);
            }
        }
    }
};

这里我们并没有关心是否需要对 this 指向做任何处理,这可能带来不必要的问题。
那么我们这里就来看看underscore中是怎么实现的:

_.each = _.forEach = function(obj, iterator, context) {
    var breaker = {};
    if (obj == null) return;
    if (nativeForEach && obj.forEach === nativeForEach) {
        obj.forEach(iterator, context);
    } else if (obj.length === +obj.length) {
        for (var i = 0, length = obj.length; i < length; i++) {
            if (iterator.call(context, obj[i], i, obj) === breaker) return;
        }
    } else {
        var keys = _.keys(obj);
        for (var i = 0, length = keys.length; i < length; i++) {
            if (iterator.call(context, obj[keys[i]], keys[i], obj) === breaker) return;
        }
    }
};

相比前面我们自己写的代码,underscore添加了两个参数,分别是 contextbreaker , context 主要用于指定当前执行环境,而 breaker 则用于中断迭代。我们依然没有在迭代内部对 this 进行任何处理,这可能是因为 underscore 的设计理念就是如此,为了简单,避免了使用 call 或者 bind 来绑定 this 指向,这样在使用的时候就必须自行考虑 this 指向问题,这会带来潜在的错误,例如:

_.each([1, 2, 3], function() { console.log(this); });

当我们执行上面的代码时,会发现 this 指向的是 window 对象,这并不是我们想要的结果。那么我们就自己写一个版本,用 call 或者 bind 来实现 this 指向的绑定,代码如下:

_.each = function(obj, iterator, context) {
    var breaker = {};
    if (obj == null) return;
    if (nativeForEach && obj.forEach === nativeForEach) {
        obj.forEach(iterator.bind(context), context);
    } else if (obj.length === +obj.length) {
        for (var i = 0, length = obj.length; i < length; i++) {
            if (iterator.call(context, obj[i], i, obj) === breaker) return;
        }
    } else {
        var keys = _.keys(obj);
        for (var i = 0, length = keys.length; i < length; i++) {
            if (iterator.call(context, obj[keys[i]], keys[i], obj) === breaker) return;
        }
    }
};

这样就解决了 this 指向问题,当然在使用的时候就需要注意传入 context 了。

在 Underscore.js 中,迭代器除了 _.each() 和 .foreach() 方法外,还有许多其他的方法,包括 .map()、.reduce()、.find() 和 _.filter() 等。这些方法都非常有用,可以帮助我们轻松地处理各种各样的数据集合。

总结

在本文中,我们深入学习了 Underscore.js 中的迭代器,重点关注了 _.each() 和 _.foreach() 方法。我们了解了它们的实现原理,并探讨了如何在 JavaScript 中有效地使用它们。在下一篇文章中,我们将继续学习 Underscore.js 中的其他迭代器方法。