jQuery技术内幕:深入解析jQuery架构设计与实现原理.2.2 总体结构

    xiaoxiao2024-02-02  150

    2.2 总体结构

    构造jQuery对象模块的总体源码结构如代码清单2-1所示。

    代码清单2-1 构造 jQuery 对象模块的总体源码结构

     16 (function( window, undefined ) {

            // 构造 jQuery 对象

     22    var jQuery = (function() {

     25       var jQuery = function( selector, context ) {

     27              return new jQuery.fn.init( selector, context, rootjQuery );

     28          },

                  // 一堆局部变量声明

     97       jQuery.fn = jQuery.prototype = {

     98          constructor: jQuery,

     99          init: function( selector, context, rootjQuery ) { ... },

                  // 一堆原型属性和方法

    319       };

    322       jQuery.fn.init.prototype = jQuery.fn;

    324       jQuery.extend = jQuery.fn.extend = function() { ... };

    388       jQuery.extend({

                  // 一堆静态属性和方法

    892       });

    955       return jQuery;

     957    })();

             // 省略其他模块的代码

    9246     window.jQuery = window.$ = jQuery;

    9266 })( window );

    下面简要梳理下这段源码。

    第16~9266行是最外层的自调用匿名函数,第1章中介绍过,当jQuery初始化时,这个自调用匿名函数包含的所有JavaScript代码将被执行。

    第22行定义了一个变量jQuery,第22~957行的自调用匿名函数返回jQuery构造函数并赋值给变量jQuery,最后在第9246行把这个jQuery变量暴露给全局作用域window,并定义了别名$。

    在第22~957行的自调用匿名函数内,第25行又定义了一个变量jQuery,它的值是jQuery构造函数,在第955行返回并赋值给第22行的变量jQuery。因此,这两个jQuery变量是等价的,都指向jQuery构造函数,为了方便描述,在后文中统一称为构造函数jQuery()。

    第97~319行覆盖了构造函数jQuery()的原型对象。第98行覆盖了原型对象的属性constructor,使它指向jQuery构造函数;第99行定义了原型方法jQuery.fn.init(),它负责解析参数selector和context的类型并执行相应的查找;在第27行可以看到,当我们调用jQuery构造函数时,实际返回的是jQuery.fn.init()的实例;此外,还定义了一堆其他的原型属性和方法,例如,selector、length、size()、toArray()等。

    第322行用jQuery构造函数的原型对象jQuery.fn覆盖了jQuery.fn.init()的原型对象。

    第324行定义了jQuery.extend()和jQuery.fn.extend(),用于合并两个或多个对象的属性到第一个对象;第388~892行执行jQuery.extend()在jQuery构造函数上定义了一堆静态属性和方法,例如,noConflict()、isReady、readyWait、holdReady()等。

    看上去代码清单2-1所述的总体源码结构有些复杂,下面把疑问和难点一一罗列,逐个分析。

    1)为什么要在构造函数jQuery()内部用运算符new创建并返回另一个构造函数的实例?

    通常我们创建一个对象或实例的方式是在运算符new后紧跟一个构造函数,例如,newDate()会返回一个Date对象;但是,如果构造函数有返回值,运算符new所创建的对象会被丢弃,返回值将作为new表达式的值。

    jQuery利用了这一特性,通过在构造函数jQuery()内部用运算符new创建并返回另一个构造函数的实例,省去了构造函数jQuery()前面的运算符new,即我们创建jQuery对象时,可以省略运算符new直接写jQuery()。

    为了拼写更方便,在第9246行还为构造函数jQuery()定义了别名$,因此,创建jQuery对象的常见写法是$()。

    2)为什么在第97行执行jQuery.fn = jQuery.prototype,设置jQuery.fn指向构造函数jQuery()的原型对象jQuery.prototype?

    jQuery.fn是jQuery.prototype的简写,可以少写7个字符,以方便拼写。

    3)既然调用构造函数jQuery()返回的jQuery对象实际上是构造函数jQuery.fn.init()的实例,为什么能在构造函数jQuery.fn.init()的实例上调用构造函数jQuery()的原型方法和属性?例如,$('#id').length和$('#id').size()。

    在第322行执行jQuery.fn.init.prototype = jQuery.fn时,用构造函数jQuery()的原型对象覆盖了构造函数jQuery.fn.init()的原型对象,从而使构造函数jQuery.fn.init()的实例也可以访问构造函数jQuery()的原型方法和属性。

    4)为什么要把第25~955行的代码包裹在一个自调用匿名函数中,然后把第25行定义的构造函数jQuery()作为返回值赋值给第22行的jQuery变量?去掉这个自调用匿名函数,直接在第25行定义构造函数jQuery()不也可以吗?去掉了不是更容易阅读和理解吗?

    去掉第25~955行的自调用匿名函数当然可以,但会潜在地增加构造jQuery对象模块与其他模块的耦合度。在第25~97行之间还定义了很多其他的局部变量,这些局部变量只在构造jQuery对象模块内部使用。通过把这些局部变量包裹在一个自调用匿名函数中,实现了高内聚低耦合的设计思想。

    5)为什么要覆盖构造函数jQuery()的原型对象jQuery.prototype?

    在原型对象jQuery.prototype上定义的属性和方法会被所有jQuery对象继承,可以有效减少每个jQuery对象所需的内存。事实上,jQuery对象只包含5种非继承属性,其余都继承自原型对象jQuery.prototype;在构造函数jQuery.fn.init()中设置了整型属性、length、selector、context;在原型方法.pushStack()中设置了prevObject。因此,也不必因为jQuery对象带有太多的属性和方法而担心会占用太多的内存。

    关于构造函数、原型、继承等基础知识,请查阅相关的基础类书籍。

    相关资源:敏捷开发V1.0.pptx
    最新回复(0)