`
testcs_dn
  • 浏览: 106007 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

第二章 jQuery技术解密(一)

 
阅读更多

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 类的实例。因此,下面这种结构模型才是正确的。

  1. <scripttype="text/javascript">
  2. var$=jQuery=function(){
  3. returnnewjQuery();//返回类的实例
  4. };
  5. jQuery.fn=jQuery.prototype={
  6. jquery:"1.3.2",//原型属性
  7. size:function(){//原型方法
  8. returnthis.length;
  9. }
  10. };
  11. alert($().jquery);
  12. alert($().size());
  13. </script>
但是,如果在浏览器中预览,则会提示如图 2.1 所示的错误。内存外溢,说明出现了死循环引用。


那么如何返回一个 jQuery 实例呢?

回忆一下,当使用 var my$ = new $(); 创建 jQuery 类的实例时,this 关键字就指向对象 my$ ,因此 my$ 实例对象就获得了 jQuery.prototype 包含的原型属性或方法,这些方法内的 this 关键字就会自动指向 my$ 实例对象。换句话说,this 关键字总是指向类的实例。

因此,我们可以这样尝试:在 jQuery 中使用一个工厂方法来创建一个实例 (就是 jQuery.fn),把这个方法放在 jQuery.prototype 原型对象中,然后在 jQuery() 函数中返回这个原型方法的调用。代码如下所示。

  1. <scripttype="text/javascript">
  2. var$=jQuery=function(){
  3. returnjQuery.fn.init();//调用原型init()
  4. };
  5. jQuery.fn=jQuery.prototype={
  6. init:function(){//在初始化原型方法中返回实例的引用
  7. returnthis;
  8. },
  9. jquery:"1.3.2",//原型属性
  10. size:function(){//原型方法
  11. returnthis.length;
  12. }
  13. };
  14. alert($().jquery);//调用属性,返回"1.3.2"
  15. alert($().size());//调用方法,返回undefined
  16. </script>

2.2.3 学步 -- 分隔作用域

我们已经初步实现了让 jQuery() 函数能够返回 jQuery 类的实例,下面继续思考:init() 方法返回的是 this 关键字,该关键字引用的是 jQuery 类的实例,如果在 init() 函数中继续使用 this 关键字,也就是说,假设我们把 init() 函数也视为一个构造器,则其中的 this 该如何理解和处理?

例如,在下面示例中,jQuery 原型对象中包含一个 length 属性,同时 init() 从一个普通的函数转身变成了构造器,它也包含一个 length 属性和一个 test() 方法。运行该示例,我们可以看到,this 关键字引用了 init() 函数作用域所在的对象, 此时它访问 length 属性时,返回0. 而 this 关键字也能够访问上一级对象 jQuery.fn 对象的作用域,所以 $().jquery 返回 "1.3.2" 。但是调用 $().size() 方法时,返回的是 0, 而不是 1 。

  1. <scripttype="text/javascript">
  2. var$=jQuery=function(){
  3. returnjQuery.fn.init();//调用原型init()
  4. };
  5. jQuery.fn=jQuery.prototype={
  6. init:function(){//在初始化原型方法中返回实例的引用
  7. this.length=0;
  8. this.test=function(){
  9. returnthis.length;
  10. }
  11. returnthis;
  12. },
  13. jquery:"1.3.2",//原型属性
  14. length:1,
  15. size:function(){//原型方法
  16. returnthis.length;
  17. }
  18. };
  19. alert($().jquery);//返回"1.3.2"
  20. alert($().test());//返回0
  21. alert($().size());//返回0
  22. </script>
这种设计思路很容易破坏作用域的独立性,对于 jQuery 这样的框架来说,很可能会造成消极影响。因此,我们可以看到 jQuery 框架是通过下面的方式调用 init() 初始化构造函数的。

<script type="text/javascript">

var $ = jQuery = function(){

return new jQuery.fn.init(); // 实例化 init 初始化类型,分隔作用域

};

</script>

这样就可以把 init() 构造器中的 this 和 jQuery.fn 对象中的 this 关键字隔离开来,避免相互混淆。但是,这种方式也会带来另一个问题:无法访问 jQuery.fn 对象的属性或方法。例如,在下面的示例中,访问 jQuery.fn 原型对象的 jquery 属性和 size() 方法时就会出现这个问题。

  1. <scripttype="text/javascript">
  2. var$=jQuery=function(){
  3. returnnewjQuery.fn.init();//实例化init初始化类型,分隔作用域
  4. };
  5. jQuery.fn=jQuery.prototype={
  6. init:function(){//在初始化原型方法中返回实例的引用
  7. this.length=0;
  8. this.test=function(){
  9. returnthis.length;
  10. }
  11. returnthis;
  12. },
  13. jquery:"1.3.2",//原型属性
  14. length:1,
  15. size:function(){//原型方法
  16. returnthis.length;
  17. }
  18. };
  19. alert($().jquery);//返回undefined
  20. alert($().test());//返回0
  21. alert($().size());//抛出异常
  22. </script>

2.2.4 生长 -- 跨域访问

如何做到既能够分隔初始化构造器函数与 jQuery 原型对象的作用域,又能够在返回实例中访问 jQuery 原型对象呢?

jQuery 框架巧妙地通过原型传递解决了这个问题,它把 jQuery.fn 传递给 jQuery.fn.init.prototype ,也就是说用 jQuery 的原型对象覆盖 init 构造器的原型对象,从而实现跨域访问,其代码如下所示。

  1. <scripttype="text/javascript">
  2. var$=jQuery=function(){
  3. returnnewjQuery.fn.init();//实例化init初始化类型,分隔作用域
  4. };
  5. jQuery.fn=jQuery.prototype={
  6. init:function(){//在初始化原型方法中返回实例的引用
  7. this.length=0;
  8. this.test=function(){
  9. returnthis.length;
  10. }
  11. returnthis;
  12. },
  13. jquery:"1.3.2",//原型属性
  14. length:1,
  15. size:function(){//原型方法
  16. returnthis.length;
  17. }
  18. };
  19. jQuery.fn.init.prototype=jQuery.fn;//使用jQuery的原型对象覆盖init的原型对象
  20. alert($().jquery);//返回"1.3.2"
  21. alert($().test());//返回0
  22. alert($().size());//返回0
  23. </script>

这是一招妙棋,new jQuery.fn.init() 创建的新对象拥有 init 构造器的 prototype 原型对象的方法,通过改变 prototype 指针的指向,使其指向 jQuery 类的 prototype ,这样创建出来的对象就继承了 jQuery.fn 原型对象定义的方法。

2.2.5 成熟 -- 选择器

jQuery 返回的是 jQuery 对象,jQuery 对象是一个类数组的对象,本质上它就是一个对象,但是它拥有数组的长度和下标,却没有继承数组的方法。

很显然,上面几节的讲解都是建立在一种空理论基础上的,目的是希望读者能够理解 jQuery 框架的核心构建过程。下面,我们就尝试为 jQuery() 函数传递一个参数,并让它返回一个 jQuery 对象。

jQuery() 函数包含两个参数 selector 和 context ,其中 selector 表示选择器,而 context 表示选择的内容范围,它表示一个 DOM 元素。为了简化操作,我们假设选择器的类型仅限定为标签选择器。实现的代码如下所示。

  1. <div></div>
  2. <div></div>
  3. <div></div>
  4. <scripttype="text/javascript">
  5. var$=jQuery=function(selector,context){//定义类
  6. returnnewjQuery.fn.init(selector,context);//返回选择器的实例
  7. };
  8. jQuery.fn=jQuery.prototype={//jQuery类的原型对象
  9. init:function(selector,context){//定义选择器构造器
  10. selector=selector||document;//设置默认值为document
  11. context=context||document;//设置默认值为document
  12. if(selector.nodeType){//如果选择符为节点对象
  13. this[0]=selector;//把参数节点传递给实例对象的数组
  14. this.length=1;//并设置实例对象的length属性,定义包含的元素个数
  15. this.context=selector;//设置实例的属性,返回选择范围
  16. returnthis;//返回当前实例
  17. }
  18. if(typeofselector==="string"){//如果选择符是字符串
  19. vare=context.getElementsByTagName(selector);//获取指定名称的元素
  20. for(vari=0;i<e.length;i++){//遍历元素集合,并把所有元素填入到当前实例数组中
  21. this[i]=e[i];
  22. }
  23. this.length=e.length;//设置实例的length属性,即定义包含的元素个数
  24. this.context=context;//设置实例的属性,返回选择范围
  25. returnthis;//返回当前实例
  26. }else{
  27. this.length=0;//否则,设置实例的length属性值为0
  28. this.context=context;//设置实例的属性,返回选择范围
  29. returnthis;//返回当前实例
  30. }
  31. },
  32. jquery:"1.3.2",//原型属性
  33. size:function(){//原型方法
  34. returnthis.length;
  35. }
  36. };
  37. jQuery.fn.init.prototype=jQuery.fn;//使用jQuery的原型对象覆盖init的原型对象
  38. alert($("div").size());//返回3
  39. </script>
在上面示例中,$("div") 基本拥有了 jQuery 框架中 $("div") 语法的功能,使用它可以选取页面中指定范围的 div 元素。同时,调用 size() 方法可以返回 jQuery 对象集合的长度。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics