Jquery源码分析与简单模拟实现

前言

  最近学习了一下jQuery源码,顺便总结一下,版本:v2.0.3

  主要是通过简单模拟实现jQuery的封装/调用、选择器、类级别扩展等。加深对js/Jquery的理解。

正文

先来说问题:

 1.jQuery为什么能使用$的方式调用,$是什么、$()又是什么、链式调用如何实现的

 2.jQuery的类级别的扩展内部是怎样实现的,方法级别的扩展有是怎样实现的,$.fn又是什么

 3.jQuery选择器是如何执行的,又是如何将结果包装并返回的

带着这些问题,我们进行jquery的模拟实现,文章下方有demo代码。

a.关于$ 

技术分享
1 //@spring:window:便于压缩,查找速度要快 undefined:ie7ie8是可以被修改如var undefined = 10;,为了防止外界改变
2 (function (window, undefined) {
3     var jQuery = {
4     };
5 
6     if (typeof window === "object" && typeof window.document === "object") {
7         window.jQuery = window.$ = jQuery;
8     }
9 }(window));
View Code

jquery用了个自执行方法封装了一下,传入window对象是为了便于压缩,相当于给了个临时变量,像jquery声明的以下变量也是这个作用

技术分享
 1 var
 2     // A central reference to the root jQuery(document)
 3     rootjQuery,//@spring:html文件的document节点
 4 
 5     // The deferred used on DOM ready
 6     readyList,//@spring:dom加载相关
 7 
 8     // Support: IE9
 9     // For `typeof xmlNode.method` instead of `xmlNode.method !== undefined`
10     core_strundefined = typeof undefined,//@spring:xmlnode判断的时候会产生bug,所以用typeof来判断
11 
12     // Use the correct document accordingly with window argument (sandbox)
13     location = window.location,//@spring:这些存储都是为了便于压缩操作,如location=window.location;location会压缩成i,l等
14     document = window.document,//@spring:同上
15     docElem = document.documentElement,//@spring:同上
16 
17     // Map over jQuery in case of overwrite
18     _jQuery = window.jQuery,
19 
20     // Map over the $ in case of overwrite
21     _$ = window.$,//冲突解决
22 
23     // [[Class]] -> type pairs
24     class2type = {},//类似两个字符串组成的[{‘[Object String]‘,‘[spring]‘}]
25 
26     // List of deleted data cache ids, so we can reuse them
27     core_deletedIds = [],
28 
29     core_version = "2.0.3",
30 
31     // Save a reference to some core methods
32     core_concat = core_deletedIds.concat,
33     core_push = core_deletedIds.push,
34     core_slice = core_deletedIds.slice,
35     core_indexOf = core_deletedIds.indexOf,
36     core_toString = class2type.toString,
37     core_hasOwn = class2type.hasOwnProperty,
38     core_trim = core_version.trim,
View Code

b.再看$()或者$("***"),也就是jquery的构造函数。先看jq源码

技术分享
1 // Define a local copy of jQuery
2     jQuery = function (selector, context) {
3         // The jQuery object is actually just the init constructor ‘enhanced‘
4         return new jQuery.fn.init( selector, context, rootjQuery );
5     },
View Code

selector:是个对象,最常见的就是字符串选择器,其他还有好多类型,下面会不断给出说明。

context:数据上下文,也就是个范围限定,平时用的少些。比如$(".highlight","#div1")就是找id为div1下面的所有class为highlight。不传就是document

new jQuery.fn.init( selector, context, rootjQuery ):使用jQuery.fn.init初始化构造jquery对象,jQuery.fn是啥,看源码截图:

技术分享

jQuery.fn就是jQuery.prototype,所以想想对象级别的扩展就是prototype下扩展方法而已。那么init也就是jquery下面的一个扩展方法了

讲到这里我们先模拟一下过程

技术分享
 1 (function (window, undefined) {
 2              var jQuery = function (selector) {
 3                  return new jQuery.fn.init(selector);
 4              };
 5              jQuery.fn = jQuery.prototype = {
 6                  jquery: "spring-1.0.js",//jquery版本
 7                  init: function (selector) {//context,rootjQuery不传了,稍微看看就懂了
 8                      console.log("" + selector + "进行处理");
 9                  }
10              }
11 
12              if (typeof window === "object" && typeof window.document === "object") {
13                  window.jQuery = window.$ = jQuery;
14              }
15          }(window));
16          $("input[name=‘age‘]");
View Code

技术分享

看下jq内部的init实现过程(已将详细实现代码剔除,只看结构)

 技术分享

看看Jquery选择器返回的数据结构。

技术分享

技术分享

技术分享

技术分享

啥都查不到时,jQuery.fn.jQuery.init[0],看起来像个数组。有个length就是查询到的数据长度。有个context 指向document,context 也就是上面所述的上下文(查找范围)

查找到数据时,更像个数组了。0/1是查到的元素,length是长度。在chrome输出台输出的也是个数组。挺奇怪的!

