logo头像

求知若渴,虚心若愚

jQuery - jQuery源码学习 - jQuery扩展工具方法源码分析(六)

guid

  • 属性jQuery.guid是一个全局计数器,用于jQuery事件模块和缓存模块
  • 在jQuery事件模块中,每个事件监听函数会被设置一个guid属性,用来唯一标识这个函数;
  • 在缓存模块中,通过在DOM元素上附加一个唯一标识,来关联该元素和该元素对应的缓存。
  • 属性jQuery.guid初始值为1,使用时自增1
1
2
3
4
5
6

function show(){
alert(this);
},
$("#input1").click(show),
$("#input2").click(function(){$("#input1").off()})
  • 这里的show方法是事件方法,所以通过off取消掉事件绑定,可以很容易找到事件方法show。
  • 但是如果把 $("#input1").click(show)改成 $("#input1").click($.proxy(show,window)),这时show不是事件方法,而是普通方法,那么通过off取消的时候,它是怎么找到这个普通方法show的,其实就是通过guid,因为guid会累加,所以是唯一的,因此可以找到。

proxy()

  • 改变方法(函数)执行的this指向
  • 方法proxy接受一个函数,返回一个新函数,新函数总是持有特定的上下文。

  • proxy两种调用形式:

    • jQuery.proxy( function, context )
      • 参数function是将被改变上下文的函数,参数context是上下文。
      • 指定参数function的上下文始终为参数content。
    • jQuery.proxy( context, name )
      • 参数name是参数context的属性。
      • 指定参数name对应的函数的上下文始终为参数context。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

proxy: function( fn, context ) {
var tmp, args, proxy;
// 修正参数fn和context。
// 如果第二个参数是字符串,说明参数格式是jQuery.proxy( context, name ),修正为jQuery.proxy( fn, context )。
if ( typeof context === "string" ) {
tmp = fn[ context ];
context = fn;
fn = tmp;
}
// 如果参数fn不是函数,则返回undefined
if ( !jQuery.isFunction( fn ) ) {
return undefined;
}
// 收集多余参数
// 说明:如果调用jQuery.proxy()时,除了传入参数fn、context之外,还传入了其他参数,那么在调用函数fn时,这些多余的参数将会优先传入。
// 这里借用数组方法slice()来获取参数对象arguments中fn、context后的其他参数。
// core_deletedIds = [],
// core_slice = core_deletedIds.slice,
args = core_slice.call( arguments, 2 );

// 创建一个代理函数,在代理函数中调用原始函数fn,调用时通过方法apply()指定上下文。
// 代理函数通过闭包机制引用context、args、slice
proxy = function() {
return fn.apply( context || this, args.concat( core_slice.call( arguments ) ) );
};

// 为代理函数设置与原始函数相同的唯一标识guid。如果原始函数没有,则重新分配一个。
proxy.guid = fn.guid = fn.guid || jQuery.guid++;

return proxy;
},

TIP

  • 相同的唯一标识将代理函数和原始函数关联了起来。

  • 例如,在jQuery事件系统中,如果为DOM元素绑定了事件监听函数的代理函数,当移除事件时,即使传入的是原始函数,jQuery也能通过唯一标识guid移除正确的函数。

access()

  • 可以为集合中的元素设置一个或多个属性值,或者读取第一个元素的属性值。
  • 如果设置的属性值是函数,并且参数exec是true时,还会执行函数并取其返回值作为属性值。

  • 为.attr()、.prop()、.css()提供支持

  • 这三个方法在调用jQuery.access()时,参数exec为true
  • 参数fn是同时支持读取和设置属性的函数(例jQuery.attr()、jQuery.prop())

TIP

  • 根据参数的个数和参数的类型不同,去区分

  • $().css(),$().attr()$().prop(),通过参数的不同,实现get/set。

  • $("div").css("width"),获得第一个div元素的width,$("div").css("width",100)设置所有的div元素的width。
  • $("div").css({width:100,height:200}),也是设置所有的div元素,尽管只有一个参数,但是类型不一样。JQuery中有很多这种方法,所以统一用access实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

