jQuery源码分析(七): each迭代器

前端语法/样式/布局 2016-11-03

起步

许多框架都有自己的迭代器,迭代器是一个重要的设计,它提供一种方式去顺序处理聚合对象中的各个元素,而且不用对外暴露内部元素。这也是设计模式中的迭代器模式(Iterator)。

each()

each()就是jq提供的这样一个实现迭代器的工具类函数,你可以遍历对象属性,数组进行处理:

$("li").each(function(index) {});

$.each([1, 2], function(index, value) {});

jQuery的each方法用在两种情况$.each()$(selector).each(),别看两个方法,其实是同一个:

jQuery.fn = jQuery.prototype = {
    //code....
    each: function( callback, args ) {
        return jQuery.each( this, callback, args );
    },
}

可见内部是直接调用的静态方法,参数this是jq的实例,jQuery的静态方法:

each: function( obj, callback, args ) {
    var value,
        i = 0,
        length = obj.length,
        isArray = isArraylike( obj );

    if ( args ) {
        if ( isArray ) {
            for ( ; i < length; i++ ) {
                value = callback.apply( obj[ i ], args );

                if ( value === false ) {
                    break;
                }
            }
        } else {
            for ( i in obj ) {
                value = callback.apply( obj[ i ], args );

                if ( value === false ) {
                    break;
                }
            }
        }

    // A special, fast, case for the most common use of each
    } else {
        if ( isArray ) {
            for ( ; i < length; i++ ) {
                value = callback.call( obj[ i ], i, obj[ i ] );

                if ( value === false ) {
                    break;
                }
            }
        } else {
            for ( i in obj ) {
                value = callback.call( obj[ i ], i, obj[ i ] );

                if ( value === false ) {
                    break;
                }
            }
        }
    }

    return obj;
},

对传进来的第一个参数进行判断,isArraylike( obj )判断是否满足数组操作,前面的文章有提到怎么把对象当做数组操作。判断的结果如果满足就用for循环,不满足说明是对象,用for in循环。参数agrs是附加参数。通过返回值来判断是否跳出循环,这样设计是有目的的:回调函数中可以控制循环的结束,return false;就跳出循环,可以以此来实现更高级的功能。回调函数用callback.call()而不是callback()呢,这要涉及到call()的特性了。

Function.prototype.call()

call() 方法在使用一个指定的this值和若干个指定的参数值的前提下调用某个函数或方法

注意:该方法的作用和 apply() 方法类似,只有一个区别,就是call()方法接受的是若干个参数的列表,而apply()方法接受的是一个包含多个参数的数组

语法:fun.call(thisArg[, arg1[, arg2[, ...]]])

什么意思呢,我的理解就是指针替换:

function fun() {
    this.xx = "fff";
    console.log(this);
}

fun();
var t = new Object();
fun.call(t);

20161103142937.png

可以看到如果直接调用fun(),this是window本身(如果fun的定义的上下文是全局)。而通过call()的调用,this则是指t了。call 和 apply 都是为了改变某个函数运行时的 context(上下文)而存在的,换句话说,就是为了改变函数体内部 this 的指向。通过这个特性,可以构造一个继承:

function BaseObject() {
  this.name = "";
  this.price = "price";
}

function Food() {
  BaseObject.call(this); 
  this.category = 'food';
}

var f = new Food()

意思差不多就是把BaseObject()的代码“贴”到这边。这个特性可以随意调用别人的函数,还不用修改原型prototype,想用谁的就用谁的,func.call(obj);

内部调用

each()方法做得很通用,除了是对外的一个接口,在jq内部会有用到:

dequeue: function( type ) {
        return this.each(function() {
            jQuery.dequeue( this, type );
        });
    },
//code...

jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
    class2type[ "[object " + name + "]" ] = name.toLowerCase();
});

jQuery.each({
    mouseenter: "mouseover",
    mouseleave: "mouseout",
    pointerenter: "pointerover",
    pointerleave: "pointerout"
}, function( orig, fix ) {
    //处理的代码
});

总结

迭代器可以访问一个聚合对象或数组而无需暴露内部结构,遍历控制权交给用户以完成更高级的功能,通过call()实现上下文this指向,并且支持参数传递。针对相同的功能,节约了大量的代码空间。


本文由 hongweipeng 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

赏个馒头吧