• 【面试题】封装jQuery源码以及实现jQuery的扩展功能


    1- 前言


    源码的学习,有点难度,但这正是可以激励 我们,让我们突破自己的瓶颈,往上走一步,再走一步…

    本篇博客是记录封装jQuery的笔记,学习源码、阅读源码,确实能学到很多东西。不只是技术,而是一种生活的态度。需要坚持下去,加油~

    2- 模拟jQuery实现


    下面我们通过模拟实现一个简单的jQuery,来巩固原型的应用。

     <script>
          // 为jQuery起一个别名,模仿jQuery的框架
          var $ = (jQuery = function () {});
          // 为jQuery原型起一个别名
          //这里没有直接赋值给fn,否则它属于window对象,容易造成全局污染
          //后面要访问jquery的原型,可以直接通过jQuery.fn来实现
          jQuery.fn = jQuery.prototype = {
            version: "6.1.1", //添加原型属性,表示jquery的版本
            //添加原型方法,表示返回jquery对象的长度
            size: function () {
              return this.length;
            },
          };
          
        </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    下面,我们使用jQuery原型中的size方法和version属性。

     // 为jQuery起一个别名,模仿jQuery的框架
          var $ = (jQuery = function () {});
          // 为jQuery原型起一个别名
          //这里没有直接赋值给fn,否则它属于window对象,容易造成全局污染
          //后面要访问jquery的原型,可以直接通过jQuery.fn来实现
          jQuery.fn = jQuery.prototype = {
            version: "6.1.1", //添加原型属性,表示jquery的版本
            //添加原型方法,表示返回jquery对象的长度
            size: function () {
              return this.length;
            },
          };
          var jq = new $();
          console.log(jq.version); // 6.1.1
          console.log(jq.size()); // undefined
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在上面的代码中,我们是创建了一个jquery的实例,然后通过该实例完成了原型属性和方法的调用。

    但是在jquery库中,是采用如下的方式进行调用。

    $().version;
    $().size()
    
    • 1
    • 2

    通过以上的两行代码,我们可以看到在jQuery库中,并没有使用new操作符,而是直接使用小括号运算符完成了对jQuery构造函数的调用。然后后面直接访问原型成员。

    那应该怎样实现这种操作?

    我们想到的就是,在jquery的构造函数中,直接创建jQuery类的实例。

      // 为jQuery起一个别名,模仿jQuery的框架
          var $ = (jQuery = function () {
            return new jQuery();
          });
          // 为jQuery原型起一个别名
          //这里没有直接赋值给fn,否则它属于window对象,容易造成全局污染
          //后面要访问jquery的原型,可以直接通过jQuery.fn来实现
          jQuery.fn = jQuery.prototype = {
            version: "6.1.1", //添加原型属性,表示jquery的版本
            //添加原型方法,表示返回jquery对象的长度
            size: function () {
              return this.length;
            },
          };
          $().version;
          //   var jq = new $();
          //   console.log(jq.version); // 6.1.1
          //   console.log(jq.size());
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在上面的代码中,给jQuery构造函数直接返回了它的实例return new jQuery();

    然后获取原型对象中的size属性的值:$().version.

    但是,出现了如下的错误:

    Uncaught RangeError: Maximum call stack size exceeded
    
    • 1

    以上错误的含义是栈内存溢出

    原因就是:当我们通过$()调用构造函数的时候,内部有执行了new操作,这时,又会重新执行jQuery的构造函数,这样就造成了死循环。

          var $ = (jQuery = function () {
            return jQuery.fn.init(); //调用原型中的`init方法`
          });
         
          jQuery.fn = jQuery.prototype = {
            init: function () {
              return this; //返回jquery的原型对象
            },
            version: "6.1.1",        
            size: function () {
              return this.length;
            },
          };
          console.log($().version);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在上面的代码中,在jQuery的构造方法中,调用的是原型中的init方法,在该方法中,返回了jquery的原型对象。

    最后进行输出:cosnole.log($().version)

    但是,以上的处理还是隐藏一个问题,具体看如下代码:

     var $ = (jQuery = function () {
            return jQuery.fn.init(); 
          });
          jQuery.fn = jQuery.prototype = {
            init: function () {
              this.length = 0; //原型属性length
              this._size = function () { //原型方法
                return this.length;
              };
              return this;
            },
            version: "6.1.1",
            length: 1, // 原型属性
            size: function () {
              return this.length;
            },
          };
          console.log($().version);
          console.log($()._size()); // 0
          console.log($().size()); // 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    在上面的代码中,在init这个原型方法中添加了lenght属性与_size方法,在该方法中打印length的值。

     var $ = (jQuery = function () {
            return new jQuery.fn.init(); //调用原型中的`init方法`
          });
    
    • 1
    • 2
    • 3

    jQuery的构造函数中,通过new操作符创建了一个实例对象,这样init()方法中的this指向的就是init方法的实例,而不是jQuery.prototype这个原型对象了。

      console.log($().version); // 返回undefined
          console.log($()._size()); // 0
          console.log($().size()); // 抛出异常:Uncaught TypeError: $(...).size is not a function
    
    • 1
    • 2
    • 3

    下面,我们来看一下怎样解决现在面临的问题。

     var $ = (jQuery = function () {
            return new jQuery.fn.init(); //调用原型中的`init方法`
          });
          jQuery.fn = jQuery.prototype = {
            init: function () {
              this.length = 0;
              this._size = function () {
                return this.length;
              };
              return this;
            },
            version: "6.1.1",
            length: 1,
            size: function () {
              return this.length;
            },
          };
    		// 将`jQuery`的原型对象覆盖掉init的原型对象。
          jQuery.fn.init.prototype = jQuery.fn;
          console.log($().version); //6.1.1
          console.log($()._size()); // 0
          console.log($().size()); // 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    在上面的代码中,我们添加了一行代码:

    jQuery.fn.init.prototype = jQuery.fn;
    
    • 1
    console.log($().version); 
    
    • 1

    下面,要实现的是选择器功能

    jQuery构造函数包括两个参数,分别是selectorcontext,selector表示的是选择器,context表示匹配的上下文,也就是可选择的访问,一般表示的是一个DOM元素。这里我们只考虑标签选择器

    <script>
          // 给构造函数传递selector,context两个参数
          var $ = (jQuery = function (selector, context) {
            return new jQuery.fn.init(selector, context); //调用原型中的`init方法`
          });
          jQuery.fn = jQuery.prototype = {
            init: function (selector, context) {
              selector = selector || document; //初始化选择器,默认值为document
              context = context || document; // 初始化上下文对象,默认值为document
              if (selector.nodeType) {
                // 如果是DOM元素
                // 把该DOM元素赋值给实例对象
                this[0] = selector;
                this.length = 1; //表示包含了1个元素
                this.context = selector; //重新设置上下文对象
                return this; //返回当前实例
              }
              if (typeof selector === "string") {
                //如果选择器是一个字符串
                var e = context.getElementsByTagName(selector); // 获取指定名称的元素
                //通过for循环将所有元素存储到当前的实例中
                for (var i = 0; i < e.length; i++) {
                  this[i] = e[i];
                }
                this.length = e.length; //存储元素的个数
                this.context = context; //保存上下文对象
                return this; //返回当前的实例
              } else {
                this.length = 0;
                this.context = context;
                return this;
              }
              //   this.length = 0;
              //   console.log("init==", this);
              //   this._size = function () {
              //     return this.length;
              //   };
              //   return this;
            },
    
            // version: "6.1.1",
            // length: 1,
            // size: function () {
            //   return this.length;
            // },
          };
          jQuery.fn.init.prototype = jQuery.fn;
          window.onload = function () {
            console.log($("div").length);
          };
          //   console.log($().version);
          //   console.log($()._size()); // 0
          //   console.log($().size()); // 0
          //   var jq = new $();
          //   console.log(jq.version); // 6.1.1
          //   console.log(jq.size());
        </script>
        <div></div>
        <div></div>
      </body>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    在上面的代码中,当页面加载完以后,这时会触发onload事件,在该事件对应的处理函数中,通过$("div"),传递的是字符串,

    selector参数表示的就是div这个字符串,这里没有传递context参数,表示的就是document对象。

    最后打印元素的个数。

    在使用jQuery库的时候,我们经常可以看到如下的操作:

    $('div').html()
    
    • 1

    以上代码的含义就是直接在jQuery对象上调用html( )方法来操作jQuery包含所有的DOM元素。
    html()方法的实现如下:

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
      </head>
      <body>
        <script>
          // 给构造函数传递selector,context两个参数
          var $ = (jQuery = function (selector, context) {
            return new jQuery.fn.init(selector, context); //调用原型中的`init方法`
          });
          jQuery.fn = jQuery.prototype = {
            init: function (selector, context) {
              selector = selector || document; //初始化选择器,默认值为document
              context = context || document; // 初始化上下文对象,默认值为document
              if (selector.nodeType) {
                // 如果是DOM元素
                // 把该DOM元素赋值给实例对象
                this[0] = selector;
                this.length = 1; //表示包含了1个元素
                this.context = selector; //重新设置上下文对象
                return this; //返回当前实例
              }
              if (typeof selector === "string") {
                //如果选择器是一个字符串
                var e = context.getElementsByTagName(selector); // 获取指定名称的元素
                //通过for循环将所有元素存储到当前的实例中
                for (var i = 0; i < e.length; i++) {
                  this[i] = e[i];
                }
                this.length = e.length; //存储元素的个数
                this.context = context; //保存上下文对象
                return this; //返回当前的实例
              } else {
                this.length = 0;
                this.context = context;
                return this;
              }
              //   this.length = 0;
              //   console.log("init==", this);
              //   this._size = function () {
              //     return this.length;
              //   };
              //   return this;
            },
            html: function (val) {
              jQuery.each(
                this,
                function (val) {
                  this.innerHTML = val;
                },
                val
              );
            },
    
            // version: "6.1.1",
            // length: 1,
            // size: function () {
            //   return this.length;
            // },
          };
          jQuery.fn.init.prototype = jQuery.fn;
    
          //提供each扩展方法
          jQuery.each = function (object, callback, args) {
            //通过for循环的方式来遍历jQuery对象中的每个DOM元素。
            for (var i = 0; i < object.length; i++) {
              // 在每个DOM元素上调用回调函数
              callback.call(object[i], args);
            }
            return object; //返回jQuery对象。
          };
          window.onload = function () {
            // console.log($("div").length);
            $("div").html("

    hello

    "); }; // console.log($().version); // console.log($()._size()); // 0 // console.log($().size()); // 0 // var jq = new $(); // console.log(jq.version); // 6.1.1 // console.log(jq.size()); </script> <div></div> <div></div> </body> </html>
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91

    在上面的代码中,首先添加了jQuery.each方法。

        //提供each扩展方法
          jQuery.each = function (object, callback, args) {
            //通过for循环的方式来遍历jQuery对象中的每个DOM元素。
            for (var i = 0; i < object.length; i++) {
              // 在每个DOM元素上调用回调函数
                //这里的让回调函数中的this指向了dom元素。
              callback.call(object[i], args);
            }
            return object; //返回jQuery对象。
          };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在上面的代码中,通过for循环遍历jQuery对象中的每个DOM元素。然后执行回调函数callback

    jQuery的原型对象上,添加html方法

      html: function (val) {
              jQuery.each(
                this, //表示jQuery原型对象
                function (val) {
                    //this表示的是dom元素,这里是div元素
                  this.innerHTML = val;
                },
                val //表示传递过来的`

    hello

    ` ); },
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    html方法中完成对jQuery.each方法的调用。

    window.onload的方法修改成如下的形式:

        window.onload = function () {
            // console.log($("div").length);
            $("div").html("

    hello

    "); };
    • 1
    • 2
    • 3
    • 4

    3- 下面我们实现jQuery的扩展功能


    jQuery 提供了良好的扩展接口,方便用户自定义 jQuery 方法。根据设计习惯,如果为 jQuery 或者 jQuery.prototype 新增方法时,我们可以直接通过点语法来实现,例如上面我们扩展的html方法,或者在 jQuery.prototype 对象结构内增加。但是,如果分析 jQuery 源码,会发现它是通过 extend() 函数来实现功能扩展的。

    通过extend()方法来实现扩展的好处是:方便用户快速的扩展jQuery功能,但不会破坏jQuery框架的结构。如果直接在jQuery源码中添加方法,这样就破坏了Jquery框架的结构,不方便后期的代码维护。

    如果后期不需要某个功能,可以直接使用Jquery提供的方法删除,而不需要从源码中在对该功能进行删除。

    extend() 函数的功能很简单,它只是把指定对象的方法复制给 jQuery 对象或者 jQuery.prototype

    DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Documenttitle>
      head>
      <body>
        <script>
          // 给构造函数传递selector,context两个参数
          var $ = (jQuery = function (selector, context) {
            return new jQuery.fn.init(selector, context); //调用原型中的`init方法`
          });
          jQuery.fn = jQuery.prototype = {
            init: function (selector, context) {
              selector = selector || document; //初始化选择器,默认值为document
              context = context || document; // 初始化上下文对象,默认值为document
              if (selector.nodeType) {
                // 如果是DOM元素
                // 把该DOM元素赋值给实例对象
                this[0] = selector;
                this.length = 1; //表示包含了1个元素
                this.context = selector; //重新设置上下文对象
                return this; //返回当前实例
              }
              if (typeof selector === "string") {
                //如果选择器是一个字符串
                var e = context.getElementsByTagName(selector); // 获取指定名称的元素
                //通过for循环将所有元素存储到当前的实例中
                for (var i = 0; i < e.length; i++) {
                  this[i] = e[i];
                }
                this.length = e.length; //存储元素的个数
                this.context = context; //保存上下文对象
                return this; //返回当前的实例
              } else {
                this.length = 0;
                this.context = context;
                return this;
              }
              //   this.length = 0;
              //   console.log("init==", this);
              //   this._size = function () {
              //     return this.length;
              //   };
              //   return this;
            },
            // html: function (val) {
            //   jQuery.each(
            //     this,
            //     function (val) {
            //       this.innerHTML = val;
            //     },
            //     val
            //   );
            // },
    
            // version: "6.1.1",
            // length: 1,
            // size: function () {
            //   return this.length;
            // },
          };
          jQuery.fn.init.prototype = jQuery.fn;
    
          //提供each扩展方法
          jQuery.each = function (object, callback, args) {
            //通过for循环的方式来遍历jQuery对象中的每个DOM元素。
            for (var i = 0; i < object.length; i++) {
              // 在每个DOM元素上调用回调函数
              callback.call(object[i], args);
            }
            return object; //返回jQuery对象。
          };
    
          jQuery.extend = jQuery.fn.extend = function (obj) {
            for (var prop in obj) {
              this[prop] = obj[prop];
            }
            return this;
          };
          jQuery.fn.extend({
            html: function (val) {
              jQuery.each(
                this,
                function (val) {
                  this.innerHTML = val;
                },
                val
              );
            },
          });
          window.onload = function () {
            // console.log($("div").length);
            $("div").html("

    hello

    "); }; // console.log($().version); // console.log($()._size()); // 0 // console.log($().size()); // 0 // var jq = new $(); // console.log(jq.version); // 6.1.1 // console.log(jq.size()); script> <div>div> <div>div> body> html>
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109

    在上面的代码中,我们为jQuery的原型对象添加了extend方法

         jQuery.extend = jQuery.fn.extend = function (obj) {
            for (var prop in obj) {
              this[prop] = obj[prop];
            }
            return this;
          };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    obj对象中的属性添加到jQuery原型对象上。

    下面调用extend方法,同时设置html属性

      jQuery.fn.extend({
            html: function (val) {
              jQuery.each(
                this,
                function (val) {
                  this.innerHTML = val;
                },
                val
              );
            },
          });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    这样jQuery原型对象上就有了html方法。

    而把原来的html方法的代码注释掉。

    刷新浏览器,查看对应的效果。

    参数传递

    我们在使用jquery的方法的时候,需要进行参数的传递,而且一般都要求传递的参数都是对象。

    使用对象作为参数进行传递的好处,就是方便参数的管理,例如参数个数不受限制。

    如果使用对象作为参数进行传递,需要解决的问题:如何解决并提取参数,如何处理默认值等问题。

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
      </head>
      <body>
        <script>
          // 给构造函数传递selector,context两个参数
          var $ = (jQuery = function (selector, context) {
            return new jQuery.fn.init(selector, context); //调用原型中的`init方法`
          });
          jQuery.fn = jQuery.prototype = {
            init: function (selector, context) {
              selector = selector || document; //初始化选择器,默认值为document
              context = context || document; // 初始化上下文对象,默认值为document
              if (selector.nodeType) {
                // 如果是DOM元素
                // 把该DOM元素赋值给实例对象
                this[0] = selector;
                this.length = 1; //表示包含了1个元素
                this.context = selector; //重新设置上下文对象
                return this; //返回当前实例
              }
              if (typeof selector === "string") {
                //如果选择器是一个字符串
                var e = context.getElementsByTagName(selector); // 获取指定名称的元素
                //通过for循环将所有元素存储到当前的实例中
                for (var i = 0; i < e.length; i++) {
                  this[i] = e[i];
                }
                this.length = e.length; //存储元素的个数
                this.context = context; //保存上下文对象
                return this; //返回当前的实例
              } else {
                this.length = 0;
                this.context = context;
                return this;
              }
              //   this.length = 0;
              //   console.log("init==", this);
              //   this._size = function () {
              //     return this.length;
              //   };
              //   return this;
            },
            // html: function (val) {
            //   jQuery.each(
            //     this,
            //     function (val) {
            //       this.innerHTML = val;
            //     },
            //     val
            //   );
            // },
    
            // version: "6.1.1",
            // length: 1,
            // size: function () {
            //   return this.length;
            // },
          };
          jQuery.fn.init.prototype = jQuery.fn;
    
          //提供each扩展方法
          jQuery.each = function (object, callback, args) {
            console.log("args=", args);
            //通过for循环的方式来遍历jQuery对象中的每个DOM元素。
            for (var i = 0; i < object.length; i++) {
              // 在每个DOM元素上调用回调函数
              callback.call(object[i], args);
            }
    
            return object; //返回jQuery对象。
          };
    
          // jQuery.extend = jQuery.fn.extend = function (obj) {
          //   for (var prop in obj) {
          //     this[prop] = obj[prop];
          //   }
          //   return this;
          // };
          jQuery.extend = jQuery.fn.extend = function () {
            var destination = arguments[0],
              source = arguments[1];
            //如果存在两个参数,并且都是对象
            if (typeof destination === "object" && typeof source === "object") {
              //把第二个对象合并到第一个参数对象中,并返回合并后的对象
              for (var property in source) {
                destination[property] = source[property];
              }
              return destination;
            } else {
              for (var prop in destination) {
                this[prop] = destination[prop];
              }
              return this;
            }
          };
          jQuery.fn.extend({
            html: function (val) {
              jQuery.each(
                this,
                function (val) {
                  this.innerHTML = val;
                },
                val
              );
            },
          });
          jQuery.fn.extend({
            fontStyle: function (obj) {
              var defaults = {
                color: "#ccc",
                size: "16px",
              };
              //如果有参数,会覆盖掉默认的参数
              defaults = jQuery.extend(defaults, obj || {});
              //为每个DOM元素执设置样式.
              jQuery.each(this, function () {
                this.style.color = defaults.color;
                this.style.fontSize = defaults.size;
              });
            },
          });
          window.onload = function () {
            // console.log($("div").length);
            $("div").html("

    hello

    "); $("p").fontStyle({ color: "red", size: "30px", }); }; // console.log($().version); // console.log($()._size()); // 0 // console.log($().size()); // 0 // var jq = new $(); // console.log(jq.version); // 6.1.1 // console.log(jq.size()); </script> <div></div> <div></div> <p>学习前端</p> <p>学习前端</p> </body> </html>
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149

    在上面的代码中,重新改造extend方法。

         jQuery.extend = jQuery.fn.extend = function () {
            var destination = arguments[0],
              source = arguments[1];
            //如果存在两个参数,并且都是对象
            if (typeof destination === "object" && typeof source === "object") {
              //把第二个对象合并到第一个参数对象中,并返回合并后的对象
              for (var property in source) {
                destination[property] = source[property];
              }
              return destination;
            } else {
              for (var prop in destination) {
                this[prop] = destination[prop];
              }
              return this;
            }
          };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    extend方法中,首先获取两个参数,然后判断这两个参数是否都是对象,如果都是对象,把第二个参数对象合并到第一个参数对象中,并返回合并后的对象。

    否则,将第一个参数对象复制到jquery的原型对象上。

         jQuery.fn.extend({
            fontStyle: function (obj) {
              var defaults = {
                color: "#ccc",
                size: "16px",
              };
              //如果有参数,会覆盖掉默认的参数
              defaults = jQuery.extend(defaults, obj || {});
                // console.log("this==", this);//init {0: p, 1: p, length: 2, context: document}
              //为每个DOM元素执设置样式.
              jQuery.each(this, function () {
                   //这里的this表示的是p标签,因为在each方法内部通过call改变了this指向,让this指向了每个遍历得到的p元素
                this.style.color = defaults.color;
                this.style.fontSize = defaults.size;
              });
            },
          });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在上面的代码中, 调用了extend方法,然后传递了fontStyle,这个fontStyle可以用来设置文本的颜色与字体大小。

    当我们第一次调用extend方法的时候,只是传递了fontStyle这个对象,这时,会将该对象添加到jQuery原型对象上。

        window.onload = function () {
            // console.log($("div").length);
            $("div").html("

    hello

    "); $("p").fontStyle({ color: "red", size: "30px", }); }; <div></div> <div></div> <p>学习前端</p> <p>学习前端</p>
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    onload事件中,调用fontStyle方法,并且传递了一个对象,这时在fontStyle方法的内部,首先会创建一个defaults默认的对象,然后再次调用extend方法,将传递的对象合并到默认对象上,当然完成了值的覆盖。

    下面调用each方法,在each方法中遍历每个元素,执行回调函数,并且改变this的指向。

    封装成独立的命名空间

    以上已经实现了一个简单的jQuery库,

    但是这里还有一个问题,需要解决:当编写了大量的javascript代码以后,引入该jquery库就很容易出现代码冲突的问题,所以这里需要将jquery库的代码与其他的javascript代码进行隔离,这里使用闭包。

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
      </head>
      <body>
        <script>
          (function (window) {
            // 给构造函数传递selector,context两个参数
            var $ = (jQuery = function (selector, context) {
              return new jQuery.fn.init(selector, context); //调用原型中的`init方法`
            });
            jQuery.fn = jQuery.prototype = {
              init: function (selector, context) {
                selector = selector || document; //初始化选择器,默认值为document
                context = context || document; // 初始化上下文对象,默认值为document
                if (selector.nodeType) {
                  // 如果是DOM元素
                  // 把该DOM元素赋值给实例对象
                  this[0] = selector;
                  this.length = 1; //表示包含了1个元素
                  this.context = selector; //重新设置上下文对象
                  return this; //返回当前实例
                }
                if (typeof selector === "string") {
                  //如果选择器是一个字符串
                  var e = context.getElementsByTagName(selector); // 获取指定名称的元素
                  //通过for循环将所有元素存储到当前的实例中
                  for (var i = 0; i < e.length; i++) {
                    this[i] = e[i];
                  }
                  this.length = e.length; //存储元素的个数
                  this.context = context; //保存上下文对象
                  return this; //返回当前的实例
                } else {
                  this.length = 0;
                  this.context = context;
                  return this;
                }
                //   this.length = 0;
                //   console.log("init==", this);
                //   this._size = function () {
                //     return this.length;
                //   };
                //   return this;
              },
              // html: function (val) {
              //   jQuery.each(
              //     this,
              //     function (val) {
              //       this.innerHTML = val;
              //     },
              //     val
              //   );
              // },
    
              // version: "6.1.1",
              // length: 1,
              // size: function () {
              //   return this.length;
              // },
            };
            jQuery.fn.init.prototype = jQuery.fn;
    
            //提供each扩展方法
            jQuery.each = function (object, callback, args) {
              //通过for循环的方式来遍历jQuery对象中的每个DOM元素。
              for (var i = 0; i < object.length; i++) {
                // 在每个DOM元素上调用回调函数
                callback.call(object[i], args);
              }
    
              return object; //返回jQuery对象。
            };
    
            // jQuery.extend = jQuery.fn.extend = function (obj) {
            //   for (var prop in obj) {
            //     this[prop] = obj[prop];
            //   }
            //   return this;
            // };
            jQuery.extend = jQuery.fn.extend = function () {
              var destination = arguments[0],
                source = arguments[1];
              //如果存在两个参数,并且都是对象
              if (typeof destination === "object" && typeof source === "object") {
                //把第二个对象合并到第一个参数对象中,并返回合并后的对象
                for (var property in source) {
                  destination[property] = source[property];
                }
                return destination;
              } else {
                for (var prop in destination) {
                  this[prop] = destination[prop];
                }
                return this;
              }
            };
            // 开发jqueyr
            window.jQuery = window.$ = jQuery;
          })(window);
    
          jQuery.fn.extend({
            html: function (val) {
              jQuery.each(
                this,
                function (val) {
                  this.innerHTML = val;
                },
                val
              );
            },
          });
          jQuery.fn.extend({
            fontStyle: function (obj) {
              var defaults = {
                color: "#ccc",
                size: "16px",
              };
              //如果有参数,会覆盖掉默认的参数
              defaults = jQuery.extend(defaults, obj || {});
    
              // console.log("this==", this);//init {0: p, 1: p, length: 2, context: document}
              //为每个DOM元素执设置样式.
              jQuery.each(this, function () {
                //这里的this表示的是p标签,因为在each方法内部通过call改变了this指向,让this指向了每个遍历得到的p元素
    
                this.style.color = defaults.color;
                this.style.fontSize = defaults.size;
              });
            },
          });
          window.onload = function () {
            // console.log($("div").length);
            $("div").html("

    hello

    "); $("p").fontStyle({ color: "red", size: "30px", }); }; // console.log($().version); // console.log($()._size()); // 0 // console.log($().size()); // 0 // var jq = new $(); // console.log(jq.version); // 6.1.1 // console.log(jq.size()); </script> <div></div> <div></div> <p>学习前端</p> <p>学习前端</p> </body> </html>
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157

    在上面的代码中,将jQuery库放在匿名函数中,然后进行自调用,并且传入window对象。

    在上面所添加的代码中还要注意如下语句:

    window.jQuery = window.$ = jQuery;
    
    • 1

    以上语句的作用:把闭包中的私有变量jQuery传递给window对象的jQuery属性。这样就可以在全局作用域中通过jQuery变量来访问闭包体内的jQuery框架了。

    以上就是模拟的jQuery库。

    4- 案例

    封装jQuery源码,jquery.js

    ;(function(){
    	// 匿名函数自执行
    	// jQ的构造函数
    	function jQuery(selector){
    		// 返回new 一个初始化函数
    		return new jQuery.fn.init(selector);
    	}
    	// 定义JQuery构造函数的显示原型
    	jQuery.fn =jQuery.prototype = {
    		constructor:jQuery,
    		jquery:"9.0.0",
    		length:0,
    		get(index){
    			return this[index];
    		},
    		/* click(callback){
    			// 单击时候让this的每个元素执行callback回调函数
    			for(var i=0;i
    		/* toggle(){
    			// 遍历每个元素如果display不是none就隐藏,否则就显示
    			for(var i=0;i
    		each(callback){
    			for(var i=0;i<this.length;i++){
    				callback(this[i])
    			}
    			return this;
    		},
    		click(callback){
    			// item指向的被遍历的每个元素
    			this.each(function(item){
    				// 让每个元素注册click事件 执行callback方法
    				// 也就是click 括号里面的回调函数
    				item.addEventListener("click",callback);
    			})
    			return this;
    		},
    		toggle(){
    			this.each(function(item){
    				if(item.style.display!="none"){
    					item.style.display="none"
    				}else{
    					item.style.display="block";
    				}
    			})
    		}
    	}
    	var isReady = false;//当前dom是否加载完毕
    	var readyList = []; // 等待要被执行的函数礼包
    	//监听domcontentLoaded 事件
    	document.addEventListener("DOMContentLoader",function(){
    		//文档加载完毕
    	    //改变isReady
    		isReady = true;
    		//遍历readyList 里面的函数并执行
    		readyList.forEach(item=>item())
    		//做完后清空
    		readyList = []
    	})
    	// jq初始化函数
    	jQuery.fn.init =function(selector){
    			
    		if(typeof selector === "function"){
    			//如果jQuery 已经准备完毕
    			if(isReady){
    				selector()
    			}else{
    				//把它加入的readyList 列表中
    				readyList.push(selector);
    			}
    		}else{
    			// 获取到选择列表
    			var list = document.querySelectorAll(selector);
    			// 当前对象的长度
    			this.length = list.length;
    			for(var i=0;i<list.length;i++){
    				//遍历类别对 this赋值
    				this[i] = list[i];
    			}
    		}
    
    	}
    	// 如何让new  init 产生对象拥有JQuery显示原型上的所有方法呢?
    	jQuery.fn.init.prototype = jQuery.fn;
    	// 全局对jQuery与$可以访问
    	window.$=window.jQuery = jQuery;
    	
    })()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • jquery.html
    <!DOCTYPE html>
    <html lang="zh">
    	<head>
    		<meta charset="UTF-8">
    		<meta http-equiv="X-UA-Compatible" content="IE=edge">
    		<meta name="viewport" content="width=device-width, initial-scale=1.0">
    		<title>Document</title>
    		<script src="./js/jquery-9.0.0.js"></script>
    		<style>
    			.active {
    				color: #f70;
    				border: 1px solid #f30;
    			}
    		</style>
    		<script>
    			//jq多态
    			$(function() {
    				alert("jq已经加载完毕")
    			})
    			$(function() {
    				alert("jq已经加载完毕11")
    			})
    		</script>
    	</head>
    	<body>
    		<h1>jquery还是要学的</h1>
    		<p class="act">要好好学</p>
    		<h1>学好了才会高薪就业</h1>
    		<button>切换</button>
    	</body>
    	<script>
    		var hs = $("h1");
    		console.log(hs);
    
    		//给button 注册一个点击事件
    		$("button").click(function() {
    			$("h1").toggle()
    		})
    		$(function() {
    			alert("jq已经加载完毕22")
    		})
    	</script>
    </html>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    5- 总结

    源码 是底层逻辑,并不简单,所以还需要保持良好的心态,坚定地学习,在网上遨游,一起努力吧!

  • 相关阅读:
    [Cesium学习]
    外包干了3个月,技术倒退2年。。。
    20240628每日前端---------解决vue项目滥用watch
    sentinel熔断报java.lang.reflect.UndeclaredThrowableException
    Blender关键帧动画简明教程
    某马机房预约系统 C++项目(一)
    nvm下node安装;node环境变量配置
    ubuntu环境下载android源码
    Github 2024-04-21 开源项目日报 Top10
    linux 进程 CPU耗费较高排查
  • 原文地址:https://blog.csdn.net/qq_59012240/article/details/128008825