本文约定:
-
文章中的jQuery指的是jQuery框架中的jQuery函数(构造器)
-
文章中的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方法分离成两个独立的构造器,这样易于代码的管理,代码扩展性强
网友评论