zoukankan      html  css  js  c++  java
  • 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的点是非常多的,也是越说越有意思。当然了本文也有可能出现一些有误的地方,请大家及时告知。

    ——我认识一个人,他每做一件小事都像救命稻草一样抓着。有一天我发现,豁!他抱着的已经是让我仰望的参天大树了.
  • 相关阅读:
    Linux Shell入门
    Linux系统结构
    Ubuntu bond 配置
    VXLAN概述
    lsof
    入驻博客园,希望可以跟大家一起讨论,一起学习和进步。
    版本管理工具小乌龟TortoiseGit的安装和使用(2)
    版本管理工具小乌龟TortoiseGit的安装和使用(1)
    定义变量时未初始化赋值的问题
    BlackBerry 9900刷机
  • 原文地址:https://www.cnblogs.com/SpringRen/p/4209575.html
Copyright © 2011-2022 走看看