一.3-jQuery.prototype对象中的属性与方法(上)
Posted re大法好
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一.3-jQuery.prototype对象中的属性与方法(上)相关的知识,希望对你有一定的参考价值。
源码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标签 $("<div></div>"); // 创建元素 $("li", $(".class1")); // 选择所有的li标签,这个li标签必须在.class1标签下
可以看到init方法中有大量的if判断(毕竟要完成大量的判断呢), 我们先整理下吧。写个简版吧。
jQuery源码中也给每一种的情况写了注释,在jQuery源码算是少见的注释了。
情况一 // 容错处理 $(""), $(null), $(undefined), $(false) if (!selector) { return this; } 情况二 // 字符串的情况: 如选择器$("div"), $("div p") , 创建元素, $("<li></li>"),$("<ul><li></li></ul>") if (typeof selector === "string"){ 情况三 // $(this), $(document) }else if(selector.nodeType){ 情况四 // $(function(){}) }else if(jQuery.isFunction(selector){ } if (){}
情况一:
这是容错处理,就算用户乱输入,也尽量不报错
情况二:
情况二比较复杂,因为我们不但能选择元素,还能创建元素。
选择器可能是复杂选择器(如"div p"),也可能是简单选择器(如"div").
创建的元素可能是单个元素,也可能是多个元素。因此采取的策略都不太一样。
// 111-117
if (selector.charAt(0) === "<" && selector.charAt(selector.length - 1) === ">" && selector.length >= 3){ // 创建元素 情况1
match = [null, selector, null];
}else{ // 选择器 情况2
match = rquickExpr.exec(selector);
}
这个if判断还是很长的。首先这个字符串的第一个字符得是 <, 最后一个字符得是 >, 其次这个选择器的长度得大于等于3.
这么一看, 那便是创建 元素的情况。 $("<li></li>")和$("<li></li><li></li>")
有人可能会问了,假如我传入了这种的 $("<li>123")。jQuery会怎么处理?
这种情况肯定是走else了,放心,在else中会针对这种情况来进行处理的。
else语句中的rquickExpr是啥?
那是一个正则表达式(75行)
rquickExpr = /^(?:s*(<[wW]+>)[^>]*|#([w-]*))$/,
源码也有详细的解释
// A simple way to check for html strings // Prioritize #id over <tag> to avoid XSS via location.hash (#9521) // Strict HTML recognition (#11290: must start with <)
我来稍微说一个这个正则表达式吧。(如果看明白了这个正则表达式,也就知道了match的结果了)
rquickExpr = /^(?:s*(<[wW]+>)[^>]*|#([w-]*))$/ ^必须是什么什么开始 $ 必须以什么什么结束 (?:s*(<[wW]+>)[^>]*|#([w-]*)) 这是为啥子要包裹一下括号,而且还有?: 这是非捕获型分组,主要是提高性能用的。 s*(<[wW]+>)[^>]*|#([w-]*) s (space)是空格的意思。 fvx20 等都可以被认为是空格 *代表可以重复0次或多次。*可以联想到天上的星星。有时候没有,而有时候数都数的不过来 注意到中间有一个管道符,这代表或的意思。 情况一 (<[wW]+>)[^>]* 情况二 #([w-]*)) 情况一 (<[wW]+>)[^>]* 注意分组内容 <[wW]+> 这个正则很有意思 必须以< 开头,中间的字符任意(包括空格), 个数大于等于1。 [^>]* 代表除了^之外,啥字符都能匹配的上,匹配次数是0次或者多次 情况二 #([w-]*) 这是匹配id选择器用的 第一个字符得是 # [w-]的字符范围比较广,0到9,a到z,A到Z以及字符-。
下面是情况1和情况2的总结
情况1总结
可能的情况: $("<li></li>") 和 $("<li></li><li></li>")
match的结果:
1. $("<li></li>") [null, ‘<li></li>‘, null] 2. $("<li></li><li></li>") [null, ‘<li></li><li></li>‘, null]
情况2总结
可能的情况: $("#main") $("div p") $("<li>123")
match的结果:
rquickExpr = /^(?:s*(<[wW]+>)[^>]*|#([w-]*))$/ 1. $("#main") ["#main", undefined, "main"] 2. $("div p") // 对,没错,选择器其实不在这里做处理 null 3. $("<li>123") [‘<li>123‘, ‘<li>‘, null]
前面的代码只是做了预处理,分类了字符串的各种情况
// 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字符串的,如<li></li>, <li></li><li></li>
返回一个数组,数组中是创建好的单个标签。
$.parseHTML("<li></li>") // [li] 注意数组中的元素是node节点!!!! $.parseHTML("<li></li><li></li>") // [li, li] $.parseHTML("<ul><li></li><li></li></ul>") // [ul]
$.parseHTML("<li></li><li></li><script>alert(1)</script>") // [li, li]
$.parseHTML("<li></li><li></li><script>alert(1)</script>", document, true) // [li, li, script]
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对象中, 举例子
$("<li></li>")
这时this对象会长成这个样子
{ 0: li }
第三个参数是true,这样script标签就能创建了
3.135-146行
这一部分是针对这样创建标签的
$("<p></p>", {title: 111, html: 123}}).appendTo($(document.body));
// <p title="111">123</p>
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 = /^<(w+)s*/?>(?:</1>|)$/ 1 是反向引用,这里的字符得和第一个分组的结果一样,即这部分 (w+) 如果是这样的html字符 <li></ul>, 那么就不满足要求
然后for遍历这个对象,将属性和属性值放到这个创建好的this对象上
这里还有一个判断
if (jQuery.isFunction(this[match]))
这主要是针对我们使用html,text,css属性的,一但使用这些属性,就会调用jQuery的上的对应的方法
$("<li></li>", {html: 123}); // 会调用 this.html(123) ,注意这里this中的内容
如果不是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节点
$(document.getElementById("main"))
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对象中的属性与方法(上)的主要内容,如果未能解决你的问题,请参考以下文章