选择器部分的代码实在很复杂,过后再看jQuery.init用到了一些实例方法,因此先看一下实例方法再回过头看init
源码中jQuery构造函数定义完之后添加的实例成员有:
jquery size get each index attr css text wrap append prepend before after end find clone filter not add is domManip pushStack
(1)jquery:存储当前jQuery的版本
(2)size:存储当前实力化对象的length属性,感觉应该是存放选取到的元素的个数
(3)get
get: function( num ) { if ( num && num.constructor == Array ) { this.length = 0; [].push.apply( this, num ); return this; } else return num == undefined ? jQuery.map( this, function(a){ return a } ) : this[num]; },
if里面很明显是数组的情况,刚开始实在搞不清楚什么时候能进这个分支
看了好久才发现在jQuery构造函数定义的时候里面调用get方法时会传入数组:
// Watch for when an array is passed in this.get( a.constructor == Array || a.length && !a.nodeType && a[0] != undefined && a[0].nodeType ? // Assume that it is an array of DOM Elements jQuery.merge( a, [] ) : // Find the matching elements and save them for later jQuery.find( a, c ) );
在这个if分支里面先初始化了length属性为0
接下来的这句有点意思:[].push.apply( this, num )
其实是把数组push方法内部的this强制改为了当前环境下的this
而apply的第二个参数是一个数组形式,所以把document放入数组中就是num
最后this就变成了
{ "0":document, "length":1 }
如果num传入的就是数字类型的话那就好办了
直接走else分支返回对应的对象
如果直接$(".div1").get()就会得到$(".div1")选中的元素的数组
(4)each 实际上是调用了静态方法$.each 第一个参数是选择器选中的元素
(5)index
index: function( obj ) { var pos = -1; this.each(function(i){ if ( this == obj ) pos = i; }); return pos; },
得到传入的obj在当前选中的元素集合中(this)的位置
由于each方法在定义的时候改变了其传入的参数中的this的指向,所以each的函数参数的函数体里面的this指向的是当前遍历的元素
(6)attr
attr: function( key, value, type ) { // Check to see if we're setting style values return key.constructor != String || value != undefined ? this.each(function(){ // See if we're setting a hash of styles if ( value == undefined ) // Set all the styles for ( var prop in key ) jQuery.attr( type ? this.style : this, prop, key[prop] ); // See if we're setting a single key/value style else jQuery.attr( type ? this.style : this, key, value ); }) : // Look for the case where we're accessing a style value jQuery[ type || "attr" ]( this[0], key ); },
这个方法的用法貌似有以下几种
$(".div").attr("abc") //设置有.div类的abc属性 //直接走else 而且type不存在所以就调用jQuery.attr($(".div")[0],"key")得到其key属性
$(".div").attr("abc","123") //设置有.div类的abc属性值为"123" //走if 内层判断走else 设置单个属性值
$(".div").attr({"abc":"123","index":"456"}) //设置多个属性 //走if 内层判断走if 设置多个属性值
此外在源码中还发现一种用法:
$(".div").attr("style","100px; height:100px; background:red;","curCSS") //设置style属性 走if 内层判断走else 设置多个样式值
而且貌似最后一个参数type和设置style属性有关
(7)css 直接调用了attr方法
(8)text
text: function(e) { e = e || this; var t = ""; for ( var j = 0; j < e.length; j++ ) { var r = e[j].childNodes; for ( var i = 0; i < r.length; i++ ) t += r[i].nodeType != 1 ? r[i].nodeValue : jQuery.fn.text([ r[i] ]); } return t; },
这个text方法貌似只能获取一个元素里面的值而不能设置
目测参数e得是类数组对象,而且这个数组对象里面的每一项都是DOM元素
所以希望通过$("#div .div").text(123)来将#div下的.div标签里面的值改为123是不太可能的
如果HTML是
<div id="div1"> <div class="div1">abc</div> </div>
那通过$("#div1").text()得到的将是#div1里面所有文本节点拼接起来的字符串
但是个人感觉一般来讲,这个字符串没什么太大意义
(9)wrap
wrap: function() { var a = jQuery.clean(arguments); return this.each(function(){ var b = a[0].cloneNode(true); this.parentNode.insertBefore( b, this ); while ( b.firstChild ) b = b.firstChild; b.appendChild( this ); }); },
wrap方法貌似是将选择器选到的元素都用传入的DOM对象包围
例如,HTML是
<div id="div1"> <div class="div1"></div> <div class="div1"></div> <div class="div1"></div> <div class="div1"></div> </div> <div class="div2"></div>
执行$("#div1 .div1").wrap($(".div2"));之后就得到了
<div id="div1"> <div class="div2"><div class="div1"></div></div> <div class="div2"><div class="div1"></div></div> <div class="div2"><div class="div1"></div></div> <div class="div2"><div class="div1"></div></div> </div> <div class="div2"></div>
由于wrap内部调用了$.clean来处理传进来的参数
因此$.clean能处理的参数形式wrap也能处理
但是传进来的参数一定不能是带有文本节点的,即不能传进来类似
<div> abc </div>
因为wrap内部拿到这个DOM参数对象之后会一直遍历到这个DOM元素的最里层
在最里层append调用wrap方法的选择器中的对象
如果传入文本节点,肯定没办法append了
(10)domManip append prepend before after
append prepend before after都是调用了domManip,所以这是一个核心方法
domManip: function(args, table, dir, fn){ var clone = this.size() > 1; var a = jQuery.clean(args); return this.each(function(){ var obj = this; if ( table && this.nodeName == "TABLE" && a[0].nodeName != "THEAD" ) { var tbody = this.getElementsByTagName("tbody"); if ( !tbody.length ) { obj = document.createElement("tbody"); this.appendChild( obj ); } else obj = tbody[0]; } for ( var i = ( dir < 0 ? a.length - 1 : 0 ); i != ( dir < 0 ? dir : a.length ); i += dir ) { fn.apply( obj, [ clone ? a[i].cloneNode(true) : a[i] ] ); } }); },
再看它的调用形式
append: function() { return this.domManip(arguments, true, 1, function(a){ this.appendChild( a ); }); },
参数arguments应该是一个形如["<div><div>"]或者[oDiv]或者[$("#div")]的数组
再通过clean方法的处理变成一个纯粹的数组
第二个参数true/false代表是否对<table>做处理
dir代表domManip里面的循环是正序还是倒序
最后一个参数function的话是对当前调用domManip的this实例化对象选择到的元素集合的各项做的操作
从append里面调用domManip的最后一个参数的情况来看
这个函数将来在调用的时候一定会通过apply或call改变其内部this的指向
因为这个function直接调用的话,this必然指向window
但是window是没有appendChild这个方法的
因此this的指向一定会改成遍历到的DOM对象
再看domManip的源码
这个domManip的第一行就让我废了半天劲去看clone这个变量到底有什么用
结果也没有发现,目前猜测是为了兼容低版本浏览器
因为clone里面存储的是要执行append的jQuery对象选择到的元素的长度
而这个clone变量到了最后遍历数组a,将a中的每个元素都添加到每个元素下面的时候才用到
如果clone是true的话(即选择器至少2个元素)就将待添加的节点新克隆一个再append进去
如果clone是false的话(即选择器只有1个元素)就将待添加的节点直接append进去
经过测试没什么区别
中间对args中的表格元素的情况作了处理
再回头来看append和prepend
append内部调用的是appendChild方法,是直接往后面添加的
而prepend内部调用的是insertBefore方法,是往最前面添加的
挨个添加时append顺序添加就可以了,所以参数dir为1
而prepend的时候要想按顺序添加上去就需要先将最后一个元素insertBefore到最前面
再将倒数第二个元素insertBefore到最前面
以此类推,知道添加完
before和after也是差不多的
(11)end 字面上看起来好像是获取选择器中所有元素的最后一个元素,不过直接调用的话会报错,从源码中也很容易看出错误的原因:this.stack没有值
所以目测end方法是要和其他方法配合使用
(12)pushStack
pushStack是实例化方法里面继domManip之后的另一个重要的核心方法
pushStack: function(a,args) { var fn = args && args[args.length-1]; if ( !fn || fn.constructor != Function ) { if ( !this.stack ) this.stack = []; this.stack.push( this.get() ); this.get( a ); } else { var old = this.get(); this.get( a ); if ( fn.constructor == Function ) return this.each( fn ); this.get( old ); } return this; }
分析了一下pushStack的调用方式,大概见到以下几种:
find方法中:this.pushStack([oDiv],[".div1"]);
clone方法中:this.pushStack([oDiv],[true]);
filter方法中:this.pushStack([oDiv]);
not方法中:this.pushStack([oDiv],".div1");以及this.pushStack([oDiv],document.getElementById("div8"));
add方法中:this.pushStack([oDiv],[[$("#div1"),$("#div2")]]);
通过以上分析,发现pushStack的功能正如它的名字——入栈
每次在一个jQuery对象上进行add find filter等操作时,都会把当前的jQuery对象push到stack属性中
stack属性值中的最后一项就是最近一次操作的效果
不过里面调用的this.get(0)和this.get(a)不太清楚是做什么的,而且经过this.get(0)这么一处理,上面的not调用方式也是有问题的
从pushStack这个方法的else分支中发现貌似第二个参数还可以传入函数
但是这种方式目前还没有见过在哪里用到过
至此为止的话,最开始初始化的这些实例化方法基本上都分析的差不多了