JQuery源码解析-JQuery的工具方法

Posted 8932809

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JQuery源码解析-JQuery的工具方法相关的知识,希望对你有一定的参考价值。

  下面对最后这几个方法进行讲解。

  guid():唯一表示(内部)

  proxy():改变this指向

  access(): 多功能值操作

  now():当前时间

  swap():css交换(内部)

 

guid:

这个属性是对事件进行控制的,例如每次对dom元素进行绑定事件的时候,会通过这个属性进行绑定,这个属性每次自增,产生一个唯一的标示,所以对dom元素进行事件解绑等操作的时候,通过这个属性就可以找到。

源码:

// A global GUID counter for objects
    guid: 1,

proxy方法:

这个方法是用来改变方法的内部指向,例如:

 function show() {
            console.log(this);
        }

        $.proxy(show, document)();  //document

不通过这个方法直接调用show方法,那么this指向的是window,可以看到,通过proxy方法改变后,this指向是document。

当需要向方法里传参时,可以这么写:

   function show(a ,b ) {
            console.log(a,b,this);
        }

        $.proxy(show, document,1)(2);  //1 2 document

可以看到参数传递既可以在proxy方法内部,从第三个参数依次传递,也可以通过括号内部将参数传入,还可以分开传入。这么做的原因是通过这个方法可以进行科里化,对某个参数进行绑定。

另外源码内部还支持另一种方式进行参数传递:

  var obj={
            show: function () {
                console.log(this);
            }
        }
        $.proxy(obj, ‘show‘)(); //Object {}

可以看到通过这种方式,也可改变方法的指向。

下面看一下源码:

// Bind a function to a context, optionally partially applying any
    // arguments.
    proxy: function( fn, context ) {
        var tmp, args, proxy;

        if ( typeof context === "string" ) {
            tmp = fn[ context ];
            context = fn;
            fn = tmp;
        }

        // Quick check to determine if target is callable, in the spec
        // this throws a TypeError, but we will just return undefined.
        if ( !jQuery.isFunction( fn ) ) {
            return undefined;
        }

        // Simulated bind
        args = core_slice.call( arguments, 2 );
        proxy = function() {
            return fn.apply( context || this, args.concat( core_slice.call( arguments ) ) );
        };

        // Set the guid of unique handler to the same of original handler, so it can be removed
        proxy.guid = fn.guid = fn.guid || jQuery.guid++;

        return proxy;
    },

首先是对参数为string类型的情况进行处理,也就是上面说的另一种方式调用。可以看到通过:

  if ( typeof context === "string" ) {
            tmp = fn[ context ];
            context = fn;
            fn = tmp;
        }

这段代码的转换后,实际上参数还是被转换成了下面这种调用方式。

 $.proxy(obj.show, obj)();

下面对参数进行判断,如果fn不是方法,那么直接返回undefined,也没有往下执行的必要了。

// Quick check to determine if target is callable, in the spec
        // this throws a TypeError, but we will just return undefined.
        if ( !jQuery.isFunction( fn ) ) {
            return undefined;
        }

下面是对参数进行处理,并改变方法的作用域:

// Simulated bind
        args = core_slice.call( arguments, 2 );
        proxy = function() {
            return fn.apply( context || this, args.concat( core_slice.call( arguments ) ) );
        };

先是获取$.proxy(show, document,1) 这里的参数,可以看到,从第二个参数以后开始截取,因为前两个参数是固定的。

然后在proxy方法中返回传入的参数,并调用apply改变作用域,经过下面这句的处理,也就是可以对分开传参这种方式进行支持了。

args.concat( core_slice.call( arguments ) )

先将$.proxy(show, document,1)这里的参数获取到,然后在调用的时候,$.proxy(show, document,1)(2)在将方法括号内部的参数获取,并进行连接。传入方法就可以了。

// Set the guid of unique handler to the same of original handler, so it can be removed
        proxy.guid = fn.guid = fn.guid || jQuery.guid++;

        return proxy;

可以看到当处理完方法后,把方法的guid属性进行自增。

