2.2 jQuery 原型技术分解
任何复杂的技术都是从最简单的问题开始的,如果你被 jQuery 几千行庞杂结构的源代码所困惑,那么建议你阅读本节内容,我们将探索 jQuery 是如何从最简单的问题开始,并逐步实现羽翼渐丰的演变过程,从 jQuery 核心技术的还原过程来理解 jQuery 框架的搭建原理。
2.2.1 起源 -- 原型继承用过 JavaScript 的读者都会明白,在 JavaScript 脚本中到处都是函数,函数可以归置代码段,把相对独立的功能封装在一个函数包中。函数也可以实现类,这个类是面向对象编程中最基本的概念,也是最高抽象,定义一个类就相当于制作了一个模型,然后借助这个模型复制无数的实例。
例如,下面的代码就可以定义最初的 jQuery 类,类名就是 jQuery ,你可以把它视为一个函数,函数名是 jQuery 。当然,你也可以把它视为一个对象,对象名就是 jQuery 。与其他面向对象的编程语言相比,JavaScript 对于这个概念的界定好像很随意,这降低了编程的门槛,反之也降低了 JavaScript 作为编程语言的层次。
<script language="javascript" type="text/javascript">
var jQuery = function(){
// 函数体
};
</script>
上面创建了一个空的函数,好像什么都不能够做,这个函数实际上就是所谓的构造函数。构造函数在面向对象语言中是类的一个特殊方法,用来创建类。在 JavaScript 中,你可以把任何函数都视为构造函数,这没有什么不可以的,这样不会伤害代码本身。
所有类都有最基本的功能,如继承、派生和重写等。JavaScript 很奇特,它通过为所有函数绑定一个 prototype 属性,由这个属性指向一个原型对象,原型对象中可以定义类的继承属性和方法等。所以,对于上面的空类,可以继续扩展原型,其代码如下。
<script language="javascript" type="text/javascript">
var jQuery = function(){};
jQuery.prototype = {
// 扩展的原型对象
};
</script>
原型对象是 JavaScript 实现继承的基本机制。如果你觉得 jQuery.prototype 名称太长,没有关系,我们可以为其重新命名,如 fn ,当然你可以随便命名。如果直接命名 fn ,则表示该名称属性 Window 对象,即全局变量名。更安全的方法是为 jQuery 类定义一个公共属性, jQuery.fn ,然后把 jQuery 的原型对象传递给这个公共属性,实现代码如下。<script language="javascript" type="text/javascript">
jQuery.fn = jQuery.prototype = {
// 扩展的原型对象
};
</script>
这里的 jQuery.fn 相当于 jQuery.prototype 的别名,方便以后使用,它们指向同一个引用。因此若要调用 jQuery 的原型方法,直接使用 jQuery.fn 公共属性即可,不需要直接引用 jQuery.prototype ,当然直接使用 jQuery.prototype 也是可以的。既然原型对象可以使用别名,jQuery 类也可以起个别名,我们可以使用 $ 符号来引用它,代码如下。
var $ = jQuery = function(){};
现在模仿 jQuery 框架源码,给它添加两个成员,一个是原型属性 jquery ,一个是原型方法 size(),其代码如下。
<script language="javascript" type="text/javascript">
var $ = jQuery = function(){};jQuery.fn = jQuery.prototype = {
jquery: "1.3.2", // 原型属性
size: function(){ // 原型方法
return this.length;
}
};
</script>
2.2.2 生命 -- 返回实例当我们为 jQuery 添加了两个原型成员:jquery 属性和 size() 方法之后,这个框架最基本的样子就孕育出来了。但是该如何调用 jquery 属性和 size() 方法呢?
也许,你可以采用如下方法调用:
<script language="javascript" type="text/javascript">
var my$ = new $(); // 实例化
alert(my$.jquery); // 调用属性,返回 "1.3.2"
alert(my$.size());// 调用方法,返回 undefined
</script>
但是,jQuery 不是这样调用的。它模仿类似下面的方法进行调用。$().jquery;
$().size();
也就是说,jQuery 没有使用 new 运算符将 jQuery 类实例化,而是直接调用 jQuery() 函数,然后在这个函数后面直接调用 jQuery 的原型方法。这是怎么实现的呢?
如果你模仿 jQuery 框架的用法执行下面的代码,浏览器会显示编译错误。这说明上面这个案例代码还不是真正的 jQuery 技术原型。
alert($().jquery);
alert($().size());
也就是说,我们应该把 jQuery 看做一个类,同时也应该把它视为一个普通函数,并让这个函数的返回值为 jQuery 类的实例。因此,下面这种结构模型才是正确的。
[html] view plain
但是,如果在浏览器中预览,则会提示如图 2.1 所示的错误。内存外溢,说明出现了死循环引用。那么如何返回一个 jQuery 实例呢?
回忆一下,当使用 var my$ = new $(); 创建 jQuery 类的实例时,this 关键字就指向对象 my$ ,因此 my$ 实例对象就获得了 jQuery.prototype 包含的原型属性或方法,这些方法内的 this 关键字就会自动指向 my$ 实例对象。换句话说,this 关键字总是指向类的实例。
因此,我们可以这样尝试:在 jQuery 中使用一个工厂方法来创建一个实例 (就是 jQuery.fn),把这个方法放在 jQuery.prototype 原型对象中,然后在 jQuery() 函数中返回这个原型方法的调用。代码如下所示。
[html] view plain
2.2.3 学步 -- 分隔作用域我们已经初步实现了让 jQuery() 函数能够返回 jQuery 类的实例,下面继续思考:init() 方法返回的是 this 关键字,该关键字引用的是 jQuery 类的实例,如果在 init() 函数中继续使用 this 关键字,也就是说,假设我们把 init() 函数也视为一个构造器,则其中的 this 该如何理解和处理?