前言
页面上如果有多个属性为class=”clr”的节点,通过jQuery操作可以拿到这些DOM节点,并包装为jQ对象:var doms = $(‘.clr’);
很容易就联想到doms应该返回的是一个数组。
这个问题很好解决,我们可以理解成jQuery继承了Array这个类。
于是doms可以像数组那样操作子元素。例如:
doms.length可以获取jQuery获取到的DOM节点的数目,经常会用 doms.length==0 来判断获取到的DOM是否为空或者不存在
doms[0]可以拿到第一个DOM节点对象(注意这里不是jQ对象!)
在实际的操作中,需要经常用到的这两个有关类数组的操作就够了。
如左图,文档里边有两个div,通过jQuery选择了$(‘div’)两个div,于是得出的jQ对象如右图所示。
Get first & Set all
在面向对象的范畴里边,GET方法就是代表这那些可以获取对象属性状态的方法,SET方法就是代表那些可以设置对象属性状态的方法。
jQuery对象里边就有大量的GET/SET方法。
例如:
- 通过$(“#input”).val()获取id为input输入框的输入值。
- 通过$(‘#test’).html(‘Hello World’)把id为test的节点的HTML内容设置成“Hello World”
- 通过$(‘#test’).attr(‘class’)获取id为test的节点的class属性
jQuery很灵活的一点就是,同样一个接口,通过参数不同来区别它是GET还是SET方法。
例如:
- $(“#input”).val()是获取输入框的值,而$(“#input”).val(‘new input value’)是设置输入框里边的值
- $(“#test”).html()是获取节点的html内容,而$(“#test”).html(‘Hello World’)是设置节点的HTML内容为“Hello World”
根据第一节料到的jQ对象实际是类数组的一个对象,那就会抛出一个问题:GET/SET方法分别操作的是数组的哪个元素呢?
举个例子说:var doms = $(‘.input’),获取到10个input节点。
那doms.val()返回的是哪个输入框的值呢?而doms.val(‘new input value’)又是设置哪个输入框的值?
这里有个设计的原则:Get first & Set all。
看名字应该就知道什么意思了,通过GET方法拿到的就是第一个元素的值,通过SET方法是设置所有元素。
也即是说doms.val()返回的是第一个输入框的值,而doms.val(‘new input value’)是设置全部输入框的值。
对于你来说,你是喜欢jQuery用参数区分GET/SET方法?还是喜欢用接口名字getVal/setVal来区分呢?
类数组的成员方法
由于方法比较简单,这里直接源码注释说明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | jQuery.fn = jQuery.prototype = { /* blablabla */ // The default length of a jQuery object is 0 //jQ对象里边选取的DOM节点数目,有了这个属性就已经像“半个”数组了,:) length: 0, // The number of elements contained in the matched element set //其实我觉得直接使用length属性即可,但是由于无法保证length属性会不会被外界赋值 //所以建议通过size()方法来获取,慎重操作length属性 size: function() { return this.length; }, //将jQ对象转换成数组类型,这里返回的结果就真的是Array类型了 toArray: function() { return core_slice.call( this ); }, // Get the Nth element in the matched element set OR // Get the whole matched element set as a clean array //获取索引为num的元素,看代码就知道这里支持num为负数 //当num为负数的时候,相当于从数组尾巴倒数索引 get: function( num ) { return num == null ? // Return a 'clean' array this.toArray() : // Return just the object ( num < 0 ? this[ this.length + num ] : this[ num ] ); }, // Take an array of elements and push it onto the stack // (returning the new matched element set) //将传进来的数组包装成jQ对象 pushStack: function( elems ) { // Build a new jQuery matched element set //this.constructor() 相当于 new jQuery() var ret = jQuery.merge( this.constructor(), elems ); //这样ret就有了jQ对象的所有方法,也就是ret就是一个jQ对象了 //并且ret里边的元素已经合并了elems了! // Add the old object onto the stack (as a reference) //为什么需要prevObject,这里看end方法就知道了,:) ret.prevObject = this; ret.context = this.context; // Return the newly-formed element set return ret; }, // 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来遍历当前对象 each: function( callback, args ) { return jQuery.each( this, callback, args ); }, slice: function() { return this.pushStack( core_slice.apply( this, arguments ), "slice", core_slice.call(arguments).join(",") ); }, first: function() { return this.eq( 0 ); }, last: function() { return this.eq( -1 ); }, //用eq的差别跟get的差别是: //get返回的是jQ对象里边的元素,元素类型是DOM节点 //而eq方法拿到DOM节点后,还会通过pushStack方法封装成一个jQ对象,以便可以继续用jQ的方法操作 eq: function( i ) { var len = this.length, j = +i + ( i < 0 ? len : 0 ); return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] ); }, map: function( callback ) { return this.pushStack( jQuery.map(this, function( elem, i ) { return callback.call( elem, i, elem ); })); }, //为什么要end方法 //看个例子就明白了: //$("#id").find('.clr').html('.clr HTML').end().html('#id HTML') //本来find函数已经使得链的上下文切换到.clr这个jQ对象了,为了最后能回到#id这个jQ对象 //可以使用end方法来返回 //这里的秘籍就是每个对象里边的prevObject保存着链中的上一个jQ对象 end: function() { return this.prevObject || this.constructor(null); }, // For internal use only. // Behaves like an Array's method, not like a jQuery method. push: core_push, sort: [].sort, splice: [].splice } |
init方法
有关jQuery的init方法的理解,看我上一篇文章《jQuery源码剖析(五)——jQuery对象》
这里我直接就把init方法当成是jQuery构造方法来表述。
我们可以通过以下几种方式来拿到页面的DOM节点,并包装成为一个jQ对象。
- $(“#test”)拿到id为test的DOM节点
- $(dom)将一个DOM节点,封装成jQ对象
- $(“<div></div>”)将HTML片段封装成jQ对象
- $(“.clr”, dom)在dom(DOM节点)下边取得所有class为clr的DOM节点
- $(“.clr”, jqobj)在jqobj(jQ对象)下边取得所有class为clr的DOM节点
- $(‘#test .clr[name="pwd"]‘)复杂的CSS选择器来选取DOM节点
- $(fn)等同于$(document).ready(fn)
- $(jqobj)等同于jqobj自身
所以jQuery的构造方法接受两个参数:选择器selector, 以及上下文context。
通过以上几种情况分析就知道,有时候context是没有传进构造函数的。
而selector这个参数有多重身份,在大多数的情况下,它是一个字符串,但是它也可以是DOM节点对象,也可以是一个函数,还可以是一个jQ对象
所以构造器的源码的结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | init = function(selector, context){ if (isDOM(selector)){//如果是DOM节点 //then $(dom) return this; } if (isString(selector)){//如果是字符串 if (isHTML(selector)){ //使用:jQuery.parseHTML(selector, context, true); return xxx; }else if(isID(selector)){ //对#id做特殊处理,加快效率 //document.getElementById(selector) return xxx; }else{ //其他情况就是比较复杂的CSS选择器了 //通过find方法去选取符合规则的DOM节点 return context.find( selector ); } }else if(isFunction(selector)){//如果是函数 //$(fn) //then $(document).ready(fn) } //其他情况认为是jQ对象了,但是也有可能是使用者一些非法的用法 //else is $(jqobj) return jQuery.makeArray(selector, this); } |
可以看到,jQuery构造方法并不难看懂,这里就不选择粘贴源码出来。
在构造方法中可以看到一个比较特殊的find方法,这个方法是用来对付比较复杂的CSS选择器的情况。
jQuery在1.3版本后把这个CSS选择器引擎分割成另一个开源项目,命名为Sizzle。
有关Sizzle的内容,在之后的文章再剖析。
至此,jQuery的core.js已经剖析完毕。截止到目前已经写了6篇文章来剖析基础框架部分,不知道这几篇文章能让别人理解到什么程度,对于自己来说,写文章的过程中让我琢磨透很多东西,所以无论如何,以后应该坚持思考坚持书写,欢迎给予意见交流。
本文链接:jQuery源码剖析(六)——类数组
转载声明:本博客文章若无特别说明,皆为原创,转载请注明来源:拉风的博客,谢谢!