选择器部分的代码实在很复杂,过后再看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","width: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分支中发现貌似第二个参数还可以传入函数
但是这种方式目前还没有见过在哪里用到过
至此为止的话,最开始初始化的这些实例化方法基本上都分析的差不多了