代码改变世界

(九)jQuery.extend代码段

2012-02-12 16:03  kwjlk  阅读(254)  评论(0编辑  收藏  举报

再次返回看init的代码,我抽离出一个init的简明结构:

init: function(){
jQuery.initDone = true;

jQuery.each( jQuery.macros.axis, function(i,n){
jQuery.fn[ i ] = function(a) {
...
};
});

jQuery.each( jQuery.macros.to, function(i,n){
jQuery.fn[ i ] = function(){
...
};
});

jQuery.each( jQuery.macros.each, function(i,n){
jQuery.fn[ i ] = function() {
...
};
});
jQuery.each( jQuery.macros.filter, function(i,n){
jQuery.fn[ n ] = function(num,fn) {
...
};
});

jQuery.each( jQuery.macros.attr, function(i,n){
n = n || i;
jQuery.fn[ i ] = function(h) {
...
};
});
jQuery.each( jQuery.macros.css, function(i,n){
jQuery.fn[ n ] = function(h) {
...
};
});
}

注意每一段each的第二个function参数,该function里定义了jQuery.fn上的属性。也就是说,这里所有的each都是在拓展jQuery对象上的功能。

先开始从第一个each代码开始看,init函数如何拓展jQuery对象上的功能。发现调用了pushStack,查看pushStack方法发现调用了each 和 get方法。这两个方法均在jQuery.fn = jQuery.prototype = { ... } 代码段的定义里。

each: function( fn, args ) {
return jQuery.each( this, fn, args );
}
get: function( num ) {
// Watch for when an array (of elements) is passed in
if ( num && num.constructor == Array ) {
// Use a tricky hack to make the jQuery object
// look and feel like an array
this.length = 0;
[].push.apply( this, num );

return this;
} else
return num == undefined ?
// Return a 'clean' array
jQuery.map( this, function(a){ return a } ) :
// Return just the object
this[num];
}
pushStack: function(a,args) {
var fn = args && args[args.length-1];
if ( !fn || fn.constructor != Function ) {
if ( !this.stack ) this.stack = [];
this.stack.push( this.get() );
this.get( a );
} else {
var old = this.get();
this.get( a );
if ( fn.constructor == Function )
return this.each( fn );
this.get( old );
}
return this;
}

首先看each函数实现。该each函数仅仅是对jQuery.each的封装,隐含each作用对象为当前的jQuery对象。

再看get函数的实现。当向get传递的参数num为数组时,则将该数组(作为一个整体,而不是把数组中的值一个个的)入栈到jQuery对象上,并将入栈后的jQuery对象返回。当num参数为非数组时,如果未定义(一般为不传参时)则返回当前jQuery对象所有属性值的数组;如果定义了,则返回当前jQuery对象上此参数作为key的值。

最后看pushStack函数的实现。这里有一个问题,a参数一般会是什么值?经过研究许久发现,a参数一般会是数组值。所以,讨论当a是数组时pushStack的一般意义。为什么a大部分情况下是数组呢?查看pushStack的大部分引用代码就可以知道了:

 

jQuery.fn[ i ] = function(a) {
var ret = jQuery.map(this,n);
if ( a && a.constructor == String )
ret = jQuery.filter(a,ret).r;
return this.pushStack( ret, arguments );
};

如果fn未设置或者不为函数的话,则pushStack先将当前jQuery对象的DOM元素压入堆栈,然后将新的a压入当前的jQuery对象。最后将这个对象返回。这里需要再次对get函数进行说明。get函数影响了当前的jQuery对象的length属性,所以在目前没有找到明显定义或设置jQuery对象的length属性的代码之前get的调用值得注意。当fn为函数时pushStack将a通过this.get(a)保存到this上,并对this调用each处理,而this.get(old)则不会被执行。于是翻看未分析过的代码,发现最开始jQuery(a,c){ ... } 的代码中调用了get函数。结合jQuery(a,c){ ... } 是创建jQuery对象的主入口和一些以往使用jQuery的经验,摘录实现代码中几行引起我注意的代码:

// Handle HTML strings
var m = /^[^<]*(<.+>)[^>]*$/.exec(a);
if ( m ) a = jQuery.clean( [ m[1] ] );
// Watch for when an array is passed in
this.get( a.constructor == Array || a.length && !a.nodeType && a[0] != undefined && a[0].nodeType ?
// Assume that it is an array of DOM Elements
jQuery.merge( a, [] ) :
// Find the matching elements and save them for later
jQuery.find( a, c ) );

首先,jQuery.length的初始化以及jQuery对象的jQueryObject[index]是什么值,在最后一行比较大的代码里可以得到解读。由于这段代码处在创建jQuery对象的地方,所以创建jQuery对象之后则会因为调用了一次get函数而将length属性初始化,具体初始化的值要根据get( ... ) 中的表达式计算出的值来确定了。这一段表达式中的注释 // Assume that it is an array of DOM Elements 假设a是一个DOM元素的数组 我们可以看出这段表达式返回的是一个DOM元素数组。所以在对jQuery对象进行each遍历时,是遍历的这个DOM元素数组。要记得each中对于有length属性的参数obj是采用数组遍历的形式的。这里,我们也要对find和filter的返回值有一个总结:那就是返回一个DOM元素数组。

再看最开始两行对jQuery.clean函数的用法,在后面分析clean时需要分析到这两行代码。现在先解析一下传递给clean的是什么参数呢?第一行是一段正则表达式,而m[1]是正则表达式中第一个元组匹配的结果。还记不记得jQuery可以根据传入的一段html文本,并返回jQuery对象。没错,这里的m[1]就是匹配出你传入的html文本这种鬼东西。在接下来的分析clean代码时要记得这点。

我现在可以理解第一段each代码所实现的内容了:

jQuery.each( jQuery.macros.axis, function(i,n){
jQuery.fn[ i ] = function(a) {
var ret = jQuery.map(this,n);
if ( a && a.constructor == String )
ret = jQuery.filter(a,ret).r;
return this.pushStack( ret, arguments );
};
});

拓展jQuery对象身上的axis类方法,该方法主题过程是:按照方法属性即n指定的操作,获取当前jQuery对象所代表的DOM元素的指定DOM元素并以数组形式返回。如果设定了过滤条件,那么对获取的数组进行过滤。ret仍旧是DOM元素数组。最后将DOM数组pushStack会当前jQuery对象,并将该对象返回。这里需要注意的是,return pushStack(ret,arguments)操作并没有创建新的jQuery对象,而是将当前jQuery对象所对应的DOM元素替换为ret,并将老的DOM元素数组放入堆栈以便调用get方法可以再次取出。

第二段each代码的实现:

jQuery.each( jQuery.macros.to, function(i,n){
jQuery.fn[ i ] = function(){
var a = arguments;
return this.each(function(){
for ( var j = 0; j < a.length; j++ )
$(a[j])[n]( this );
});
};
});

to中是进行的DOM节点插入操作。arguments是指这些操作的参数,可以是节点数组,即传入多个节点。这个each代码是进行的对给定参数中的每个节点调用当前jQuery对象所关联的DOM元素的指定方法。

第三段each代码的实现

jQuery.each( jQuery.macros.filter, function(i,n){
jQuery.fn[ n ] = function(num,fn) {
return this.filter( ":" + n + "(" + num + ")", fn );
};
});

这一段代码是拓展jQuery对象上的过滤类方法。实现很简单,即是调用当前jQuery对象上的filter方法。

第四段each代码的实现

jQuery.each( jQuery.macros.attr, function(i,n){
n = n || i;
jQuery.fn[ i ] = function(h) {
return h == undefined ?
this.length ? this[0][n] : null :
this.attr( n, h );
};
});

这一段代码是拓展jQuery对象上的DOM节点属性操作方法。注意,这里在return时实现了指定h时设置指定属性,未指定时获取值的功能。

jQuery.each( jQuery.macros.css, function(i,n){
jQuery.fn[ n ] = function(h) {
return h == undefined ?
( this.length ? jQuery.css( this[0], n ) : null ) :
this.css( n, h );
};
});

这段代码是拓展jQuery对象的css类方法。实现了与属性一样的传入值则设置css属性否则获取css属性值的功能。我们看到jQuery.css 和 jQuery.fn.css(即this.css)我们还没有分析,于是