zoukankan      html  css  js  c++  java
  • js菜鸟进阶-jQuery源码分析(1)-基本架构

    导读:

    本人JS菜鸟一枚,为加强代码美观和编程思想。所以来研究下jQuery,有需要进阶JS的同学很适合阅读此文!我是边看代码(jquery2.2.1),边翻“javascript高级程序设计”写的,有很多基本知识点我都写了书本对应的章节。有分析得不好的还请各位多多指教,更正!

    希望我的分析对大家有所帮助,谢谢!

    一、代码构成


    (function(global, factory){
        if ( typeof module === "object" && typeof module.exports === "object" ) {
        //模块化环境
        }else{
            factory( global );
        }
    })(typeof window !== "undefined" ? window: this, function(window, noGlobal) {
        //回调函数
    
        if ( typeof noGlobal === strundefined ) {
            window.jQuery = window.$ = jQuery;
        }
        return jQuery;
    });

    首先一个优美的JS库都会是在一个匿名函数里去写自己的代码对吧,jQuery也不列外。这其实只要把代码格式化一看,就一目了然。
    这个匿名函数接受的是两个参数,global(当期执行作用域链的对象),factory(回调)
      匿名函数 : 他本身是做了一个初始化方法的判断,判断当前JS使用环境是不是采用的模块化开发。如果是再做一些相应的逻辑处理(模块化就不多介绍了,可以自己到网上查询),否则直接执行回调factory并把当前执行的作用域对象当参数传递过去。
      回调函数: factory 所有的JQ方法属性都是在这个回调里实现.里的最后一段代码,就是对外开放方法的接口。

    二、jQuery类的构建


    开始入正题了,嘎嘎。。
    首先我们来看几个jQ的一常用场景方法:

    $.get(); $.post(); $.extend();//场景一

    这么一看jQuery不就是一个Object对象里面添加了几个方法么,其实确实是的,只不过他是一个Function类型的对象。看下面代码:

    $("#id").html(); $(".class").show();//场景二

    是的jQuery就是一个Function对象,那么我们就有几个问题了:

      1、在我们印象中jQuery不是一个类库么?
      2、JS中的类不是用构造函数来仿造的吗?
      3、JS构造函数不是都是用new操作符来实例化的么,为什么jQuery不需要使用new来实例化???
    要实现无new操作,我们想到可以使用工厂模式。(工厂模式可以参阅 “高级-第六章 面向对象的程序设计” )接下来我们先看看这个function的代码:

    // Define a local copy of jQuery
    jQuery = function(selector, context) {
    
        // The jQuery object is actually just the init constructor 'enhanced'
        // Need init if jQuery is called (just allow error to be thrown if not included)
        return new jQuery.fn.init(selector, context);
    }

    是的他的这段代码使用的就是JS中典型的工厂模式,工厂模式是不需new操作符的,我们来把jQuery改成一个典型的工厂模式看看; 

    var jQuery = function(selector,context){
        // jQuery("#id")这种使用场景我们留到选择器的时候再分析
    
        var o = new Object();
        o.get = function(){
            console.log("get");
        };
        o.post = function(){
            console.log("post");
        };
        return o;
        //或者
        retrun {
            get : function(){}
            ,post : function(){}
        }
        //对jQuery中返回一个 new 实例有疑问的参阅(第5章 引用类型)
    };
    
    jQuery.get(); //get
    jQuery.post(); //post

    嗯,如果改成上面这种方式似乎也可以是吧,但我们仔细看下代码。是不是每次使用我们都需要创建一个新对象,也在内存堆里开辟一个新的内存空间了(堆、栈知识参阅 第4章 变量、作用域和内存问题)那样就影响性能了,所以我们再改写下: 

    var fn = {
        get : function(){}
        ,name : "cH"
        ,post : function(){}
        ,init : function(){
            //这是一个内部构造函数
            this.age = 18;
            console.log(this.age);
            console.log(this.name);
        }
    };
    
    var jQuery = function(selector,context){
        return new fn.init();
    };
    jQuery();

    首先解释下为什么又返回是 return new init 而不是 return fn:
      我们如果直接返回fn的话那它就会造成对象引用而且是单实例的。而我们用new实例的话,我们每次使用jQuery都是一个新的实例,这样与其他的实例他就是没有任何干扰的。还有就是考虑到$(".calss").hide().html()这种应用场景,我们也必须考虑使用多实例。
      那我们每次调用不又是new一个实例,他不是又会造成性影响了么?是的,但是我们把fn拿出来了,所以每次new只是在栈里拷贝了一份这个对象的指针,并不像开始一样是在堆里新建了一个对象。所以照样减少了性能开销。
    OK,我们运行下代码,发现打this.name属性是undefied,为啥呢?看这个new fn.init(),我们new的init是个实例,在这个实例里的this指向是当前实例的,而name、get这些是fn这个局部变量的,所以this.name当然是undefied咯。那我们再改下代码:

    var fn = {
        get : function(){
            console.log("get");
        }
        ,name : "cH"
        ,post : function(){}
        ,init : function(){
            //这是一个内部构造函数
            this.age = 18;        
            console.log(this.age);
            console.log(this.name);
        }
    };
    //我们把内部构造函数的原型继承fn的属性 (继承 第6章 6.2)
    fn.init.prototype = fn;
    
    var jQuery = function(selector,context){
        return new fn.init();
    };
    jQuery();
    jQuery().get();

    这下是正常的了,我们把init这个构造函数的原型继承了fn这个对象。这样他的属性就继承过来了。
    我们知道有个这样的属性 jQuery.fn
    所以再改下代码:

    var jQuery = function(selector,context){
        return new jQuery.fn.init();
    };
    jQuery.fn = {
        get : function(){
            console.log("get");
        }
        ,name : "cH"
        ,post : function(){}
        ,init : function(){
            //这是一个内部构造函数
            this.age = 18;        
            console.log(this.age);
            console.log(this.name);
        }
    };
    jQuery.fn.init.prototype = jQuery.fn;

     这样一看代码就跟源码差不多了,我们对应再来看下jQuery源码:

    //构建jQuery类
    // Define a local copy of jQuery
    jQuery = function(selector, context) {
    
        // The jQuery object is actually just the init constructor 'enhanced'
        // Need init if jQuery is called (just allow error to be thrown if not included)
        return new jQuery.fn.init(selector, context);
    }
    //定义我们分析时的局部变量
    jQuery.fn = jQuery.prototype = {
        get :...
        ,name : ....
        ,post : ....
    }
    //定义fn.init方法,内部构造函数
    init = jQuery.fn.init = function(selector, context, root) {
        var match, elem;
    
        // HANDLE: $(""), $(null), $(undefined), $(false)
        if (!selector) {
            return this;
        }
    }
    //内部构造函数的原型继承
    init.prototype = jQuery.fn;

    我们发现他定义的局部变量是定义在了jQuery的prototype上了,而fn又引用了他的原型。

    1、为啥不用一个单独的局部变量,而是放在了jQuery的prototype上?
    2、fn 他这fn也没啥特殊意思,就一个引用。感觉这俩步有点多余?
    这两点 望大神解释!!

    三、方法拓展(方法扩展接口)


    我们把jQuery的使用方法归成了俩大类。

    //全局方法类
    $.get();
    $.post();
    ...
    
    //当前实例类
    $("#id").show(); 
    $("#id").css();
    ...

    全局方法类:
      扩展入口:$.extend()
      这个扩展就是直接给我们第一的jQuery构造器添加了一些静态方法  

    $.extend({meSay : function(){
           console.log("meSay")
     }});
     $.meSay();//meSay
      //这个扩展就相当于这样 jQuery.meSay = function...;

     当前实例类:

      扩展入口:$.fn.extend()
      而这个,还记得有个fn的属性吧,而他的所有属性方法是被继承到了当前实例的原型里去了的。所以我们这个扩展就只是给当前实例做的一个扩展。

    $.fn.extend({
        meSay : function(){
            console.log("meSay");
        }
    });
    $("#id").meSay();//meSay
    //这个扩展相当于 jQuery.fn.meSay = function...;

    看到jQuery的源码,jQuery.extend和jQuery.fn.extend其实是同一个方法的不同引用

    jQuery.extend = jQuery.fn.extend = function() {}
    //jQuery.extend 对jQuery本身的属性和方法进行了扩展
    //jQuery.fn.extend 对jQuery.fn的属性和方法进行了扩展

    extend的实现源码

     1 jQuery.extend = jQuery.fn.extend = function() {
     2     var options, name, src, copy, copyIsArray, clone, target = arguments[0] || {},
     3     i = 1,
     4     length = arguments.length,
     5     deep = false;
     6 
     7     // Handle a deep copy situation
     8     if (typeof target === "boolean") {
     9         deep = target;
    10 
    11         // Skip the boolean and the target
    12         target = arguments[i] || {};
    13         i++;
    14     }
    15 
    16     // Handle case when target is a string or something (possible in deep copy)
    17     if (typeof target !== "object" && !jQuery.isFunction(target)) {
    18         target = {};
    19     }
    20 
    21     // Extend jQuery itself if only one argument is passed
    22     if (i === length) {
    23         target = this;
    24         i--;
    25     }
    26 
    27     for (; i < length; i++) {
    28 
    29         // Only deal with non-null/undefined values
    30         if ((options = arguments[i]) != null) {
    31 
    32             // Extend the base object
    33             for (name in options) {
    34                 src = target[name];
    35                 copy = options[name];
    36 
    37                 // Prevent never-ending loop
    38                 if (target === copy) {
    39                     continue;
    40                 }
    41 
    42                 // Recurse if we're merging plain objects or arrays
    43                 if (deep && copy && (jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)))) {
    44 
    45                     if (copyIsArray) {
    46                         copyIsArray = false;
    47                         clone = src && jQuery.isArray(src) ? src: [];
    48 
    49                     } else {
    50                         clone = src && jQuery.isPlainObject(src) ? src: {};
    51                     }
    52 
    53                     // Never move original objects, clone them
    54                     target[name] = jQuery.extend(deep, clone, copy);
    55 
    56                     // Don't bring in undefined values
    57                 } else if (copy !== undefined) {
    58                     target[name] = copy;
    59                 }
    60             }
    61         }
    62     }
    63 
    64     // Return the modified object
    65     return target;
    66 };
    jQuery.extend = jQuery.fn.extend = function() {

    这个extend函数就是一个升级的深拷贝函数,jQuery内部的方法属性也都是使用此方法扩展出来的。我们看下他的使用场景:

    $.extend({"meName" : "cHjQuery"});
    $.meName // cHjQuery
    
    var d = {name : "d"};
    $.extend(d,{age:15});
    d //{name :"d",age:15};
    
    var c = $.extend({},{name:"c",age:15});
    c //{name :"c",age:15};
    //还有第个参数为true的场景,可以查询jQuery API

    jQuery的基本架构就是这样的了,总结
      1、所有的工作都是在一个匿名函数内执行,保证了命名空间的安全
      2、使用了工厂模式构建的类
      3、jQuery有一个叫extend的方法,jQuery所有方法属性都是使用此方法扩展出来的
      4、使用的方法分两大类,全局方法类和实例方法类,他们都有自己的对应的扩展入口


    本文为原创文章,转载请注明出处!

     http://www.cnblogs.com/hrw3c/p/5304849.html

     

     

  • 相关阅读:
    python
    mysql 操作
    you-get 使用代理
    恢复本地策略组--用于启动项管理等
    bat批处理——获取文件夹下所有文件名/重命名
    cmd--set用法,下次补充实例
    bat+7z批量压缩"文件夹"
    Excel提取字符串示例
    cron
    AIX修改用户密码登录不成功案例分享
  • 原文地址:https://www.cnblogs.com/hrw3c/p/5304849.html
Copyright © 2011-2022 走看看