本文将分析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),由于概念相近,因此放在这里讲解。