title | date | categories | tags | |
---|---|---|---|---|
jQuery源码分析---96至283 |
2017-08-01 15:00:40 -0700 |
前端 |
|
<Excerpt in index | 首页摘要> jQuery源码分析---96至283
<The rest of contents | 余下全文>
给jQuery对象,添加一些常用的属性和方法
jQuery.fn=jquery.prototype={
jquery:版本,
constructor:修正指向问题,
init():初始化和参数管理,
110-177 处理字符串,包含下面情况:
$("#div1"),$(".box"),$("div") $("#div1 div.box")
$("li") $("<li>1</li><li>2</li>")
120-167 if 语句,创建标签和id的形式
123-150:在次进行if,创建标签
150-164:上面if对应的else,就是对应的id的形式
167-172 else if
172-177 else
177-184
184-186
selector:存储选择字符串,
length:this对象的长度,
toArray():转数组,
get():改原生集合,
pushStact():JQ对象入栈,
each():遍历集合,
ready():DOM加载的接口,
slice():集合的截取,
first():集合的第一项,
last():集合的最后一项,
eq():集合的指定项目,
map():返回新集合,
end():返回集合前一个状态,
push():内部使用,
sort():内部使用,
slice():内部使用
};
上面,属性都没有加括号,方法都加了括号
版本就是指向我们上面的那个变量(#49)
alert($().jquery);
//2.0.3
constructor在面向对象的中就是创建出来的对象下面所拥有的一个属性,这个属性指向的就是这个对象所属的构造函数是谁
eg:
function Aaa(){};
var a1=new Aaa();
alert(a1.constructor);
//function Aaa(){}
a1.constructor指向的是他的构造函数Aaa。在js源码中会自动生成一个
Aaa.prototype.constructor=Aaa;
所以在我们调用的时候是可以找到他的。那么是自动生成的,那么为什么jQuery中还要手动的指向一下呢?那是因为他的写法中,把指向给改了。
function Aaa(){};
var a1=new Aaa();
//如果强制更改他的constructor指向
Aaa.prototype.constructor=Array;
alert(a1.constructor);
//加了强制指向:function Array() { [native code] }
这说明constructor属性很容易被修改掉,所以有些特殊情况我们需要修复一下,比如:
Aaa.prototype.name="jarry";
Aaa.prototype.age=20;
//因为Aaa.prototype本身就是一个对象,所以我们也可以用对象的写法,如下
Aaa.prototype={
"name":"jarry",
"age":12
};
//在大部分情况下,上面的两种写法都是没有区别的,但是在面向对象的过程中是有区别的
prototype两种不同的挂载方法的区别
- 第一种,直接等号挂载,实例的返回的constructor就是Aaa
function Aaa(){};
Aaa.prototype.name="jarry";
Aaa.prototype.age=20;
var a1=new Aaa();
alert(a1.constructor);
//function Aaa(){}
- 第二种:使用prototype对象挂载,实例返回的constructor是Objcet!
function Aaa(){};
Aaa.prototype={
"name":"jarry",
"age":12
};
var a1=new Aaa();
alert(a1.constructor);
//function Object() { [native code] }
第一种写法,name和age都是往原型上进行添加处理,所以他默认的Aaa.prototype.constructor=Aaa
,不会有任何的影响。
第二种不是添加,而是他是一个json进行覆盖操作,这个json对象直接把原来的prototype给覆盖了。所以现在的constructor指向的肯定是那个给定的json所对应的构造函数。
基于上面的不同情况,所以我们在平时使用的时候还需要把他的指向给修正过来,否则使用的时候肯定出问题
function Aaa(){};
Aaa.prototype={
constructor:Aaa,
"name":"jarry",
"age":12
};
var a1=new Aaa();
alert(a1.constructor);
//function Aaa(){}
基于以上的原因,jQuery源码中prototype方法挂载也是用的第二种,所以就有了第100行的代码写法
初始化和参数的一个管理 对外提供接口就是$()或者jQuery(),其实他俩是同一个函数,$就是jQuery的简写方式,最终调用的都是(#61)那个函数,但是(#61),那里真正的构造函数是init,#61如下:
jQuery = function( selector, context ) {
// The jQuery object is actually just the init constructor 'enhanced'
return new jQuery.fn.init( selector, context, rootjQuery );
}
可以看到对外接口传进来的参数就是一个元素一个作用域,最后都跑到了init里面了,
这里面初始化的时候selector有如下情况
$() jQuery()
$('li','ul')
$('') $(null) $(undefined) $(false)
$("<li>") $("<li>2</li><li>3</li>)
$("#div1") $(".box") $("div") $("#div1 div.box")
$(this) $(document)
$(function(){})
$([]) $({})
$("li")
获取的是页面中所有的li,而$("li","ul")
是获取的ul下面的li,第二个元素就是前面第一个元素的限制条件。
init可以对这写类型进行一些简单的分配,然后在分别进行处理
当你写错,让他直接返回,不让他往下继续执行了,这里的this就是对象,这样他也不会报错
比如$("#div1"),$(".box"),$("div")或者是一些更复杂的选择,比如$("#main div.div1")
$(function(){
//$("li").css("background","#f00");
//将上面代码分解一下用原生去写
var aLi=document.getElementsByTagName("li");
for(var i=0;i<aLi.length;i++){
aLi[i].style.background="red";
};
//jQuery中
this={
0:"li",
1:"li",
2:"li",
length:3,
};
for(var i=0;i<this.length;this++){
this[i].style.background="red";
};
})
还有:$("<li>")
:创建一个标签,所以说字符串除了被选以外还可以被创建,还可以创建更复杂的,比如$("<li>1</li><li>a</li>)
$(function(){
//用Firefox打开这个页面,就可以看到效果
console.log($("li").css("background","#f00"));
})
$(function(){
$("li")[1].style.background="red";
})
在我们前面说过,在96-283简化版本中有一个length:this对象的长度,
$("#div1"),$(".box"),$("div") $("#div1 div.box")
$("li") $("<li>1</li><li>2</li>")
首先判断一下,最左边的字符是不是<,然后又判断了一下最右边的字符是不是>,并且长度>=3,其实就是去找标签
//li会被添加进入ul,但是hello不会,相当于$("<li>").appendTo($("ul"));
//$("<li>hello").appendTo($("ul"));
//这样写li和hello都会添加进入ul
$("<li>hello</li>").appendTo($("ul"));
if(){
//$("li") $("<li>1</li><li>2</li>")
}else{
//$("#div1"),$(".box"),$("div") $("#div1 div.box")
//注意下面这种情况
//$("<li>hello")
}
-
如果是单标签,就是if里面的第一种情况 match=[null,"
- ",null];
-
如果是多标签,就是if里面的第二种 match=[null,
- 1
- 2 ,null]
匹配正则 rquickExpr (#75),其实就是标签有没有加文字,就是113里面的,$("<li>hello")
,这种情况
- (<[\w\W]+>):匹配标签
- [^>]:后面是文字
匹配正则 rquickExpr (#75),或者是id的情况,
$("#div1")
- #([\w-]*) 或上ID的形式
根据#116的写法match = rquickExpr.exec( selector );
-
$("#div1"):
match=["#div1",null,"div1"];
-
$("
- hello"):
match=["<li>hello","<li>",null];
-
但是依照我们113写的,还有一种情况没有匹配到,如下:
$("div") $("#div1 div.box")
所以他返回的就是null
if ( match && (match[1] || !context) ) {
能走上面的那个if的
//$("li") $("<li>1</li><li>2</li>")
//$("#div1")
也就是能进这个if的都是创建标签或者id的形式
!context:的意思是没有上下文
进入if的是选择标签的时候$("<li>")
看一下执行的上下文,instanceof jQuery是否为真。创建一个标签为什么会有执行上下文?
eg:
平时创建标签$("<li>")
,对应到原生就是document.createElement("li")
.那么$("<li>",第二个参数)
里面的第二个参数只能是document,即$("<li>",document)
,所以说第二个参数的作用就是在指定的不同的环境下找到根节点。如果不写document,比如$("<li>")
,他就是在当前页面创建li标签;也可以写上document,即$("<li>",document)
,他的意思还是在当前页面创建li标签。但是你也可以写成别的,比如iframe的形式$("<li>",contentWindow.document)
。这样就可以找到iframe当中的document,这样写的时候,li是在对应的iframe里面创建的。jQuery中确实是这么做的,但是用处不大。
打开jQuery官网,打开API Documentation页面,在search里面搜索jQuery(),找到如下如所示的区域,点击展开 http://api.jquery.com/jQuery/ 如上图所示就能找到我们的
这里为什么要判断一下instance of jjQuery?
context = context instanceof jQuery ? context[0] : context;
原因在于
$("<li>",documentWindow.document)
第二个参数可能直接写成原生的
$("<li>",document)
如果写成原生的,document instance of jQuery
肯定是假的,然后直接就返回了context,这里是原始的document。但是有时候可能这么写
$("<li>",$(document))
这时候context instanceof jQuery
返回的就是真了,即context=context[0]
,这时候的context本身就是$(document)
,但是我们要的是原生的,所以在里面选择一个属性是0的那个原生的document。反正最后得到的都是原生的。
补充知识: contentWindow.document
$("<li>1</li><li>2</li>").appendTo('ul')
通过
$("<li>1</li><li>2</li>")
我们想得到
this={
0:'li',
1:'li',
length:2
}
所以不管是单标签$("<li>")
还是多标签$("<li>1</li><li>2</li>")
最后都是走的是127那一行的parseHTML.你要对jQuery.parseHTML这个方法有一定的了解
jQuery.parseHTML这个方法就是把一个字符串转成节点数组,
举个例子
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="../jquery.js"></script>
<script>
$(function(){
var str='<li>1</li><li>2</li><li>3</li>';
var arr=jQuery.parseHTML(str);
console.log(arr);
$.each(arr,function(i){
$('ul').append(arr[i]);
})
})
</script>
<title>Document</title>
</head>
<body>
<ul></ul>
</body>
</html>
就是指定根节点,可以是当前的页面也可以是iframe的页面
jQuery.parseHTML(str,document)
那么true和false有什么关系呢? 在默认情况下,是false
var str='<li>1</li><li>2</li><li>3</li><script>alert("1")</script>';
**注意:**在字符串中,其他的标签的闭合标签都可以写/
,但是唯独</script>
标签的反斜杠不能直接写,需要转义一下<\/script>
这第三个参数就是允不允许<script>
标签添加,默认情况下(false)是添加不进去的,如果你把第三个参数改为true,这时候就可以弹出1的,如下代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="../jquery.js"></script>
<script>
$(function(){
var str='<li>1</li><li>2</li><li>3</li><script>alert("哈哈哈")<\/script>';
var arr=jQuery.parseHTML(str,document,true);
console.log(arr);
$.each(arr,function(i){
$('ul').append(arr[i]);
})
})
</script>
<title>Document</title>
</head>
<body>
<ul></ul>
</body>
</html>
jQuery.parseHTML操作最终返回的是一个数组,但是我们最后要的是一个json而不是数组,这时候就用到啦127行jQuery.merge,两个数组合并到一个数组中
$(function(){
var arr1=["s","b","c"],
arr2=["d","c","m"];
var arr=$.merge(arr1,arr2);
console.log(arr);
//Array [ "s", "b", "c", "d", "c", "m" ]
})
$.merge对外就是对数组进行合并,但是对内,他还可以合并json,但是有特定条件,如下代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="../jquery.js"></script>
<script>
$(function(){
var arr1={
0:'a',
1:'b',
length:2
},
arr2=["d","c","m"];
var arr=$.merge(arr1,arr2);
console.log(arr);
//firefox中:Object [ "a", "b", "d", "c", "m" ]
//chrome:Object {0: "a", 1: "b", 2: "d", 3: "c", 4: "m", length: 5}
})
</script>
<title>Document</title>
</head>
<body>
<ul></ul>
</body>
</html>
综上所述127-131得到的就是如下的结果
this={
0:'li',
1:'li',
length:2
}
这一部分处理的是如下的形式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="../jquery.js"></script>
<script>
$(function(){
$("<li>",{title:"hi",html:"abcd"}).appendTo('ul');
})
</script>
<title>Document</title>
</head>
<body>
<ul></ul>
</body>
</html>
首先进行了一个正则的判断 rsingleTag.test
, rsingleTag
这个正则的意思是匹配单标签,单标签的意思就是他只能匹配成功$('<li>')
,或者是这种$('<li></li>')
,除了这两种以外,剩下的都不行,比如说两个的时候。$('<li></li><li></li>')
,可以试一下
$(function(){
$("<li></li><li></li>",{title:"hi",html:"abcd"}).appendTo('ul');
})
可以看到li标签是创建成功了,但是{}里面的东西没有创建成功
jQuery.isPlainObject( context )
我们可以看到上面那个例子,第二个参数确实是一个字面量形式的参数
{title:"hi",html:"abcd"}
进入if以后,进行了一个for...in...循环,然后里面又是一个if...else..
if ( jQuery.isFunction( this[ match ] ) )
isFunction
判断他是不是函数,此时的this是{title:"hi",html:"abcd"}
,match就是title或者html.咱们的jQuery里面有html()
这个方法,但是没有title的方法,所以此时html进入if里面,然后进行函数调用处理this[ match ]( context[ match ] );
,所以你在里面写html:"abcd"
,相当于this.html("abcd")
else就是给他加属性,就像上面的title:"hi"
加属性,如上
字符串里面只说了标签
$("<li>") $("<li>2</li><li>3</li>)
但是还有好多没有处理的,如
$() jQuery()
$('li','ul')
$('') $(null) $(undefined) $(false)
$("#div1") $(".box") $("div") $("#div1 div.box")
$(this) $(document)
$(function(){})
$([]) $({})
进入else的时候是选择id的时候$("#div1")
通过原生的getElementById来获取id元素,这里的match[2],就是match=["#div1",null,"div1"];
里面的角标为2的,正好就是div1
一般的情况下,所有的浏览器去判断元素存不存在就知道我们的获取的id对不对,但是有一种特殊的情况,就是在黑莓46的系统下,光判断elem是不靠谱的,可能这个元素 不在页面上,但是if(elem)还是能找到,所以又加了一个如果这个elem存在,并且他有父级,那他一定是存在的;相反的如果一个元素不存在,他肯定是没有父级的
前面说过,jQuery选择元素的时候,是存成了一个json的形式,并不是一个数组,这样的话是没有长度的,所以说必须手动的给他加一个length,并且里面的第一项存的 就是我们的elem
这是的上下文就是document
这时候的select就是我们容器选择到的字符串,比如#div1
返回这个对象,不需要在往后去查找了
判断$(expr, $(...))
,当这个执行上下文不存在的时候!context
,下面的context就是不存在,或一下rootjQuery
,所以( context || rootjQuery )
里面的值就是rootjQuery,rootjQuery就是$(document)
,find就是在指定的节点下面进行筛选,比如
$(document).find('ul li.box')
在复杂的一些选择其实都是通过find来实现的,find最后调用的就是sizzle
当context存在上线文的时候,他就会接着走context.jquery
,这时候就是看一下上下文是不是jQuery对象,如果是jQuery对象,就接着往后走,如果不是就到了172行
上面有点绕,详细一些
$('ul',document).find('li'); document是原生的,这是的167行`context.jquery`,上下文是找不到jQuery属性的,因为属性是在jQuery对象下面的,这时候就会走172的else,这个else里面的`this.constructor`就是jQuery,context就是document,综上,这段代码就相当于
jQuery(document).find();
//或者
$('ul',$(document)).find('li'); 这句话的document是jQuery选择器下面的,167行的`context.jquery`就会为真,就会走168的rootjQuery,我们上面说了,rootjQuery就是`$(document)`,综上,这段代码相当于
···
jQuery(document).find();
$('ul').find('li')在167没有上下文的时候,就是!context
,由于context不存在,所以168的时候就是走的rootjQuery
说白了就是find要求他前面必须是一个jQuery对象
$('ul',document).find('li'); document是原生的,这是的167行`context.jquery`,上下文是找不到jQuery属性的,因为属性是在jQuery对象下面的,这时候就会走172的else,这个else里面的`this.constructor`就是jQuery,context就是document,综上,这段代码就相当于
jQuery(document).find();
#### 200 默认length的长度是0
#### 177 对dom元素直接进行处理
比如`$(this)`,或者`$(document)`
#### 184 处理传函数的情况
比如`$(function(){})`
#### 193 处理传数组或者json的情况
例如`$([])`或者`$({})`
11