美文网首页Web前端之路程序员让前端飞
详解jQuery构造器 - jQuery源码探索(一)

详解jQuery构造器 - jQuery源码探索(一)

作者: 不洗头的野人 | 来源:发表于2017-03-14 23:37 被阅读201次

    本文约定:

    1. 文章中的jQuery指的是jQuery框架中的jQuery函数(构造器)

    2. 文章中的jQ指的是jQuery框架

    为了防止混淆,方便阅读,故在此说明一下!

    1.jQuery构造器的实现

    jQuery构造器也就是jQuery函数,也是平时我们使用jQ时经常用到的美元符号$

    在jQ源码的最后面有:

    //这就是jQ暴露出来的接口
    window.jQuery = window.$ = jQuery;
    ```
    
    那么我们再来看看在jQ中jQuery函数是如何实现的:
    
    ```javascript
    jQuery = function( selector, context ) {
            return new jQuery.fn.init( selector, context );
    }
    //在后面,又发现了这么一句
    jQuery.fn = jQuery.prototype;
    ```
    
    看完上面代码,可以知道我们无论传一个什么参数给jQuery函数,它都会返回一个**jQuery.prototype.init**的实例对象。也就是说,我们平时用的jQuery对象,其实并不是jQuery构造器创建出来的,而是**jQuery函数原型中的init函数(构造器)**创建出来的!
    
    ## 2.jQuery为什么使用jQuery.prototype.init作为构造器
    
    **我们再来看看有关init函数的源码**
    
    ```javascript
    jQuery = function( selector, context ) {
            return new jQuery.fn.init( selector, context );
    }
    
    jQuery.fn = jQuery.prototype = {
        constructor: jQuery,
        init: function () {
            //实例化代码
        }
        //后面原型方法省略,不是本文所探究
    }
    //修改init的原型对象为jQuery函数的原型
    jQuery.fn.init.prototype = jQuery.fn;
    ```
    
    **先将上面代码,转化成我们习惯阅度的如下代码:**
    
    ```javascript
    jQuery = function( selector, context ) {
            return new jQuery.prototype.init( selector, context );
    }
    
    jQuery.prototype = {
        constructor: jQuery,
        init: function () {
            //实例化代码
        }
    }
    //修改init的原型对象为jQuery函数的原型
    jQuery.prototype.init.prototype = jQuery.prototype;
    ```
    
    由于将init.prototype=jQuery.prototype,因此init函数(构造器)创建出来的对象,它们的__proto__指针依然会指向jQuery.prototype,因此归根到底它们还是jQuery对象,只不过把,应该写在jQuery函数中的实例代码,写到了init函数中!
    
    因此上面的代码就是:**换了个包装,配方还是原来的!**
    
    **那我们思考一下,为什么jQ作者绕了几个弯把实例化代码写在jQuery.prototype.init里,而不是直接写在jQuery函数里?这么干有什么好处?**
    
    如果想不出来,那我们不妨尝试先把实例化代码写在jQuery函数里!
    
    ## 3.把实例化代码写在jQuery函数上
    
    不过在写之前,我们得清楚jQuery有一个很明显的特色是**"强制实例化"**,什么是"强制实例化"?还是说明一下吧,因为这词是我临时想出来的,你百度谷歌也查不出来...
    顾名思义,强制实例化就是强行实例化!举个栗子,jQ中无论传什么参数,它最终都会返回一个jQuery对象给你,比如: $("div"),我们传入了一个div字符串,并没有使用new $("div"),却返回了一个jQuery对象给我们。
    
    **那假如我们把jQuery的实例代码写在jQuery函数里,为了实现"强制实例化",那可以写成如下:**
    
    ```javascript
    var jQuery = function(selector, context) {
        //通过判断是否是jQuery对象,实现"强制实例化"
       if(!(this instanceof jQuery)){
           return new jQuery(selector, context);
       }
      //伪实例代码
       this.test = selector;
    };
    var obj = jQuery('obj');
    console.log(obj instanceof jQuery); //true
    </script>
    ```
    
    **上面方法实现了"强制实例化",但是又出现了另外一个问题,如果引用不当,就会出现无限递归的情况**
    
    我们把上面的代码稍微修改一下:
    
    ```javascript
    var jQuery = function(selector, context) {
            //比如在此处修改jQuery的原型对象
            jQuery.prototype = {};
            if(!(this instanceof jQuery)){
    
                return new jQuery(selector, context);
            }
            this.test = selector;
        };
        var obj = jQuery('obj');
        console.log(obj instanceof jQuery); 
    ```
    
    上面代码将抛出: ***Uncaught RangeError: Maximum call stack size exceeded ***
    
    程序进入了死循环、无限递归;
    
    
    **同样在jQuery函数里修改jQuery的原型,把实例化代码写到init上**
    
    ```javascript
    jQuery = function( selector, context ) {
       jQuery.prototype = {};
       return new jQuery.fn.init( selector, context );
    };
    
    jQuery.fn = jQuery.prototype = {
       constructor: jQuery,
       init: function ( selector, context ) {
           this.test = selector;
       }
    }
    jQuery.fn.init.prototype = jQuery.fn;
    var obj = jQuery('obj');
    console.log(obj);  //{test: "obj"}
    ```
    
    我们在上面修改jQuery的原型对象后,不仅没有进入无限递归,并且一点不影响obj的实例化,obj的__proto__指针依旧指向原来原型对象,也就是jQuery.fn
    
    在此额外说明一下,可能有些小伙伴会纠结,为什么要把jQuery.prototype = {}写在里面?写在外面就不会出错了!其实这只是我为了试错临时想的例子,用来验证代码的容错性。要知道,一个好的框架,容错性都是很高很高的!当然,这样做的好处不仅仅只有容错性,还有扩展性都很高,就是将init方法和jQuery方法分离成两个独立的构造器,以后静态方法写在jQuery上,实例方法写在init上,这样易于管理。
    
    ## 总结 
    
    上面的测试证明,这样的代码设计有很多的优点,这里我总结了几条
    
    1. 避免意外条件下,出现无限递归的情况
    
    2. 防止原型被修改
    
    3. 将init方法和jQuery方法分离成两个独立的构造器,这样易于代码的管理,代码扩展性强

    相关文章

      网友评论

        本文标题:详解jQuery构造器 - jQuery源码探索(一)

        本文链接:https://www.haomeiwen.com/subject/xdpknttx.html