最后返回proxy,实际上也就是传入的方法指向,所以这个方法不是自动运行的,想要调用,还需要在后面加上一对括号。

access方法:

这个方法是内部使用的获取或赋值操作的一个公共方法,例如jQuery的css方法,attr方法都会调用这个方法,我们在调用这些方法时,当传入一个参数的时候,是取值操作,例如:

$(‘#div1‘).css(‘width‘)

当传入两个参数的时候,是赋值操作,例如:

$(‘#div1‘).css(‘width‘,‘200px‘)

当传入一个json对象的时候,是设置多个属性,例如:

$(‘#div1‘).css({ ‘width‘: ‘200px‘, ‘height‘: ‘200px‘ })

那么这些都怎么做到的呢,在一个方法里可以实现取值赋值的,其实在各自的方法中,例如css(),attr()这些方法中,都是对access方法进行调用,并通过回调方法,来实现各自特有的逻辑,看一下源码:

// Multifunctional method to get and set values of a collection
    // The value/s can optionally be executed if it‘s a function
    access: function( elems, fn, key, value, chainable, emptyGet, raw ) {
        var i = 0,
            length = elems.length,
            bulk = key == null;

        // Sets many values
        if ( jQuery.type( key ) === "object" ) {
            chainable = true;
            for ( i in key ) {
                jQuery.access( elems, fn, i, key[i], true, emptyGet, raw );
            }

        // Sets one value
        } else if ( value !== undefined ) {
            chainable = true;

            if ( !jQuery.isFunction( value ) ) {
                raw = true;
            }

            if ( bulk ) {
                // Bulk operations run against the entire set
                if ( raw ) {
                    fn.call( elems, value );
                    fn = null;

                // ...except when executing function values
                } else {
                    bulk = fn;
                    fn = function( elem, key, value ) {
                        return bulk.call( jQuery( elem ), value );
                    };
                }
            }

            if ( fn ) {
                for ( ; i < length; i++ ) {
                    fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) );
                }
            }
        }

        return chainable ?
            elems :

            // Gets
            bulk ?
                fn.call( elems ) :
                length ? fn( elems[0], key ) : emptyGet;
    },

可以看到,这个方法接收很多参数,elems表示节点元素,fn是回调方法,key是属性名,比如 width等,value是对应的值,比如100px,chainable是用来区分取值还是赋值。赋值为true,取值为false,通过css方法的arguments.length > 1,来进行判断。

大于一个就是赋值,否则就是取值:

 return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 );

接下来定义变量:

        var i = 0,
            length = elems.length,
            bulk = key == null;

i:是用来循环的索引,length来保存传入元素的长度,后面判断用,bulk来判断是否传入key这个变量,如果未传,则为true。

接着往下看:

// Sets many values
        if ( jQuery.type( key ) === "object" ) {
            chainable = true;
            for ( i in key ) {
                jQuery.access( elems, fn, i, key[i], true, emptyGet, raw );
            }

从注释就可以看到,这里是对多个属性进行赋值,当传入的参数为object时,也就是json对象时,会执行这里。

先将chainable这个值赋值true,因为只传一个json对象是,那么必然不满足arguments>1这个条件,所以这里将其手动改变一下。

然后遍历这个对象,对json进行拆解之后,对当前方法进行递归。

接下来看一下赋值单个属性的操作:

 else if ( value !== undefined ) {
            chainable = true;

            if ( !jQuery.isFunction( value ) ) {
                raw = true;
            }

            if ( bulk ) {
                // Bulk operations run against the entire set
                if ( raw ) {
                    fn.call( elems, value );
                    fn = null;

                // ...except when executing function values
                } else {
                    bulk = fn;
                    fn = function( elem, key, value ) {
                        return bulk.call( jQuery( elem ), value );
                    };
                }
            }

            if ( fn ) {
                for ( ; i < length; i++ ) {
                    fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) );
                }
            }
        }

首先对value进行判断,当value不为undefined时,也就代表了赋值操作,避免漏传chainable参数的情况。然后对chainable进行手动更改。

接着判断value是否方法,在如果是的话,则表示:

attr( attributeName, function(index, attr) )