access: function( elems, fn, key, value, chainable, emptyGet, raw ) {
// elems:元素集合,通常是jQuery对象,操作的元素,可能是一个集合
// fn是一个回调函数(有区别的在回调函数中处理,比如,css设置样式,attr设置属性)
// key和value就是属性名和属性值
// chainable为true,设置,为false就获取, 是否可以链式调用,如果是get动作,为false,如果是set动作,为true
// emptyGet:该参数一般是不给的,当没有元素时返回undefined, 如果jQuery没有选中到元素的返回值
// raw : 字符串为真,函数为假
var i = 0,
length = elems.length,
bulk = key == null;

// 如果参数key是对象,表示要设置多个属性,则遍历参数key,为每个属性递归调用方法jQuery.access(),遍历完后返回元素集合elems。
// 处理这种类型$("div").css({width:100,height:200})
if ( jQuery.type( key ) === "object" ) {
chainable = true;
for ( i in key ) {
jQuery.access( elems, fn, i, key[i], true, emptyGet, raw );
}
}
// 如果参数value不是undefined,表示要设置单个属性,则遍历元素集合elems,为每个元素调用回调函数fn,遍历完后返回元素集合elems。如果参数exec为true,并且参数value是函数,则执行参数value,并取其返回值作为属性值。
// 处理这种$("div").css("width",100)
else if ( value !== undefined ) {
chainable = true;// 表示可以链式调用
// 如果value不是function,设置raw为true
if ( !jQuery.isFunction( value ) ) {
raw = true;
}
// 判断key值是否为null或者undefined,key若为空值
if ( bulk ) {
if ( raw ) { // 如果value是字符串(数字)
fn.call( elems, value );// 调用回调方法
fn = null;// 把回调方法赋为空
}
// 如果是函数,这里面的不用深入理解
else {
bulk = fn;
fn = function( elem, key, value ) {
return bulk.call( jQuery( elem ), value );
};
}
}

// 如果没有key值,并且value是字符串(数字),这里就为null,不会执行
// 如果fn存在,掉调用每一个元素,无论key是否有值,都会走到这个判断,执行set动作
if ( fn ) {
for ( ; i < length; i++ ) {
fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) );
}
}
}

return chainable ? // 如果chainable为true,说明是个set方法,就返回elems,否则说明是get方法
elems : // //设置时,chainable为true,直接返回元素,进行后续的链式操作

// 1.如果bulk是个true,说明没有key值,调用fn,将elems传进去
// 2.如果bulk为false,说明key有值哦,然后判断元素的长度是否大于0
// 2.1 如果大于0,调用fn,传入elems[0]和key,完成get
// 2.2 如果为0,说明传参有问题,返回指定的空值emptyGet
bulk ?
fn.call( elems ) :
length ? fn( elems[0], key ) : emptyGet; // 有key值时,判断元素有没有元素,有的话就获取第一个元素的key值(属性名的值),没有元素的话,就返回emptyGet。
},
  • 首先判断key值是不是一个object,如果是,遍历key,递归调用jQuery.access,并将是否可以链式调用的标志位设置为true
  • 判断value值是否已经定义,如果已经定义,说明是个set操作

    • set操作的是可链式调用的
    • 如果value不是function,设置raw为true
    • 判断key值是否为null或者undefined,key若为空值

      • 如果value不是个函数,或者强制赋值raw为true,那么调用fn,可能是以下调用:$(‘#box’).attr(null,{abc:’def’,a:’1’})
      • 如果value是个函数,将fn包装之,改变原来fn的作用域和参数
    • 如果fn存在,遍历jQuery内部元素,分别执行set操作

  • 首先判断是否为set方法,如果是,返回 elems,如果不是执行get操作(如果jQuery内部length为0,返回指定的默认空值)

now()

  • 当前时间距离1970年的毫秒数
  • 相当于(new Date()).getTime()

swap

  • css交换(内部)
  • 原生js无法获取display为none的属性值,而jQuery却可以获取,原因就是在内部使用了swap方法。
1
2
3
4
5
6
7
8

<div id="div1" style="width:100px;height:100px;display:none;">ddd</div> 

$("#div1").get(0).offsetWidth取到的是0,因为它是display:none,不存在DOM树中。

$("#div1").width()取到的是100,为啥jQuery可以。因为jQuery会对display:none的元素进行处理,变成<div id="div1"style="width:100px;height:100px;display:block;visibility:hidden;position:absolute">ddd</div>

这里就可以通过$("#div1").get(0).offsetWidth取到100了,然后再把新添加的样式去掉。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

swap: function( elem, options, callback, args ) {
var ret, name,
old = {};

//保存老样式,插入新样式。
for ( name in options ) {
old[ name ] = elem.style[ name ];
elem.style[ name ] = options[ name ];
}

ret = callback.apply( elem, args || [] );
// 通过插入的新样式来获取元素的css值
for ( name in options ) { // 恢复老样式
elem.style[ name ] = old[ name ];
}

return ret;
}
支付宝打赏 微信打赏

赞赏是对我们的肯定!