本文将分析jQuery对象操作相关方法(包括静态和实例方法):

merge方法,代码如下

 


//此方法用于合并两个jQuery对象(因为jQuery对象中有length属性)或者数组,
	//这个方法非常简单,就是简单的追加第二个对象的属性到第一个对象上去
	merge: function( first, second ) {
		var i = first.length,
			j = 0;
		if ( typeof second.length === "number" ) {
			for ( var l = second.length; j < l; j++ ) {
				first[ i++ ] = second[ j ];
			}

		} else {
			while ( second[j] !== undefined ) {
				first[ i++ ] = second[ j++ ];
			}
		}

		first.length = i;

		return first;
	},

可以看到此方法非常简单,但它在jQuery内部应用的地方却是非常多的,包括init方法,pushStack方法,makeArray方法里都用到了此方法。

pushStack方法,代码如下

 



pushStack: function( elems, name, selector ) {
		//constructor指向jQuery方法本身,那自然ret会被创建为一个空的jQuery对象(可作为一个元素集)
		//此时等效为 var ret = jQuery();
		// Build a new jQuery matched element set
		var ret = this.constructor();

		if ( jQuery.isArray( elems ) ) {
			//push是Array.prototype.push的一个shortcut,此时将elems添加到ret中
			push.apply( ret, elems );

		} else {
			jQuery.merge( ret, elems );
		}
		// 保存操作前的对象
		// Add the old object onto the stack (as a reference)
		ret.prevObject = this;

		ret.context = this.context;

		if ( name === "find" ) {
			ret.selector = this.selector + (this.selector ? " " : "") + selector;
		} else if ( name ) {
			ret.selector = this.selector + "." + name + "(" + selector + ")";
		}

		// Return the newly-formed element set
		return ret;
	},



要注意的是pushStack方法并不对源对象做破坏性更改(所谓破坏性更改,是指对原jQuery对象中选择的元素有变动的更改),而是返回新的 jQuery对象,因此在使用时需要注意。也就是说诸如下面的代码在使用时容易使人迷惑,其实obj并未把DOM元素加入到当前的jQuery栈,而我们 将元素加入当前jQuery栈的一般做法还是用选择器来实现:

 



var obj = $("a");
	ret = obj.pushStack(document.getElementsByTagName("span")).pushStack(document.getElementsByTagName("input"));
	//obj依然是包含a元素的jQuery对象
	console.log(obj); //[<a>?</a>?]
	//ret则仅仅包含input
	console.log(ret); //[<input type=?"button" id=?"btn" value=?"click!">?]



之所以把pushStack设置为实例方法而非静态方法,就是为了在jQuery中实现一个undo操作的功能,更多可以参见jQuery文档中关于end方法的使用。

其实pushStack本身在jQuery文档中也未公开,作为Internals方法存在,因此不建议使用此方法,但是在jQuery内部经常看到此方法出现,比如下面即将说到的map方法:

//该方法将返回经过callback过滤后的jQuery对象,
	map: function( callback ) {
		return this.pushStack( jQuery.map(this, function( elem, i ) {
			return callback.call( elem, i, elem );
		}));
	},



由于实例map方法调用了静态map方法,我们接着看看静态map方法:

 

// arg is for internal usage only
	map: function( elems, callback, arg ) {
		var value, key, ret = [],
			i = 0,
			length = elems.length,
			// 传入jQuery对象时,将会把jQuery对象也作为数组来处理
			// jquery objects are treated as arrays
			isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ;

		// Go through the array, translating each of the items to their
		if ( isArray ) {
			for ( ; i < length; i++ ) {
				value = callback( elems[ i ], i, arg );//必须有返回值

				if ( value != null ) {
					ret[ ret.length ] = value;
				}
			}

		// Go through every key on the object,
		} else {
			for ( key in elems ) {
				value = callback( elems[ key ], key, arg );

				if ( value != null ) {
					ret[ ret.length ] = value;
				}
			}
		}
		// 最后连接数组,这样做是为了防止空项出现
		// Flatten any nested arrays
		return ret.concat.apply( [], ret );
	},

map方法和each方法非常的相似,其区别在于,map提供了一个数组到另一个数组映射的功能,而each则单纯地队对象或数组是进行迭代访问,这要求我们必须在使用map时,回调函数必须提供返回值,否则将在返回值中删除该项。

另一个容易混淆的数组遍历方法是grep:

 

grep: function( elems, callback, inv ) {
		var ret = [], retVal;
		inv = !!inv;  //小提示,!!是为了确保inv转换为bool值,当不传递inv时,!!undefined便等于false

		// Go through the array, only saving the items
		// that pass the validator function
		for ( var i = 0, length = elems.length; i < length; i++ ) {
			retVal = !!callback( elems[ i ], i );
			if ( inv !== retVal ) {//默认回调函数返回false时将不保留此项
				ret.push( elems[ i ] );
			}
		}

		return ret;
	},


很容易知道我们在迭代数组时,必须要求回调函数中返回一个bool值来确定该项是否保留。

grep和map方法在文档中属于实用工具(Utilities),由于概念相近,因此放在这里讲解。