首頁  >  文章  >  web前端  >  實例詳解jquery的整體架構

實例詳解jquery的整體架構

怪我咯
怪我咯原創
2017-06-27 11:34:251300瀏覽

學習開源框架,童鞋們最想學到的就是設計的想法和實現的技巧。最近研究jQuery源碼,記錄一下我對jquery的理解和心得,跟大家分享,權當拋磚引玉。

jQuery整體框架甚是複雜,也不易讀懂,這幾天一直在研究這個笨重而強大的框架。 jQuery的整體架構可分為:入口模組、底層模組和功能模組。這裡,我們以jquery-1.7.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 );

分析一下以上程式碼,我們發現jquery採取了匿名函數自執行的寫法,這樣做的好處就是可以有效的防止命名空間與變數污染的問題。縮寫以上程式碼就是:

 程式碼如下:

(function(window, undefined) {
    var jQuery = function() {}
    // ...
    window.jQuery = window.$ = jQuery;
})(window);

參數window

匿名函數傳了兩個參數進來,一個是window,一個是undefined。我們知道,在js中變數是有作用域鏈的,這兩個變數的傳入就會變成匿名函數的局部變量,而訪問起來的時候速度會更快。透過傳入window物件可以使window物件作為局部變數使用,那麼,函數的參數也都變成了局部變量,當在jquery中存取window物件時,就不需要將作用域鏈退回到頂層作用域,從而可以更快的存取window物件。

參數undefined

js在尋找變數的時候,js引擎會先在函數本身的作用域中找出這個變數,如果沒有的話就繼續往上找,找到了就回傳該變量,找不到就回傳undefined。 undefined是window物件的屬性,透過傳入undefined參數,但又不進行賦值,可以縮短尋找undefined時的作用域鏈。在 自呼叫匿名函數 的作用域內,確保undefined是真的未定義。因為undefined能夠被重寫,賦予新的值。

jquery.fn是啥?

程式碼如下:

 jQuery.fn = jQuery.prototype = {
              constructor: jQuery,
              init: function( selector, context, rootjQuery ) { ... },
                 // 一堆原型属性和方法
        };

透過分析以上程式碼,我們發現jQuery.fn即是jQuery.prototype,這樣寫的好處就是更簡短。之後,我們又看到jquery為了簡潔,乾脆使用一個$符號來代替jquery使用,因此,在我們使用jquery框架的使用經常都會用到$(),

構造函數jQuery( )

圖片描述

jQuery的物件並不是透過new jQuery 建立的,而是透過new jQuery.fn.init 建立的:

#程式碼如下:

var jQuery = function( selector, context ) {
       return new jQuery.fn.init( selector, context, rootjQuery );
}

這裡定義了一個變數jQuery,他的值是jQuery建構函數,在955行(最上面的程式碼)回傳並賦值給jQuery變數

jQuery.fn.init

jQuery.fn (上面97行)是建構子jQuery()的原型對象,jQuery.fn.init()是jQuery原型方法,也可以稱為建構子。負責解析參數selector和context的類型並執行對應的查找。

參數context:可以不傳入,或傳入jQuery對象,DOM元素,普通js對象之一
參數rootjQuery:包含了document對象的jQuery對象,用於document.getElementById()查找失敗等情況。

 程式碼如下:

jQuery.fn.init.prototype = jQuery.fn = jQuery.prototype
jQuery(selector [,context])

預設情況下,對符合元素的尋找從根元素document 物件開始,即查找範圍是整個文件樹,不過也可以傳入第二個參數context來限定它的查找範圍。例如:

程式碼如下:

$('p.foo').click(function () {
            $('span',this).addClass('bar');//限定查找范围,即上面的context
   });
jQuery.extend()和jQuery.fn.extend()

方法jQuery.extend(object)和jQuery.fn.extend(object)用於合併兩個或更多個對像到第一個對象。相關原始碼如下(部分):

 程式碼如下:

