一.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-117if (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*(<[\w\W]+>)[^>]*|#([\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*(<[\w\W]+>)[^>]*|#([\w-]*))$/

^必须是什么什么开始
$ 必须以什么什么结束

(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))
这是为啥子要包裹一下括号,而且还有?:
这是非捕获型分组,主要是提高性能用的。

\s*(<[\w\W]+>)[^>]*|#([\w-]*)
\s (space)是空格的意思。\r\n\f\v\\x20 等都可以被认为是空格

*代表可以重复0次或多次。*可以联想到天上的星星。有时候没有,而有时候数都数的不过来

注意到中间有一个管道符,这代表或的意思。
情况一
(<[\w\W]+>)[^>]*

情况二
#([\w-]*))


情况一
(<[\w\W]+>)[^>]*
注意分组内容 <[\w\W]+>
这个正则很有意思 
必须以< 开头,中间的字符任意(包括空格), 个数大于等于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*(<[\w\W]+>)[^>]*|#([\w-]*))$/

1. $("#main") 
["#main", undefined, "main"]

2. $("div p")  // 对,没错,选择器其实不在这里做处理
null 

3. $("<li>123") 
[‘<li>123‘, ‘<li>‘, null]

前面的代码只是做了预处理,分类了字符串的各种情况

// 120-174if (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

相关推荐