这些都很奇怪,而且更奇怪的是new jQuery.fn.init(selector),实例化的是init对象,init里面没有这些ajax/add/append/css等方法或属性,这些都是jquery的属性/方法。

_proto_是指向init的prototype的(关于_proto_是啥,每个对象初始化实例都会生成一个_proto_指向该对象的prototype。简单说下,其他的自行百度研究一下),却为啥会指向jQuery.prototype。

查一下jQuery源码,没啥玄虚,手动改指向。这样new了init对象,执行也查询方法,同时又指向了Jquery,这才有了$().各类方法。如下:

技术分享

前面一直说查询的元素像个数组,像个数组但不是数组,它是一个对象。怎么做的呢,我们把init方法模拟下一起说

技术分享
 1 //辅助:jquery合并数组的方法
 2         function merge(first, second) {
 3             var l = second.length,
 4                 i = first.length,
 5                 j = 0;
 6 
 7             if (typeof l === "number") {
 8                 for (; j < l; j++) {
 9                     first[i++] = second[j];
10                 }
11             } else {
12                 while (second[j] !== undefined) {
13                     first[i++] = second[j++];
14                 }
15             }
16 
17             first.length = i;
18 
19             return first;
20         }
21 
22 
23         (function (window, undefined) {
24             var core_version = "spring v.1",
25                 core_deletedIds = [],
26                 core_push = core_deletedIds.push,
27                 core_slice = core_deletedIds.slice;
28             var jQuery = function (selector) {
29                 return new jQuery.fn.init(selector);
30             };
31             jQuery.fn = jQuery.prototype = {
32                 jquery: core_version,//jquery版本
33                 constructor: jQuery,//覆盖构造函数防止被外部改变
34                 init: function (selector) {//context,rootjQuery不传了,稍微看看就懂了
35                     //针对不同参数类型进行不同处理方式,如果$("")$(null就直接返回)
36                     if (!selector) {
37                         //参数不对直接将this返回,想想现在this的值是什么,提示:new init();=>jQuery.fn.init[0]
38                         return this;
39                     } else {
40                         //如果是字符串juqery会调用查询方法进行查询dom元素(jquery调用sizzle专门进行dom解析)
41                         var nodes = document.getElementsByName("age");
42                         var arr = [];
43                         for (var i = 0; i < nodes.length; i++) {
44                             arr.push(nodes[i]);
45                         }
46                         //如果传递了Context上下文,则在context中寻找元素。这里指定位document
47                         this.context = document;
48                         //把selector存到jQuery中
49                         this.selector = selector;
50                         //jquery的合并方法,直接拿出来就能用,合并查询结果
51                         var result = merge(this, arr);
52                         //对处理过的this进行封装返回,注意为了链式调用,都需要返回this
53                         return result;
54                     }
55                 },
56                 selector: ""
57             }
58             jQuery.fn.init.prototype = jQuery.fn;
59             if (typeof window === "object" && typeof window.document === "object") {
60                 window.jQuery = window.$ = jQuery;
61             }
62         }(window));
63         $(".test");
View Code

其实代码里没啥东西都是模仿jquery的。不过就是简化一下,模仿一下。所以要先看结构这样才知道简化哪句,模仿那句。

看下结果:

技术分享

结果查出来了,但是不像数组啊,四不像的。init后面也没有个[]啊。

看下jQuery源码:

技术分享

关键代码就这这里,让对象像个数据加这几句就行了,我们来试试(完整的代码):

技术分享
 1 <input type="text" class="test" name="age" />
 2     <input type="text" class="test" name="Name" />
 3     <div class="test"></div>
 4     <script>
 5         //辅助:jquery合并数组的方法
 6         function merge(first, second) {
 7             var l = second.length,
 8                 i = first.length,
 9                 j = 0;
10 
11             if (typeof l === "number") {
12                 for (; j < l; j++) {
13                     first[i++] = second[j];
14                 }
15             } else {
16                 while (second[j] !== undefined) {
17                     first[i++] = second[j++];
18                 }
19             }
20 
21             first.length = i;
22 
23             return first;
24         }
25 
26 
27         (function (window, undefined) {
28             var core_version = "spring v.1",
29                 core_deletedIds = [],
30                 core_push = core_deletedIds.push,
31                 core_slice = core_deletedIds.slice;
32             var jQuery = function (selector) {
33                 return new jQuery.fn.init(selector);
34             };
35             jQuery.fn = jQuery.prototype = {
36                 jquery: core_version,//jquery版本
37                 constructor: jQuery,//覆盖构造函数防止被外部改变
38                 init: function (selector) {//context,rootjQuery不传了,稍微看看就懂了
39                     //针对不同参数类型进行不同处理方式,如果$("")$(null就直接返回)
40                     if (!selector) {
41                         //参数不对直接将this返回,想想现在this的值是什么,提示:new init();=>jQuery.fn.init[0]
42                         return this;
43                     } else {
44                         //如果是字符串juqery会调用查询方法进行查询dom元素(jquery调用sizzle专门进行dom解析)
45                         var nodes = document.getElementsByName(selector);
46                         var arr = [];
47                         for (var i = 0; i < nodes.length; i++) {
48                             arr.push(nodes[i]);
49                         }
50                         //如果传递了Context上下文,则在context中寻找元素。这里指定位document
51                         this.context = document;
52                         this[0] = document;
53                         //把selector存到jQuery中
54                         this.selector = selector;
55                         //jquery的合并方法,直接拿出来就能用,合并查询结果
56                         var result = merge(this, arr);
57                         //对处理过的this进行封装返回,注意为了链式调用,都需要返回this
58                         return result;
59                     }
60                 },
61                 selector: "",
62                 length: 0,
63                 toArray: function () {
64                     return core_slice.call(this);
65                 },
66                 get: function (num) {
67                     return num == null ?
68                         this.toArray() :
69                         (num < 0 ? this[this.length + num] : this[num]);
70                 },
71                 //这里要注意,想要长得像jquery.fn.jquery.init[0],并且init方法中的this值为数组就必须加下面这三个字段
72                 push: core_push,
73                 sort: [].sort,
74                 splice: [].splice
75             }
76             jQuery.fn.init.prototype = jQuery.fn;
77             if (typeof window === "object" && typeof window.document === "object") {
78                 window.jQuery = window.$ = jQuery;
79             }
80         }(window));
81         $("age");
82 
83     </script>
View Code