jQuery.extend = jQuery.fn.extend = function() {
    var options, name, src, copy, copyIsArray, clone,//定义的一组局部变量
        target = arguments[0] || {},
        i = 1,
        length = arguments.length,
        deep = false;

jQuery.extend(object); 為jQuery類別新增新增類別方法,可以理解為新增靜態方法。如:

 程式碼如下:

$.extend({ 
  add:function(a,b){returna+b;} 
});

便為jQuery 添加一個為add 的“靜態方法”,之後便可以在引入jQuery 的地方,使用這個方法了,

$.add(3,4); //return 7
jQuery.fn.extend(object),查看一段官網的程式碼示範如下:

# 程式碼如下:

<label><input type="checkbox" name="foo"> Foo</label>
<label><input type="checkbox" name="bar"> Bar</label>
<script>
    jQuery.fn.extend({
        check: function() {
            return this.each(function() {
                this.checked = true;
            });
        },
        uncheck: function() {
            return this.each(function() {
                this.checked = false;
            });
        }
    });
    // Use the newly created .check() method
    $( "input[type=&#39;checkbox&#39;]" ).check();
</script>

CSS選擇器引擎Sizzle

可以說,jQuery是為操作DOM而誕生的,jQuery之所以如此強大,得益於CSS選擇器引擎Sizzle,解析規則引用網上的一段實例:

selector:"p > p + p.aaron input[type="checkbox"]"

解析规则:
1 按照从右到左
2 取出最后一个token  比如[type="checkbox"]
                            {
                                matches : Array[3]
                                type    : "ATTR"
                                value   : "[type="
                                checkbox "]"
                            }
3 过滤类型 如果type是 > + ~ 空 四种关系选择器中的一种,则跳过,在继续过滤
4 直到匹配到为 ID,CLASS,TAG  中一种 , 因为这样才能通过浏览器的接口索取
5 此时seed种子合集中就有值了,这样把刷选的条件给缩的很小了
6 如果匹配的seed的合集有多个就需要进一步的过滤了,修正选择器 selector: "p > p + p.aaron [type="checkbox"]"
7 OK,跳到一下阶段的编译函数

deferred对象

开发网站的过程中,我们经常遇到某些耗时很长的javascript操作。其中,既有异步的操作(比如ajax读取服务器数据),也有同步的操作(比如遍历一个大型数组),它们都不是立即能得到结果的。

通常的做法是,为它们指定回调函数(callback)。即事先规定,一旦它们运行结束,应该调用哪些函数。

但是,在回调函数方面,jQuery的功能非常弱。为了改变这一点,jQuery开发团队就设计了deferred对象。

简单说,deferred对象就是jQuery的回调函数解决方案。在英语中,defer的意思是"延迟",所以deferred对象的含义就是"延迟"到未来某个点再执行。

回顾一下jQuery的ajax操作的传统写法:

代码如下:

$.ajax({
   url: "test.html",
   success: function(){
     alert("哈哈,成功了!");
   },
   error:function(){
     alert("出错啦!");
   }
 });


在上面的代码中,$.ajax()接受一个对象参数,这个对象包含两个方法:success方法指定操作成功后的回调函数,error方法指定操作失败后的回调函数。

$.ajax()操作完成后,如果使用的是低于1.5.0版本的jQuery,返回的是XHR对象,你没法进行链式操作;如果高于1.5.0版本,返回的是deferred对象,可以进行链式操作。

现在,新的写法是这样的:

代码如下:

$.ajax("test.html")
  .done(function(){ alert("哈哈,成功了!"); })
  .fail(function(){ alert("出错啦!"); });


为多个操作指定回调函数

deferred对象的另一大好处,就是它允许你为多个事件指定一个回调函数,这是传统写法做不到的。

请看下面的代码,它用到了一个新的方法$.when():

 代码如下:

$.when($.ajax("test1.html"), $.ajax("test2.html"))
 .done(function(){ alert("哈哈,成功了!"); })
 .fail(function(){ alert("出错啦!"); });

这段代码的意思是,先执行两个操作$.ajax("test1.html")和$.ajax("test2.html"),如果都成功了,就运行done()指定的回调函数;如果有一个失败或都失败了,就执行fail()指定的回调函数。

jQuery.Deferred( func ) 的实现原理

内部维护了三个回调函数列表:成功回调函数列表、失败回调函数列表、消息回调函数列表,其他方法则围绕这三个列表进行操作和检测。

jQuery.Deferred( func ) 的源码结构:

代码如下:

 jQuery.extend({

    Deferred: function( func ) {
            // 成功回调函数列表
        var doneList = jQuery.Callbacks( "once memory" ),
            // 失败回调函数列表
            failList = jQuery.Callbacks( "once memory" ),
            // 消息回调函数列表
            progressList = jQuery.Callbacks( "memory" ),
            // 初始状态
            state = "pending",
            // 异步队列的只读副本
            promise = {
                // done, fail, progress
                // state, isResolved, isRejected
                // then, always
                // pipe
                // promise           
            },
            // 异步队列
            deferred = promise.promise({}),
            key;
        // 添加触发成功、失败、消息回调函列表的方法
        for ( key in lists ) {
            deferred[ key ] = lists[ key ].fire;
            deferred[ key + "With" ] = lists[ key ].fireWith;
        }
        // 添加设置状态的回调函数
        deferred.done( function() {
            state = "resolved";
        }, failList.disable, progressList.lock )
        .fail( function() {
            state = "rejected";
        }, doneList.disable, progressList.lock );
        // 如果传入函数参数 func,则执行。
        if ( func ) {
            func.call( deferred, deferred );
        }

        // 返回异步队列 deferred
        return deferred;
    },
}

jQuery.when( deferreds )

提供了基于一个或多个对象的状态来执行回调函数的功能,通常是基于具有异步事件的异步队列。

jQuery.when( deferreds ) 的用法

如果传入多个异步队列对象,方法 jQuery.when() 返回一个新的主异步队列对象的只读副本,只读副本将跟踪所传入的异步队列的最终状态。

一旦所有异步队列都变为成功状态,“主“异步队列的成功回调函数被调用;

如果其中一个异步队列变为失败状态,主异步队列的失败回调函数被调用。

 代码如下:

/*
请求 &#39;/when.do?method=when1&#39; 返回 {"when":1}
请求 &#39;/when.do?method=when2&#39; 返回 {"when":2}
请求 &#39;/when.do?method=when3&#39; 返回 {"when":3}
*/
var whenDone = function(){ console.log( &#39;done&#39;, arguments ); },
    whenFail = function(){ console.log( &#39;fail&#39;, arguments ); };
$.when(
    $.ajax( &#39;/when.do?method=when1&#39;, { dataType: "json" } ),
    $.ajax( &#39;/when.do?method=when2&#39;, { dataType: "json" } ),
    $.ajax( &#39;/when.do?method=when3&#39;, { dataType: "json" } )
).done( whenDone ).fail( whenFail );

图片描述

异步队列 Deferred

解耦异步任务和回调函数

为 ajax 模块、队列模块、ready 事件提供基础功能。

原型属性和方法

原型属性和方法源代码:

 代码如下:

  97 jQuery.fn = jQuery.prototype = {
  98     constructor: jQuery,
  99     init: function( selector, context, rootjQuery ) {}
 210     selector: "",
 213     jquery: "1.7.1",
 216     length: 0,
 219     size: function() {},
 223     toArray: function() {},
 229     get: function( num ) {},
 241     pushStack: function( elems, name, selector ) {},
 270     each: function( callback, args ) {},
 274     ready: function( fn ) {}, //
 284     eq: function( i ) {},
 291     first: function() {},
 295     last: function() {},
 299     slice: function() {},
 304     map: function( callback ) {},
 310     end: function() {},
 316     push: push,
 317     sort: [].sort,
 318     splice: [].splice
 319 };

 

属性selector用于记录jQuery查找和过滤DOM元素时的选择器表达式。
属性.length表示当前jquery对象中元素的个数。
方法.size()返回当前jquery对象中元素的个数,功能上等同于属性length,但应该优先使用length,因为他没有函数调用开销。

.size()源码如下:

 代码如下:

size():function(){
    return this.length;
}

方法.toArray()将当前jQuery对象转换为真正的数组,转换后的数组包含了所有元素,其源码如下:

代码如下:

toArray: function() {
        return slice.call( this );
    },

方法.get(index)返回当前jQuery对象中指定位置的元素,或包含了全部元素的数组。其源
码如下:

 代码如下:

 get: function( num ) {
        return num == null ?

            // Return a &#39;clean&#39; array
            this.toArray() :

            // Return just the object
            ( num < 0 ? this[ this.length + num ] : this[ num ] );
    },

如果没有传入参数,则调用.toArray()返回了包含有锁元素的数组;如果指定了参数index,则返回一个单独的元素,index从0开始计数,并且支持负数。

首先会判断num是否小于0,如果小于0,则用length+num重新计算下标,然后使用数组访问操作符([])获取指定位置的元素,这是支持下标为负数的一个小技巧;如果大于等于0,直接返回指定位置的元素。

eg()和get()使用详解:jquery常用方法及使用示例汇总

方法.each()用于遍历当前jQuery对象,并在每个元素上执行回调函数。方法.each()内部通过简单的调用静态方法jQuery.each()实现:

 代码如下:

each: function( callback, args ) {
        return jQuery.each( this, callback, args );
    },

回调函数是在当前元素为上下文的语境中触发的,即关键字this总是指向当前元素,在回调函数中return false 可以终止遍历。

方法.map()遍历当前jQuery对象,在每个元素上执行回调函数,并将回调函数的返回值放入一个新jQuery对象中。该方法常用于获取或设置DOM元素集合的值。

 代码如下:

map: function( callback ) {
        return this.pushStack( jQuery.map(this, function( elem, i ) {
            return callback.call( elem, i, elem );
        }));
    },

原型方法.pushStack()创建一个新的空jQuery对象,然后把DOM元素集合放入这个jQuery对象中,并保留对当前jQuery对象的引用。

原型方法.pushStack()是核心方法之一,它为以下方法提供支持:

jQuery对象遍历:.eq()、.first()、.last()、.slice()、.map()。

DOM查找、过滤:.find()、.not()、.filter()、.closest()、.add()、.andSelf()。

DOM遍历:.parent()、.parents()、.parentsUntil()、.next()、.prev()、.nextAll()、.prevAll()、.nextUnit()、.prevUnit()、.siblings()、.children()、.contents()。

DOM插入:jQuery.before()、jQuery.after()、jQuery.replaceWith()、.append()、.prepent()、.before()、.after()、.replaceWith()。
定义方法.push( elems, name, selector ),它接受3个参数:

参数elems:将放入新jQuery对象的元素数组(或类数组对象)。

参数name:产生元素数组elems的jQuery方法名。

参数selector:传给jQuery方法的参数,用于修正原型属性.selector。
方法.end()结束当前链条中最近的筛选操作,并将匹配元素还原为之前的状态

代码如下:

end: function() {
        return this.prevObject || this.constructor(null);
    },

返回前一个jQuery对象,如果属性prevObect不存在,则构建一个空的jQuery对象返回。方法.pushStack()用于入栈,方法.end()用于出栈

静态属性和方法

相关源码如下:

代码如下:

388 jQuery.extend({
 389     noConflict: function( deep ) {},
 402     isReady: false,
 406     readyWait: 1,
 409     holdReady: function( hold ) {},
 418     ready: function( wait ) {},
 444     bindReady: function() {},
 492     isFunction: function( obj ) {},
 496     isArray: Array.isArray || function( obj ) {},
 501     isWindow: function( obj ) {},
 505     isNumeric: function( obj ) {},
 509     type: function( obj ) {},
 515     isPlainObject: function( obj ) {},
 544     isEmptyObject: function( obj ) {},
 551     error: function( msg ) {},
 555     parseJSON: function( data ) {},
 581     parseXML: function( data ) {},
 601     noop: function() {},
 606     globalEval: function( data ) {},
 619     camelCase: function( string ) {},
 623     nodeName: function( elem, name ) {},
 628     each: function( object, callback, args ) {},
 669     trim: trim ? function( text ) {} : function( text ) {},
 684     makeArray: function( array, results ) {},
 702     inArray: function( elem, array, i ) {},
 724     merge: function( first, second ) {},
 744     grep: function( elems, callback, inv ) {},
 761     map: function( elems, callback, arg ) {},
 794     guid: 1,
 798     proxy: function( fn, context ) {},
 825     access: function( elems, key, value, exec, fn, pass ) {},
 852     now: function() {},
 858     uaMatch: function( ua ) {},
 870     sub: function() {},
 891     browser: {}
 892 });

 

未完待续、、、今天就先到这里了,下次补齐。别急哈小伙伴们

以上是實例詳解jquery的整體架構的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn