jQuery transform类型实例方法探究

来源:互联网 发布:mysql修改连接密码 编辑:程序博客网 时间:2024/05/21 02:36

jQuery.prototype中定义了许多非常有趣的方法,同样对比Spark RDD的逻辑可以将其一部分划分为几个大的类型:transform类型及与之相关的方法集、action类型及与之相关的方法集,本篇将着重分析Transform类型的方法。这两大类方法是针对"已有选中原生客户端对象集合"的jQuery对象而言的,"怎样在文档对象中选中原生客户端对象集"可以看做是构造RDD的一大类方法,这个主题(Sizzle)几乎是jQuery最核心的部分,将在后续文章详细阐述。

一 jQuery.prototype.each方法


首先看看jQuery原型对象的each方法,这个方法单独提前分析是因为它与其他的transform类型的方法不太一致。

// Execute a callback for every element in the matched set.// (You can seed the arguments with an array of args, but this is// only used internally.)each: function( callback, args ) {return jQuery.each( this, callback, args );},


前一篇文章已经详细分析过jQuery类型静态工具方法jQuery.each的原理和实现,那么这里就很好理解这个原型对象的each方法了。
第一个参数this,在原型对象中关键字this可以看做是jQuery实例对象的引用,既然jQuery对象是Array-Like Object,那么this传入jQuery.each就被当做数组处理了(jQuery对象几乎总是被当做一个Array实例处理的)。
第二个参数callback已经在jQuery.each中分析过,约束了其参数类型和顺序,参见jQuery.each,第一个参数是index,第二个参数是jQuery选中客户端对象集合中的元素。
特别需要注意的是jQuery.each返回的对象就是第一个参数对象,因此这里返回的就是传入的this--当前jQuery实例对象,但是其选中集合中的每个原生对象元素都可能被回调函数callback处理过。each方法与其他transform实例方法不同的地方就在于它没有在jQuery对象stack上新产生一个jQuery对象,接下来的几个transform方法都产生新实例对象了。

二 jQuery.prototype.map方法


前面的each方法与这里的map方法都类似于Spark的map操作,区别是each方法不生成新jQuery对象,map方法生成一个新jQuery对象。

map: function( callback ) {return this.pushStack( jQuery.map(this, function( elem, i ) {return callback.call( elem, i, elem );}));},

这里出现一个新实例方法--pushStack,先不用去关注其具体实现,首先看其参数--是一个由静态方法jQuery.map处理后返回的数组对象。其次,关注下回调函数callback,由于传递给jQuery.map的第二个参数是一个匿名回调函数,这个匿名回调函数的参数类型和顺序已经被静态方法jQuery.map约束了,但是其内部调用[实例方法map的回调函数参数]callback的时候把参数顺序换了一下,所以jQuery实例方法map的回调函数与jQuery静态类方法map的回调函数的参数顺序是相反的。其次关于为什么callback中this关键字表示的是原生客户端对象,在jQuery.each的解析部分已解释过,这里类似,匿名函数的第一个参数elem已经是原生客户端对象了,借用callback的时候作为第一个参数--callback.call( elem, i, elem ),当然callback中的this就指向elem了。

再来看看pushStack的实现,这也是jQuery原型对象的实例方法并被其他大量的实例方法广泛调用。

// Take an array of elements and push it onto the stack// (returning the new matched element set)pushStack: function( elems, name, selector ) {// Build a new jQuery matched element setvar ret = this.constructor();if ( jQuery.isArray( elems ) ) {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 setreturn ret;},

