JQuery源码解析-JQuery的工具方法(5)
下面对最后这几个方法进行讲解。
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。
如果还不满足则判断是否为数组。