jQuery源码分析之整体框架分析

 

    读一个开源框架,大家最想学到的就是设计的思想和实现的技巧。最近读jQuery源码,记下我对大师作品的理解和心得,跟大家分享,权当抛砖引玉。

 

先附上jQuery的代码结构。

 

 

(function(){
    //jQuery变量定义
    var jQuery  = function(){...};
    //jQuery原型定义(包含核心方法)              
    jQuery.fn = jQuery.prototype = {...};
    //看上去很奇怪吧? 非常巧妙的设计,后面详细介绍
    jQuery.fn.init.prototype = jQuery.fn;
    //提供jQuery静态方法与对象方法的扩展函数
    jQuery.extend = jQuery.fn.extend = function(){...}; 
    //后面依次有多个对jQuery静态方法的扩展
    jQuery.extend({...});
    //后面依次有多个对jQuery对象方法的扩展
    jQuery.fn.extend({...});
    jQuery.support = (function() {...})();
    //提供统一时间管理,jQuery内部使用,并不对外开放
    jQuery.event = {...};
    //Event类似于Java的POJO类.传递事件的对象
    jQuery.Event = function( src, props ) {...}; 
    //Sizzle选择器,一个框架,可独立使用。
    (function(){
        ...
        jQuery.find = Sizzle;
        ...
    })();
    ...
    //将定义的jQuery定义为全局变量
    window.jQuery = window.$ = jQuery;
    ...
})();

 

 

 

在结构上非常的清晰,定义一个jQuery对象,对jQuery对象进行扩展,赋给window,变成全局变量。就以下几点做介绍:

1). 自执行的匿名函数。

2). $("...")形式调用返回 jQuery.fn.init对象。

3). 框架里最常见的 extend 函数。

 

一. 自执行匿名函数。

对javascript有一定基础的都应该知道自执行匿名函数的好处。js是函数作用域。在函数里定义的变量都是局部变量,这样就很好的避免了过多的全局变量(jQuery仅仅2个全局变量jQuery和$)。由于闭包属性,虽然函数自执行结束了,但自执行函数里面定义的局部函数和变量还是能够被定义成全局变量的jQuery和$所引用到,类似于Java的私有变量。好处可见一斑。

 

二. $("...")形式调用返回 jQuery.fn.init对象。

这是我刚看源码的时候最不理解的地方。

 

var jQuery = function( selector, context ) {
    // The jQuery object is actually just the init constructor 'enhanced'
    return new jQuery.fn.init( selector, context, rootjQuery );
}
和
jQuery.fn.init.prototype = jQuery.fn;
看懂这段我们先看看jQuery的使用。jQuery采用链式调用(如:$("#id").data("xxx")),这样就知道$("#id")返回的是一个jQuery对象。但是调用的方式是函数调用。这个问题就成为了:以函数的方式调用返回jQuery对象,并且构造函数是init。现在就围绕解决这个问题展开:
首先想到的是在函数式调用的时候返回一个jQuery对象。
var jQuery = function( selector, context ) {
    return new jQuery( selector, context);
}
兄弟,你确定这样? 明眼人一看就知道严重的问题所在,死递归!
既然不能调用本身,那我们想到另一种办法:再定义一个函数A,A的原型与jQuery的原型一样,那A的对象与jQuery生成的对象就是一模一样了(javascript是原型继承),而且将A的constructor定义为jQuery,上面的问题不是迎刃而解么?
 
var jQuery = function( selector, context ) {
    return new A( selector, context);
}

var A = function(){
    if(this.init) {
        this.init();
    }
};
A.prototype = jQuery.prototype;

这样就解决了上面的问题,因为jQuery和A拥有同一个原型,所以生成的对象都拥有相同的方法。但是还是感觉A定义的有些多余,是不是?
既然定义A就为了返回A的对象,那init函数也能生成对象(以为js中没有类,定义的函数可以当函数执行,也能new成对象)。既然用init的话,那样init函数会自动执行,也不用再调用,岂不是更方便!所以就看到了我们之前看到的代码。
这里可能还有个fn解释下,其实这个fn没有什么特殊意思,只是jQuery.prototype的引用,jQuery支持自己扩展属性,这个对外提供了一个接口,jQuery.fn.extend()来对对象增加方法,比使用jQuery.prototype.extend()更好。封装想,字面就能看懂是对函数扩展,而不是看上去直接修改prototype.友好的用户接口。
相对于文字,图形化更加直观,对上面的引用来引用去画了个图,更好的理解:

jQuery源码分析之整体框架分析
其实在使用返回 new jQuery.fn.init( selector, context, rootjQuery ) 对象方式,我还有 另一种实现
var jQuery = function( selector, context ) {
    //如果以$("#id") 方式调用this就不是jQuery.这样返回jQuery对象
    if(!(this instanceof jQuery)) {
        return new jQuery(selector, context);
    }
    if(this.init) {
        this.init();
    }
}
//这行就可以注释了
//jQuery.fn.init.prototype = jQuery.fn;
 
这种经过测试时可以的,不知道还有没有其他的隐蔽问题,暂时没发现,也算是一种实现吧。供大家参考。
三. 框架里最看到的 extend 函数
在后面的段落中有大段大段的 jQuery.extend({...}) 和 jQuery.fn.extend({...}) 代码。这里先解释下这个的作用和不同。
extend 在java中是继承,在我之前写的一篇 <简单实现Javascrip继承> 文章不同,也都是用了extend关键字。那些我们都说叫继承,而这里我更加喜欢叫扩展。为什么呢? 继承是产生了新的类,而这里没有,这里的2个函数第一个是扩展jQuery的静态方法,而第二个是用户自己扩展对象的方法。静态方法?对象方法?这里我来做个解释,在jQuery中有2中调用形式:
1)$.Ajax(...);
2)$("#id").data("xxx");
第一种调用我称为静态调用,就类似于Java的静态方法一样,不用生成对象,而是类级函数。这里的$就相当于命名空间一样。我们知道在以往js的编程中,如果有命名空间我们都这样:
var ns = {};
ns.Ajax = function(){...};
那为什么这里不是对象,而是函数做一个命名空间呢? 其实在js中一切都是对象!包括函数也是对象(说Java一切都是对象,我觉得其实这句话形容js更加贴切)。
第二种调用我成为对象调用,因为.data()方法是定义在原型中的,只有new个对象才能调用的,所以成为对象方法。
我们看代码jQuery.extend = jQuery.fn.extend = function(){...}; 这个是连等,也就是2个指向同一个函数,怎么会实现不同的功能呢?这就是this 的功能了。jQuery.extend 调用的时候,this是指向jQuery对象的(jQuery是函数,也是对象!),所以这里扩展在jQuery上。 而jQuery.fn.extend 调用的时候,this指向fn对象,而上图中科院看到,jQuery.fn 和jQuery.prototype指向同一对象,扩展fn就是扩展jQuery.prototype原型对象。这里增加的是原型方法,也就是对象方法了。所以jQuery的api中提供了以上2中扩展函数。
本章结束,有不对和不准确的地方望大家指正。有疑问可以留言。
下篇介绍jQuery的data实现。欢迎关注。

你可能感兴趣的:(jquery)