<b>2.7 原型属性和方法</b>
在构造jquery对象模块时,除了2.3节和2.6节已经介绍和分析的jquery.fn.init()和jquery.fn.extend()外,还定义了一些其他的原型属性和方法,其整体源码结构如代码清单2-2所示。
代码清单2-2 原型属性和方法
97 jquery.fn =
jquery.prototype = {
98
constructor: jquery,
99
init: function( selector, context, rootjquery ) {}
210
selector: "",
213
jquery: "1.7.1",
216
length: 0,
219
size: function() {},
223
toarray: function() {},
229
get: function( num ) {},
241
pushstack: function( elems, name, selector ) {},
270
each: function( callback, args ) {},
274
ready: function( fn ) {}, //
284
eq: function( i ) {},
291
first: function() {},
295
last: function() {},
299
slice: function() {},
304
map: function( callback ) {},
310
end: function() {},
316
push: push,
317
sort: [].sort,
318
splice: [].splice
319 };
其中.ready()用于绑定ready事件,将在9.11节进行介绍和分析。下面对其他的原型属性和方法逐一介绍和分析。
2.7.1 .selector、.jquery、.length、.size()
属性selector用于记录jquery查找和过滤dom元素时的选择器表达式,但不一定是可执行的选择器表达式,该属性更多的是为了方便调试。下面是一些示例:
$('div').find('p').selector // "div p"
$('div p').selector // "div p"
$('div').first().selector //
"div.slice(0,1)"
属性jquery表示正在使用的jquery版本号。
属性.length表示当前jquery对象中元素的个数。
方法.size()返回当前jquery对象中元素的个数。方法.size()在功能上等价于属性.length,但应该优先使用属性.length,因为它没有函数调用开销。
属性.selector、.jquery、.length、.size()的相关代码如下所示:
209
// start with an empty selector
211
212
// the current version of jquery being used
214
215
// the default length of a jquery object is 0
217
218
// the number of elements contained in the matched element set
size: function() {
220
return this.length;
221
},
222
2.7.2 .toarray()、.get(
[index] )
1.?.toarray()
方法.toarray()将当前jquery对象转换为真正的数组,转换后的数组包含了所有元素。方法.toarray()的实现巧妙地借用了数组的方法slice(),相关的代码如下所示:
86
// save a reference to some core methods
87
tostring = object.prototype.tostring,
88
hasown = object.prototype.hasownproperty,
89
push = array.prototype.push,
90
slice = array.prototype.slice,
91
trim = string.prototype.trim,
92
indexof = array.prototype.indexof,
223
toarray: function() {
224
return slice.call( this, 0 );
225
第86~92行:连同slice()一起声明的还有tostring()、hasown()、trim()、indexof(),这里通过声明对这些核心方法的引用,使得在jquery代码中可以借用这些核心方法的功能,执行时可通过方法call()和apply()指定方法执行的环境,即关键字this所引用的对象。这种“借鸡下蛋”的技巧非常值得借鉴。
数组方法slice()返回数组的一部分,语法如下:
array.slice(start, end)
// 参数start 表示数组片段开始处的下标。如果是负数,它声明从数组末尾开始算起的位置
// 参数end 表示数组片段结束处的后一个元素的下标。如果没有指定这个参数,切分的数组包含从 start 开始到数组结束的所有元素。如果这个参数是负数,它声明的是从数组尾部开始算起的元素
2.?.get( [index] )
方法.get( [index] )返回当前jquery对象中指定位置的元素或包含了全部元素的数组。如果没有传入参数,则调用.toarray()返回包含了所有元素的数组;如果指定了参数index,则返回一个单独的元素;参数index从0开始计算,并且支持负数,负数表示从元素集合末尾开始计算。相关代码如下所示:
227
// get the nth element in the matched element set or
228
// get the whole matched element set as a clean array
get: function( num ) {
230
return num == null ?
231
232 // return a 'clean' array
233
this.toarray() :
234
235 // return just the object
236 ( num < 0 ? this[ this.length +
num ] : this[ num ] );
237
第236行:先判断num是否小于0,如果小于0,则用length + num重新计算下标,然后使用数组访问操作符([])获取指定位置的元素,这是支持下标为负数的一个小技巧;如果num大于等于0,则直接返回指定位置的元素。
<b>2.7.3 .each(</b>
function(index, element) )、jquery.each( collection, callback (indexinarray, valueofelement) )
1.?.each( function(index, element) )
方法.each()遍历当前jquery对象,并在每个元素上执行回调函数。每当回调函数执行时,会传递当前循环次数作为参数,循环次数从0开始计数;更重要的是,回调函数是在当前元素为上下文的语境中触发的,即关键字this总是指向当前元素;在回调函数中返回false可以终止遍历。
方法.each()内部通过简单的调用静态方法jquery.each()实现,相关代码如下所示:
267
// execute a callback for every element in the matched set.
268
// (you can seed the arguments with an array of args, but this is
269
// only used internally.)
each: function( callback, args ) {
271
return jquery.each( this, callback, args );
272
2.?jquery.each( collection, callback(indexinarray,
valueofelement) )
静态方法jquery.each()是一个通用的遍历迭代方法,用于无缝地遍历对象和数组。对于数组和含有length属性的类数组对象(如函数参数对象arguments),该方法通过下标遍历,从0到length-1;对于其他对象则通过属性名遍历(for-in)。在遍历过程中,如果回调函数返回false,则结束遍历。相关代码如下所示:
627
// args is for internal usage only
628
each: function( object, callback, args ) {
629
var name, i = 0,
630 length = object.length,
631 isobj = length === undefined ||
jquery.isfunction( object );
632
633
if ( args ) {
634 if ( isobj ) {
635 for ( name in object ) {
636 if ( callback.apply(
object[ name ], args ) === false ) {
637 break;
638 }
639 }
640 } else {
641 for ( ; i < length; ) {
642 if ( callback.apply(
object[ i++ ], args ) === false ) {
643 break;
644 }
645 }
646 }
647
648
// a special, fast, case for the most common use of each
649
} else {
650 if ( isobj ) {
651 for ( name in object ) {
652 if ( callback.call(
object[ name ], name, object[ name ] ) === false ) {
653 break;
654 }
655 }
656 } else {
657 for ( ; i < length; ) {
658 if ( callback.call(
object[ i ], i, object[ i++ ] ) === false ) {
659 break;
660 }
661 }
662 }
663
}
664
665
return object;
666
第628行:定义方法jquery.each( object, callback, args ),它接受3个参数。
参数object:待遍历的对象或数组。
参数callback:回调函数,会在数组的每个元素或对象的每个属性上执行。
参数args:传给回调函数callback的参数数组,可选。如果没有传入参数args,则执行回调函数时会传入两个参数(下标或属性名,对应的元素或属性值)如果传入了参数args,则只把该参数传给回调函数。
第631行:变量isobj表示参数object是对象还是数组,以便决定遍历方式。如果object.length是undefined或object是函数,则认为object是对象,设置变量isobj为true,将通过属性名遍历;否则认为是数组或类数组对象,设置变量isobj为false,将通过下标遍历。
第633~646行:如果传入了参数args,对于对象,通过for-in循环遍历属性名,对于数组或类数组对象,则通过for循环遍历下标,执行回调函数时只传入一个参数args。
注意,执行回调函数callback时通过方法apply()指定this关键字所引用的对象,同时要求并假设参数args是数组,如果不是就会抛出typeerror。
第648~663行:如果未传入参数args,对于对象,通过for-in循环遍历属性名,对于数组或类数组对象,则通过for循环遍历下标,执行回调函数时传入两个参数:下标或属性名,对应的元素或属性值。
注意,执行回调函数callback时通过方法call()指定this关键字所引用的对象。
似乎jquery.each()的代码略显啰嗦,因为第633~646行和第648~663行的代码相似度很高,只是触发回调函数的方式和参数不同,完全可以考虑把它们合并,在合并后的代码中根据变量isobj的值决定触发方式和参数,这样就可以减少一半的代码,但是,这也会导致在遍历过程中需要反复判断变量isobj的值。两权相较,方法jquery.each()选择了“略显啰嗦的”代码来避免性能下降。
第665行:最后,返回传入的参数object。方法.each()调用jquery.each()时,把当前jquery对象作为参数object传入,这里返回该参数,以支持链式语法。
<b>2.7.4 .map(</b>
callback(index, domelement) )、jquery.map( arrayorobject, callback(value, indexorkey) )
1.?.map( callback(index, domelement) )
方法.map()遍历当前jquery对象,在每个元素上执行回调函数,并将回调函数的返回值放入一个新jquery对象中。该方法常用于获取或设置dom元素集合的值。
执行回调函数时,关键字this指向当前元素。回调函数可以返回一个独立的数据项或数据项数组,返回值将被插入结果集中;如果返回一个数组,数组中的元素会被插入结果集;如果回调函数返回null或undefined,则不会插入任何元素。
方法.map()内部通过静态方法jquery.map()和原型方法.pushstack()实现,相关代码如下所示:
map: function( callback ) {
305
return this.pushstack( jquery.map(this, function( elem, i ) {
306 return callback.call( elem, i,
elem );
307
}));
308
原型方法.pushstack()将在2.7.5节介绍和分析。
2.?jquery.map( arrayorobject,
callback(value, indexorkey) )
静态方法jquery.map()对数组中的每个元素或对象的每个属性调用一个回调函数,并将回调函数的返回值放入一个新的数组中。执行回调函数时传入两个参数:数组元素或属性值,元素下标或属性名。关键字this指向全局对象window。回调函数的返回值会被放入新的数组中;如果返回一个数组,数组中将被扁平化后插入结果集;如果返回null或undefined,则不会放入任何元素。相关代码如下所示:
760
// arg is for internal usage only
761
map: function( elems, callback, arg ) {
762
var value, key, ret = [],
763 i = 0,
764 length = elems.length,
765 // jquery objects are treated as
arrays
766 isarray = elems instanceof jquery
|| length !== undefined && typeof length === "number"
&& ( ( length > 0 && elems[ 0 ] && elems[ length -1
] ) || length === 0 || jquery.isarray( elems ) ) ;
767
768
// go through the array, translating each of the items to their
769
if ( isarray ) {
770 for ( ; i < length; i++ ) {
771 value = callback( elems[ i ],
i, arg );
772
773 if ( value != null ) {
774 ret[ ret.length ] = value;
775 }
776 }
777
778
// go through every key on the object,
779
780 for ( key in elems ) {
781 value = callback( elems[ key
], key, arg );
782
783 if ( value != null ) {
784 ret[ ret.length ] = value;
785 }
786 }
787
788
789
// fl atten any nested arrays
790
return ret.concat.apply( [], ret );
791
第761行:定义方法jquery.map( elems, callback, arg ),它接受3个参数:
参数elems:待遍历的数组或对象。
参数callback:回调函数,会在数组的每个元素或对象的每个属性上执行。执行时传入两个参数:数组元素或属性值,元素下标或属性名。
参数arg:仅限于jquery内部使用。如果调用jquery.map()时传入了参数arg,则该参数会被传给回调函数callback。
第766行:变量isarray表示参数elems是否是数组,以便决定遍历方式。如果为true,将通过下标遍历;否则将通过属性名遍历。这行复合布尔表达式有些长,为了方便分析,将这行代码等价地格式化为下面的形式:
isarray = elems instanceof jquery
||
length !== undefined && typeof length === "number"
&& ( ( length > 0 && elems[ 0 ] && elems[
length -1 ] )
|| length === 0
|| jquery.isarray( elems ) ) ;
如果elems是jquery对象,则变量isarray为true;如果elem.length是数值型,且满足以下条件之一,则变量isarray为true:
length大于0,且elems[0]存在,且elems[
length -1 ]存在,即elems是一个类数组对象。
length等于0。
elems是真正的数组。
第769~776行:对于数组或类数组对象,则通过for循环遍历下标,为每个元素执行回调函数callback,执行时依次传入三个参数:元素、下标、arg。如果回调函数的返回值不是null和undefined,则把返回值放入结果集ret。
第778~787行:对于对象,则通过for-in循环遍历属性名,为每个属性值执行回调函数callback,执行时依次传入三个参数:属性值、属性名、arg。如果回调函数的返回值不是null和undefined,则把返回值放入结果集ret。
第790行:最后在空数组[]上调用方法concat()扁平化结果集ret中的元素,并返回。
<b>2.7.5 .pushstack(</b>
elements, name, arguments )
原型方法.pushstack()创建一个新的空jquery对象,然后把dom元素集合放入这个jquery对象中,并保留对当前jquery对象的引用。
原型方法.pushstack()是核心方法之一,它为以下方法提供支持:
jquery对象遍历:.eq()、.first()、.last()、.slice()、.map()。
dom查找、过滤:.find()、.not()、.filter()、.closest()、.add()、.andself()。
dom遍历:.parent()、.parents()、.parentsuntil()、.next()、.prev()、.nextall()、.prevall()、
.nextunit()、.prevunit()、.siblings()、.children()、.contents()。
dom插入:jquery.before()、jquery.after()、jquery.replacewith()、.append()、.prepent()、
.before()、.after()、.replacewith()。
相关代码如下所示:
239
// take an array of elements and push it onto the stack
240
// (returning the new matched element set)
pushstack: function( elems, name, selector ) {
242
// build a new jquery matched element set
243
var ret = this.constructor();
244
245
if ( jquery.isarray( elems ) ) {
246 push.apply( ret, elems );
247
248
249 jquery.merge( ret, elems );
250
251
252
// add the old object onto the stack (as a reference)
253
ret.prevobject = this;
254
255
ret.context = this.context;
256
257
if ( name ===
"find" ) {
258 ret.selector = this.selector + (
this.selector ? " " : "" ) + selector;
259
} else if ( name ) {
260 ret.selector = this.selector +
"." + name + "(" + selector + ")";
261
262
263
// return the newly-formed element set
264
return ret;
265
第241行:定义方法.push( elems, name, selector ),它接受3个参数:
参数elems:将放入新jquery对象的元素数组(或类数组对象)。
参数name:产生元素数组elems的jquery方法名。
参数selector:传给jquery方法的参数,用于修正原型属性.selector。
第243行:构造一个新的空jquery对象ret,this.constructor指向构造函数jquery()。
第245~250行:把参数elems合并到新jquery对象ret中。如果参数elems是数组,则借用数组方法push()插入,否则调用方法jquery.merge( first, second )合并。
第253行:在新query对象ret上设置属性prevobject,指向当前jquery对象,从而形成一个链式栈。因此,方法.pushstack()的行为还可以理解为,构建一个新的jquery对象并入栈,新对象位于栈顶,这也是该方法如此命名的原因所在。
第255行:在新jquery对象ret上设置属性prevobject,指向当前jquery对象的上下文,后续的jquery方法可能会用到这个属性。
第257~261行:在新jquery对象ret上设置属性selector,该属性不一定是合法的选择器表达式,更多的是为了方便调试,例如,下面的代码打印了调用方法.pushstack()之后属性selector的值:
console.log( $('div').eq(0).selector );
// div.slice(0,1)
console.log( $('div').first().selector );
console.log( $('div').last().selector );
// div.slice(-1)
console.log( $('div').slice(0,9).selector
);
// div.slice(0,9)
console.log( $('div').find('p').selector );
// div p
console.log( $('div').not('.cls').selector
// div.not(.cls)
console.log(
$('div').find('p').not('.cls').selector );
// div p.not(.cls)
第264行:最后返回新jquery对象ret。
<b>2.7.6 .end()</b>
方法.end()结束当前链条中最近的筛选操作,并将匹配元素集合还原为之前的状态。相关代码如下所示:
end: function() {
311
return this.prevobject || this.constructor(null);
312
第311行:返回前一个jquery对象。如果属性prevobject不存在,则构建一个空的jquery对象返回。
方法.pushstack()用于入栈,方法.end()则用于出栈。这两个方法可以像下面的例子这样使用:
$('ul.first').find('.foo')
.css('background-color', 'red')
.end().find('.bar')
.css('background-color', 'green')
.end();
<b>2.7.7 .eq( index )、.first()、.last()、.slice(</b>
start [, end] )
方法.eq( index )将匹配元素集合缩减为集合中指定位置的元素;方法.first()将匹配元素集合缩减为集合中的第一个元素;方法.last()将匹配元素集合缩减为集合中的最后一个元素;方法.slice( start [, end] )将匹配元素集合缩减为指定范围的子集。
方法.first()和.last()通过调用.eq( index )实现,.eq( index )则通过.slice( start [, end] )实现,.slice( start [,
end] )则通过调用.pushstack( elements, name, arguments )实现,方法调用链为.first/last()→.eq( index )→.slice(
start [, end] )→.pushstack( elements, name, arguments )。相关代码如下所示:
284
eq: function( i ) {
285
i = +i;
286
return i === -1 ?
287 this.slice( i ) :
288 this.slice( i, i + 1 );
289
290
291
first: function() {
292
return this.eq( 0 );
293
294
295
last: function() {
296
return this.eq( -1 );
297
298
299
slice: function() {
300
return this.pushstack( slice.apply( this, arguments ),
301 "slice",
slice.call(arguments).join(",") );
302
第285行:如果参数i是字符串,则通过在前面加上一个加号把该参数转换为数值。
第300~301行:先借用数组方法slice()从当前jquery对象中获取指定范围的子集(数组),再调用方法.pushstack()把子集转换为jquery对象,同时通过属性prevobject保留了对当前jquery对象的引用。
<b>2.7.8 .push( value, ... )、.sort(</b>
[orderfunc] )、.splice( start,deletecount, value, ... )
方法.push( value, ... )向当前jquery对象的末尾添加新元素,并返回新长度,例如:
var foo = $(document);
foo.push( document.body ); // 2
方法.sort( [orderfunc] )对当前jquery对象中的元素进行排序,可以传入一个比较函数来指定排序方式,例如。
var foo = $([33, 4, 1111, 222]);
foo.sort(); // [1111, 222, 33, 4]
foo.sort(function(a, b){
console.log( 'orderfun', a, b );
return a - b;
}) // [4, 33, 222, 1111]
方法.splice( start,deletecount, value, ... )向当前jquery对象中插入、删除或替换元素。如果从当前jquery对象中删除了元素,则返回含有被删除元素的数组。例如:
var foo = $('<div id="d1"
/><div id="d2" /><div id="d3" />');
// [<div
id="d1"></div>, <div id="d2"></div>,
<div id="d3"></div>]
foo.splice( 1, 2 );
id="d2"></div>, <div id="d3"></div>]
方法.push()、.sort()、.splice()仅在内部使用,都指向同名的数组方法,因此它们的参数、功能和返回值与数组方法完全一致。相关代码如下所示:
314
// for internal use only.
315
// behaves like an array's method, not like a jquery method.
<b>2.7.9 小结</b>
构造jquery对象模块的原型属性和方法可以总结为图2-8。