看看输出结果:

技术分享

恩恩,不错不错…挺像的。

这就是对选择器的简单模拟。其实jQuery也是调用Sizzle.js进行html元素解析的(牵涉许多,不多讲了,自己去查吧)

 

至于jQuery对象级别的扩展,简单模拟一个,其实就是jQuery.prototype.method扩展一个方法而已

//jquery对象级别的扩展插件,看看就明白是啥了
    jQuery.fn.css = function (className) {
        //注意this是一个对象,length值是手动赋予的
        for (var i = 0; i < this.length; i++) {
            var item = this[i];//通过下标找元素,this不是数组
            item.setAttribute("class", className);
        }
        return this;//链式调用返回this
    };

调用如:

技术分享

我们自己扩展一个:

 1 //对象级别的扩展插件
 2 $.fn.attr = function (name, value) {
 3     for (var i = 0; i < this.length; i++) {
 4         var item = this[i];
 5         if (name && value) {
 6             item.setAttribute(name, value);
 7         } else if (name && !value) {
 8             return item.getAttribute(name);
 9         }
10     }
11     return this;
12 };

调用一下,结果没错。返回this,也是为了链式调用。

技术分享

如上所示比较简单,不多说。

 

然后就是所谓的类级别的扩展了,也就是jquery的静态方法。经常被写为$.method(如$.ajax)。实现的时候呢用的是$.extend({方法对象,写各种扩展方法})

$.extend是啥,看看源码:

技术分享

其实就是jQuery的一个扩展方法,接收argument参数,这个参数就是你传过来的方法对象了,使用argument[0]一个个获取就行了

获取完了就是怎么把这些方法合并到jQuery本身了。看了下jquery源码,也来模拟下extend吧。

先看个小demo:

技术分享

一看就懂,Person本身就是个对象,给它加个方法而已(Person又是啥对象呢,越讲越多,讲不完滴)。你把Person看成jQuery,那就是$.ajax。

再看下面这个模拟jQuery的方法:

 1 //jquery静态方法扩展,即类级别扩展
 2     jQuery.extend = jQuery.fn.extend = function () {
 3         var src, copy, options, target = this;
 4         ////arguments[0] 如{a:function(){},b:funciton(){}},一个参数对象
 5         if ((options = arguments[0]) != null) {
 6             for (var name in options) {
 7                 copy = options[name];
 8                 target[name] = copy;//其实jquery就是把这些参数取出来,然后一个个复制到jquery这个object中
 9                 //如 var Person=function(){};Person.ajax=function(){}一样
10             }
11         }
12     };

关键代码第一句:target=this;this是啥或者说jQuery.fn.extend中的this是啥,其实就是jQuery对象。

关键代码第二句:for (var name in options),option就是你传递的那个对象,循环那个对象如:

var options={
  ajax: function () {
    console.log("模拟执行ajax");
  },
  load: function () {
    console.log("模拟执行load");
  }
}

关键代码第三句:target[name] = copy,其实也就是:

jQuery["ajax"]=function(){

  console.log("模拟执行ajax");

}

结合前面Person的demo一下子就明白了。

然后我们就可以写出下面的jQuery方法了

 1 /*****调用演示******/
 2 //函数级别的扩展插件
 3 $.extend({
 4     ajax: function () {
 5         console.log("模拟执行ajax");
 6     },
 7     load: function () {
 8         console.log("模拟执行load");
 9     }
10 });
11 $.ajax();

以上就是全部正文,本文全部代码:http://git.oschina.net/GspringG/jQueryDemo

总结

其实这篇也是越讲越多,js/jQuery的点是非常多的,也是越说越有意思。当然了本文也有可能出现一些有误的地方,请大家及时告知。

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。