一.3-jQuery.prototype对象中的属性与方法(上)
2021-03-05 09:29
标签:context xpl 简单 索引 body 是什么 长度 eth core 123 $(document.getElementById("main")) 一.3-jQuery.prototype对象中的属性与方法(上) 标签:context xpl 简单 索引 body 是什么 长度 eth core 原文地址:https://www.cnblogs.com/re-is-good/p/12906215.html源码96-280行
前言
上一篇说到了jQuery的extend方法,它是用来给jQuery函数扩展方法或者给jQuery的原型上加方法。
其实上,jQuery的原型上还是有一些属性和方法的。其中就有我们熟悉的init方法。
当我们调用 $("li") 时,new的便是jQuery.prototype.init构造函数
源码
首先源码比较长,我先写一个简版的
jQuery.fn = jQuery.prototype = {
jquery: core_version, // 49行 core_version = "2.0.3"
constructor: jQuery,
init: function(){},
selector: "",
length: 0,
toArray: function(){},
get: function(){},
pushStack: function(){},
each: function(){},
ready: function(){},
slice: function(){},
first: function(){},
last: function(){},
eq: function(){},
map: function(){},
end: function(){},
push: core_push,
// 其实这个方法就是Array.prototype.push。这样可以方便压缩代码。见53行及47行
sort: [].sort,
splice: [].splice
}
可以看到还是有很多属性和方法的。有些实现比较简单,比如最后三个(push, sort,splice),有些很复杂,如init方法
1.jquery: core_version
获取jQuery的版本
console.log($().jquery); // "2.0.3"
有用处吗?
有,有些库是需要特定版本的jQuery的(有可能版本特别低),他的检测方法基本上就通过这种方法的。嘿嘿,只要你改一下这个,可能这个库就能用了。(我试过)
为啥子不换jQuery呢? 因为麻烦。哈哈(也不麻烦,jQuery有防冲突的机制,可以实现一个页面多个版本的jQuery库)
2.constructor: jQuery
重写下constructor的指向
3. init: function(){}
这个方法很重要,这个方法要需要处理的情况比较多。下面举些例子
$(function(){}); // 当dom加载完成后就调用此方法(我们自己写的代码都在这里面)
$("li"); // 选择所有的li标签
$(""); // 创建元素
$("li", $(".class1")); // 选择所有的li标签,这个li标签必须在.class1标签下
可以看到init方法中有大量的if判断(毕竟要完成大量的判断呢), 我们先整理下吧。写个简版吧。
jQuery源码中也给每一种的情况写了注释,在jQuery源码算是少见的注释了。
情况一
// 容错处理 $(""), $(null), $(undefined), $(false)
if (!selector) {
return this;
}
情况二
// 字符串的情况: 如选择器$("div"), $("div p") , 创建元素, $("
")
情况一:
这是容错处理,就算用户乱输入,也尽量不报错
情况二:
情况二比较复杂,因为我们不但能选择元素,还能创建元素。
选择器可能是复杂选择器(如"div p"),也可能是简单选择器(如"div").
创建的元素可能是单个元素,也可能是多个元素。因此采取的策略都不太一样。
// 111-117
if (selector.charAt(0) === "" && selector.length >= 3){ // 创建元素 情况1}else{ // 选择器 情况2
}
这个if判断还是很长的。首先这个字符串的第一个字符得是 , 其次这个选择器的长度得大于等于3.
这么一看, 那便是创建 元素的情况。 $("
有人可能会问了,假如我传入了这种的 $("
这种情况肯定是走else了,放心,在else中会针对这种情况来进行处理的。
else语句中的rquickExpr是啥?
那是一个正则表达式(75行)
rquickExpr = /^(?:\s*()[^>]*|#([\w-]*))$/,
源码也有详细的解释
// A simple way to check for HTML strings
// Prioritize #id over
我来稍微说一个这个正则表达式吧。(如果看明白了这个正则表达式,也就知道了match的结果了)
rquickExpr = /^(?:\s*()[^>]*|#([\w-]*))$/
^必须是什么什么开始
$ 必须以什么什么结束
(?:\s*()[^>]*|#([\w-]*))
这是为啥子要包裹一下括号,而且还有?:
这是非捕获型分组,主要是提高性能用的。
\s*()[^>]*|#([\w-]*)
\s (space)是空格的意思。\r\n\f\v\\x20 等都可以被认为是空格
*代表可以重复0次或多次。*可以联想到天上的星星。有时候没有,而有时候数都数的不过来
注意到中间有一个管道符,这代表或的意思。
情况一
()[^>]*
情况二
#([\w-]*))
情况一
()[^>]*
注意分组内容
这个正则很有意思
必须以 开头,中间的字符任意(包括空格), 个数大于等于1。
[^>]* 代表除了^之外,啥字符都能匹配的上,匹配次数是0次或者多次
情况二
#([\w-]*)
这是匹配id选择器用的
第一个字符得是 #
[\w-]的字符范围比较广,0到9,a到z,A到Z以及字符-。
下面是情况1和情况2的总结
情况1总结
可能的情况: $("
match的结果:
1. $("
情况2总结
可能的情况: $("#main") $("div p") $("
match的结果:
rquickExpr = /^(?:\s*()[^>]*|#([\w-]*))$/
1. $("#main")
["#main", undefined, "main"]
2. $("div p") // 对,没错,选择器其实不在这里做处理
null
3. $("
前面的代码只是做了预处理,分类了字符串的各种情况
// 120-174
if (match && (match[1] || !context)){
// 创建标签或者id选择器的情况
// id选择器的match的第二个元素为undefined,但是id选择器是没有context的。
// context是啥? $(“li”, $("div")) $的第二个参数便是context,但我们如果使用id选择器的话,是不会传入第二个参数来缩小上下文的。
// 因此id选择器也算一个
if (match[1]){
// 创建标签的情况,见下面
}else{
// id选择器的情况, 见下面
}
}else if (!context || context.jquery){
}else{
}创建标签的情况
1. 124行
// 124
context = context instanceof jQuery ? context[0] : context;
这个context便是我们在使用$ 时传入的第二个参数,用于缩小上下文。
$("li", $("div")); //此时context便是jQuery的实例,但是这玩意不能直接用的,因此要将jQuery转为dom。
// (因为jQuery实例是一种伪数组的形式,因此直接context[0])
2. 127行-131行
jQuery.merge(this, jQuery.parseHTML(
match[1],
context && context.nodeType ? context.ownerDocument || context : document,
true
));
这几行代码看起来比较复杂啊。
里面涉及了两个jQuery工具方法(通过上一节说的extend进行扩展的)
先说说这两个方法的使用吧
merge
var arr1 = [1,2];
var arr2 = [3,4];
$.merge(arr1, arr2);
console.log(arr1); // [1,2,3,4]
console.log(arr2); // [3,4]
var obj = {
0: 1,
1: 2,
length: 2
}
var arr3 = [3,4];
$.merge(obj, arr3);
console.log(obj); // {0:1, 1:2, 2:3,3:4, length: 4}
console.log(arr3); // [3,4]
通过上面的代码可以看到merge工具方法接受两个参数(实现中也是两个形参)
这个两个参数可以是数组,也可以是伪数组(为什子要支持伪数组,因为jQuery实例便是一个伪数组)
第一个参数会融合第二个参数的元素。因此第一个参数会被修改,第二个参数就不会了。(可参考上面的例子)
parseHTML
这是一个处理html字符串的,如
返回一个数组,数组中是创建好的单个标签。
$.parseHTML("
") // [ul]
$.parseHTML("
$.parseHTML("
parseHTML的第一个参数便是要解析的html字符串, 第二个参数是context,默认是document(大多情况是document,除非你想在iframe中操作)
第三个参数是一个布尔值。是否解析script标签,默认是不解析的。(为了安全考虑)
再来看看这几行代码
jQuery.merge(this, jQuery.parseHTML(
match[1],
context && context.nodeType ? context.ownerDocument || context : document,
true
));
context的确定
context && context.nodeType ? context.ownerDocument || context : document
提示下: ownerDocument是对于iframe的处理。iframe.ownerDocument 返回这个iframe的document。而document就没有这个属性。
merge 会将 创建好的元素融合到this对象中, 举例子
$("
这时this对象会长成这个样子
{
0: li
}
第三个参数是true,这样script标签就能创建了
3.135-146行
这一部分是针对这样创建标签的
$("", {title: 111, html: 123}}).appendTo($(document.body));
// if (rsingleTag.test(match[1]) && jQuery.isPlainObject(context)) {
for (match in context) {
// Properties of context are called as methods if possible
if (jQuery.isFunction(this[match])) {
this[match](context[match]);
// ...and otherwise set as attributes
} else {
this.attr(match, context[match]);
}
}
}
首先得满足这两个条件
要创建的标签得是单标签, 第二个参数得是对象字面量( {} 这个便是对象字面量)
说一下这个正则,涉及了反向引用
rsingleTag = /^(?:|)$/
\1 是反向引用,这里的字符得和第一个分组的结果一样,即这部分 (\w+)
如果是这样的html字符
然后for遍历这个对象,将属性和属性值放到这个创建好的this对象上
这里还有一个判断
if (jQuery.isFunction(this[match]))
这主要是针对我们使用html,text,css属性的,一但使用这些属性,就会调用jQuery的上的对应的方法
$("
如果不是jQuery上的方法,就直接添加了
4. 148行
return this;
返回this, 里面的是已经创建好的dom对象
id选择器的情况(152-164)
id选择器就比较简单了,毕竟浏览器原生支持id选择器(虽然IE有bug)
// 调用原生方法
elem = document.getElementById(match[2]);
// Check parentNode to catch when Blackberry 4.6 returns
// nodes that are no longer in the document #6963
// 通过这个索引(#6963)可以去jQuery官网查看更详细的说明
// 这里也写了原因,是因为黑莓4.6删除了node后,这个node还会存在。因为需要判断下这个node的父节点在不在
if (elem && elem.parentNode) {
// Inject the element directly into the jQuery object
this.length = 1;
this[0] = elem;
}
this.context = document;
this.selector = selector;
return this;
上面的一些属性(如selector, context)是不是比较熟悉呀
168行
else if (!context || context.jquery){
return (context || rootjQuery).find(selector);
}
// rootjQuery = jQuery(document); 见867行针对这种情况
$(‘li‘) // context 不存在 $(document).find(‘li‘)
$(‘li‘, $(‘div‘)) // $(‘div‘).find(‘li‘)
174行
对于复杂选择器的处理
return this.constructor(context).find(selector);
// $(context).find(selector);
这个方法要调用就是要Sizzle选择器了(源码很复杂,特别是1.8.3版本后引入了编译,2000行代码)
好了,对于字符串处理的部分终于结束了,好长呀。
情况三
178行
如果传入了node节点
else if(selector.nodeType){
this.context = this[0] = selector;
this.length = 1;
return this;
}情况四
186行
如果传入了一个函数
else if (jQuery.isFunction(selector)) {
return rootjQuery.ready(selector);
}
这个需要以后再说,这个形式是我们经常用的。
189-192行
如果写成这样子 $($("div")), 其实下面的代码就起作用了。
if (selector.selector !== undefined) {
this.selector = selector.selector;
this.context = selector.context;
}
$("div")的selector肯定不是undefined,因此需要重新赋值下。因此 $($("div"))的效果与$("div")相同
194行
return jQuery.makeArray(selector, this)
这个makeArray工具方法如果只有一个参数的话,将会伪数组转为数组。
两个参数的话,会将融合另一个属性,并返回这个伪数组
jQuery.makeArray(selector, {0: 1, length: 1})
返回这样的对象
0: 1
1: "111"
length: 2
文章标题:一.3-jQuery.prototype对象中的属性与方法(上)
文章链接:http://soscw.com/index.php/essay/60391.html