对这个方法的理解有难度,如果看不懂第一行代码的话:“var ret = this.constructor();”,其注释是一个很好的解释:Build a new jQuery matched element set。this.constructor指向jQuery函数
var jQuery = function( selector, context ) {
// The jQuery object is actually just the init constructor 'enhanced'
return new jQuery.fn.init( selector, context, rootjQuery );
},
调用this.constructor()后返回的是由jQuery真正的构造函数jQuery.prototype.init生成的一个jQuery实例对象,问题是此处没有传递任何参数,所以虽然返回的是jQuery实例对象,但它几乎是一个光杆司令--连length属性都没有开辟(详情参见对jQuery构造函数的阐述篇)。
但是接下来的活儿就是可劲儿的渲染这个空jQuery对象的各种属性了:push.apply( ret, elems )或者jQuery.merge( ret, elems ),由于map传入的是一个数组,因此执行的是push.apply( ret, elems ),在此不得不对Array.prototype.push方法表示由衷的敬意--Array.prototype.slice好歹要求处理的对象起码得有length属性,push方法没有任何条款!此步调用后jQuery对象如愿以偿的拥有index属性和length属性了--Array-Like Object。
继续渲染一个属性:ret.prevObject = this,所谓栈数据结构就形成了。(像不像java的HashMap中hash位上的对象栈?)
当然还要设置上下文属性的:ret.context = this.context;(jQuery的上下文对象又足以开辟一个很长很长的专章去阐述了。。。)
由于map方法没有传递第二个参数,所以ret没有selector属性,因为这个ret对象没有辛辛苦苦的去使用selector遍历DOM(Sizzle的专题)产生,而是使用拿来主义直接利用原jQuery对象经过map后的集合产生,所以不对这类jQuery对象开辟selector属性是有道理的。

三 jQuery.prototype.slice方法


jQuery.prototype.eq(i)、jQuery.prototype.first()、jQuery.prototype.last()都是jQuery.prototype.slice方法的变种,这里只分析jQuery.prototype.slice方法。

slice: function() {return this.pushStack( slice.apply( this, arguments ),"slice", slice.call(arguments).join(",") );},

与jQuery.prototype.map方法类似,slice方法也产生一个新的jQuery对象。这个新的jQuery对象在原jQuery对象上经过slice操作而来,关于Array.prototype.slice的操作参见其专题,特点是足够灵活。
这个方法没有定义形式参数就是因为slice的使用方法足够灵活:传入"-1"表示截取数组的最后一个元素,传入"i,i+1"表示截取数组指定位置i上的一个元素,传入"i,i+n"表示截取数组从i到i+n之间的所有元素(不包含i+n位置)。
需要注意的是pushStack的第二、三个参数,结合上面对其分析,可知返回的新jQuery对象具有selector属性且值为“oriselector.slice(i1,i2)”。

四 jQuery.prototype.filter方法


filter方法定义如下,对应winnow函数一并列出。

filter: function( selector ) {return this.pushStack( winnow(this, selector, true), "filter", selector );},// Implement the identical functionality for filter and notfunction winnow( elements, qualifier, keep ) {// Can't pass null or undefined to indexOf in Firefox 4// Set to 0 to skip string checkqualifier = qualifier || 0;if ( jQuery.isFunction( qualifier ) ) {return jQuery.grep(elements, function( elem, i ) {var retVal = !!qualifier.call( elem, i, elem );return retVal === keep;});} else if ( qualifier.nodeType ) {return jQuery.grep(elements, function( elem, i ) {return (elem === qualifier) === keep;});} else if ( typeof qualifier === "string" ) {var filtered = jQuery.grep(elements, function( elem ) {return elem.nodeType === 1;});if ( isSimple.test( qualifier ) ) {return jQuery.filter(qualifier, filtered, !keep);} else {qualifier = jQuery.filter( qualifier, filtered );}}return jQuery.grep(elements, function( elem, i ) {return (jQuery.inArray( elem, qualifier ) >= 0) === keep;});}

重点在于winnow函数,所以分析下这个函数。可以看到充当filter pridicate的qualifier参数可以是函数、可以是dom node、也可以是string或其他。
当qualifier是函数时,其参数顺序和类型与jQuery("xx").each方法、jQuery("").map方法保持一致,即第一个参数是index,第二个参数是elements元素。并且可以根据winnow第三个参数keep决定取反逻辑。
当qualifier是dom node时(有nodeType属性),只精确选定elements中就是qualifier节点的那些元素。(该qualifier对象只有一个,但是引用可以有多个)
当qualifier是string时,首先从elements中过滤出只是Element类型的节点集合,然后在这个集合中进一步过滤--通过调用静态工具方法jQuery.filter,jQuery.filter涉及到Sizzle核心,专题讨论。
当qualifier不是以上三种类型时(可能是Array对象或jQuery对象),过滤出elements中包含于qualifier的元素,类似两个集合的intersection操作。
0 0
原创粉丝点击