jQuery 源码精粹5 -- event 与 effect

引言

翻过DeferPromise的大山,我们来到了本系列博客的最后一站,这一篇我们来研究event.jseffect.js

event.js

关于jQueryevent模块,最有趣的代码位于src/event/alias.js

1
2
3
4
5
6
7
8
9
10
11
12
jQuery.each( ( "blur focus focusin focusout resize scroll click dblclick " +
"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
"change select submit keydown keypress keyup contextmenu" ).split( " " ),
function( i, name ) {

// Handle event binding
jQuery.fn[ name ] = function( data, fn ) {
return arguments.length > 0 ?
this.on( name, null, data, fn ) :
this.trigger( name );
};
} );

我们可以看到,几乎所有常用的event,底层都在调用on或者trigger方法(根据是否传入一个额外的fn参数), 以on方法为例,它是如何定义的呢?

1
2
3
4
5
6
7
jQuery.fn.extend( {

on: function( types, selector, data, fn ) {
return on( this, types, selector, data, fn );
},
...
}

on方法内部,调用jQuery.event.add方法,给每个选中的DOM 节点绑定相应的事件。

1
2
3
4
5
6
function on( elem, types, selector, data, fn, one ) {
...
return elem.each( function() {
jQuery.event.add( this, types, fn, data, selector );
} );
}

jQuery.event.add方法定义内部:

1
2
3
if ( elem.addEventListener ) {
elem.addEventListener( type, eventHandle );
}

可以看到底层调用addEventListener方法将事件绑定到相应的节点。

effect.js

effect.js定义了jQuery各种酷炫的动画效果,通读其代码,首先看到的有趣部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
// Generate shortcuts for custom animations
jQuery.each( {
slideDown: genFx( "show" ),
slideUp: genFx( "hide" ),
slideToggle: genFx( "toggle" ),
fadeIn: { opacity: "show" },
fadeOut: { opacity: "hide" },
fadeToggle: { opacity: "toggle" }
}, function( name, props ) {
jQuery.fn[ name ] = function( speed, easing, callback ) {
return this.animate( props, speed, easing, callback );
};
} );

可以看到绝大多数的动画定义,底层都是调用的animate方法,区别在于传入了略微不同的prop参数,animate方法是如何定义的呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
jQuery.fn.extend( {
animate: function( prop, speed, easing, callback ) {
var empty = jQuery.isEmptyObject( prop ),
optall = jQuery.speed( speed, easing, callback ),
doAnimation = function() {
...
};
doAnimation.finish = doAnimation;

return empty || optall.queue === false ?
this.each( doAnimation ) :
this.queue( optall.queue, doAnimation );
},
...
}

animate方法在每个DOM节点上执行doAnimation函数。doAnimation的定义十分简单:

1
2
3
4
5
6
7
8
9
10
doAnimation = function() {

// Operate on a copy of prop so per-property easing won't be lost
var anim = Animation( this, jQuery.extend( {}, prop ), optall );

// Empty animations, or finishing resolves immediately
if ( empty || dataPriv.get( this, "finish" ) ) {
anim.stop( true );
}
};

可以看到doAnimaiton方法内部主要是初始化了一个Animation对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function Animation( elem, properties, options ) {
...
var animation = deferred.promise( {
...
} ),

....

jQuery.fx.timer(
jQuery.extend( tick, {
elem: elem,
anim: animation,
queue: animation.opts.queue
} )
);

// attach callbacks from options
return animation.progress( animation.opts.progress )
.done( animation.opts.done, animation.opts.complete )
.fail( animation.opts.fail )
.always( animation.opts.always );
}

Animation的主体代码十分清晰,内部定义了一个私有的Promise对象animation, 按指定的时间间隔触发相应的动画操作,并根据传入的参数绑定完成或失败后的回调函数。

写到这里,jquery 源码分析系列就告一段落了。分享一些感悟:

  • 知其然也要知其所以然,只有洞悉了源代码,方可对使用的库有透彻的理解。
  • 带着问题去阅读(无论是书和代码),可以让阅读变得有趣,让记忆变得深刻。

一千个读者眼中有一千个哈姆雷特,同一份源代码,不同的人读来应有不同的体会。希望本系列博客,可以抛砖引玉,为你阅读jQuery源码提供一些线索。