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

本文约定:

  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

```

**上面方法实现了"强制实例化",但是又出现了另外一个问题,如果引用不当,就会出现无限递归的情况**

我们把上面的代码稍微修改一下:

```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源码探索(一))