可能是这种方式调用的,平时几乎没使用过这种情况。

接下来判断是否有属性值,在对raw进行判断,也就是,如果value不是function,直接回调并传入节点元素和value。

如果value是function的话,则将外层包一层方法(这里没太明白具体原因,平时使用的时候一般不会执行这个判断

接下来判断fn是否为空,然后依次把拆解之后的参数传入回调方法。

再来看最后一部分:

    return chainable ?
            elems :

            // Gets
            bulk ?
                fn.call( elems ) :
                length ? fn( elems[0], key ) : emptyGet;

如果cahinable为true也就是赋值的时候,直接返回这个元素,方便链式调用,否则就是取值操作,在这里又是一个判断,当bulk为true的时候,代表key为null则直接把元素传给回调方法。

如果有key值在判断节点的长度,如果有长度,则第一个元素和key值返回,否则返回一个undefined。

这个方法以后到那些赋值取值的方法时,在仔细说明,现在只看这个方法,可能不是很清晰。

now属性:

    now: Date.now,

这个属性没什么好说的,直接返回当前时间。

 swap方法:

这个方法是用作css交换的。例如:

 <div id="div1" style="width:100px;height:100px;background:red;display:none">1111</div>
        $(function () {
            console.log($(‘#div1‘).css(‘width‘)); //100px
            console.log($(‘#div1‘).get(0) .offsetWidth); //0
        })

从结果可以看出,原生js无法获取display为none的属性值,而jQuery却可以获取,原因就是在内部使用了这个方法。

方法主要将div的样式转换成:

 <div id="div1" style="width:100px;height:100px;background:red;display:block;visibility:hidden;position:absolute;">1111</div>

获取到需要的值之后,在将其css属性还原成css属性。源码:

// A method for quickly swapping in/out CSS properties to get correct calculations.
    // Note: this method belongs to the css module but it‘s needed here for the support module.
    // If support gets modularized, this method should be moved back to the css module.
    swap: function( elem, options, callback, args ) {
        var ret, name,
            old = {};

        // Remember the old values, and insert the new ones
        for ( name in options ) {
            old[ name ] = elem.style[ name ];
            elem.style[ name ] = options[ name ];
        }

        ret = callback.apply( elem, args || [] );

        // Revert the old values
        for ( name in options ) {
            elem.style[ name ] = old[ name ];
        }

        return ret;
    }

代码主要为三部分,第一部分:

 // Remember the old values, and insert the new ones
        for ( name in options ) {
            old[ name ] = elem.style[ name ];
            elem.style[ name ] = options[ name ];
        }

这部分主要将原有的样式存到old对象中,然后将新的样式插入到节点中。

第二部分:

    ret = callback.apply( elem, args || [] );

获取到需要的值。

第三部分:

// Revert the old values
        for ( name in options ) {
            elem.style[ name ] = old[ name ];
        }

将原有的样式还原。

下面在来说明一下isArraylike这个方法,这个方法在前面文章中用到过,是用来判断是否为数组或类数组的方法。

源码:

function isArraylike( obj ) {
    var length = obj.length,
        type = jQuery.type( obj );

    if ( jQuery.isWindow( obj ) ) {
        return false;
    }

    if ( obj.nodeType === 1 && length ) {
        return true;
    }

    return type === "array" || type !== "function" &&
        ( length === 0 ||
        typeof length === "number" && length > 0 && ( length - 1 ) in obj );
}

首先获取到参数的长度和类型。如果类型为window的话,做判断的原因是因为window对象在后面的判断中满足,所以提前做处理。直接返回false,如果有nodeType的话并且还有长度,那么就是类数组的形式,返回true。

如果还不满足则判断是否为数组。

 

 

 


 

以上是关于JQuery源码解析-JQuery的工具方法的主要内容,如果未能解决你的问题,请参考以下文章

JQuery源码解析-JQuery的工具方法

JQuery源码解析-JQuery的工具方法

jQuery源码解析--结构分析

JQuery源码解析-添加JQuery的一些方法和属性

jQuery源码解析 -- 概述

jQuery 源码解析一:jQuery 类库整体架构设计解析