1. 程式人生 > >jQuery原始碼分析系列(30) : Ajax 整體結構

jQuery原始碼分析系列(30) : Ajax 整體結構

開頭引用一段

想起一句話:前端研究,研究個屁~ 的確如此呀。補充下聯:前端設計,設計個屁~

前端目前最大的困境是,如 HTML 一樣,無論你承不承認,市場上並不太需要 HTML 高手

其實這裡引發一個問題:前端的價值究竟是什麼?未來應該如何發展?

我個人覺得還是一個核心價值的問題,前端在漂亮的東西都是需要後端的資料支撐的,而且前端的絕大部分問題,其實都需要後端才能解決,就如我開發了三年的混合專案,雖然前端程式碼有3萬行,但是後端一個數據拷貝失敗,整個專案都是白搭

當然這裡我並非要深入這個話題,只是想表明儘可能的提高自身的價值

從前端的歷史變更與發展過程來看,如果拿人類的歷史來比喻,從最原始的刀耕火種以HTML靜態結構為主的原始社會,到後來帶有邏輯處理與伺服器混搭互動的的石器時代以及最近幾年流行的node.js,MVC,MVVM,Phonegap等衍生而來的東東讓前端大躍進式的跳躍進入了一個新的工業時代,不到20年的時間web技術翻天覆地的變化

如今的前端進入了 MV* 時代,同時Ajax也帶來了SPA,Node帶來了全棧,這些都切實推動著前端往前發展

優秀的前端一方面是完成的效率,另一方面是可維護和可擴充套件性的提高帶來的潛在價值,解放生產力,專注展現與業務邏輯,減少瑣碎的相容處理

如果你使用一面大鏡子作為衝浪板會發生什麼?或許你會在較短的時間內征服海浪,但是你肯定從內心深處明白,這不是衝浪的正確選擇

所以我們不應該只要求自己僅僅停留在API的層次,如果能理解原理深入設計,那麼就將會跨上一個新的臺階

jQuery.Ajax做了那些事?

我們知道AJAX的底層實現其實是很簡單的.拋開IE不說,標準的w3c直接提供了XMLHttpRequest方法

我們主要站在設計的角度理解,如何設計出低耦合高內聚的程式碼

jQuery對Ajax的處理主要體現在對瀏覽器相容,資料的處理及過濾,各種事件的封裝上

主要有以下幾部分擴充套件:

提供快捷介面

提供底層介面

提供資料序列化

提供全域性Ajax事件處理

具體使用請看API,這裡不再重複

分析下面一個demo

給document繫結ajaxStart,ajaxComplete回撥事件

trigger繫結一個點選事件,傳送ajax請求

點選trigger出發點之後,傳送一個ajax請求,並且通過complete,done,ajaxStart, ajaxComplete返回狀態回撥

//全域性事件觸發
$(document).ajaxStart(function() {
    console.log(arguments)
}).ajaxComplete(function() {
    $(".log").text("Triggered ajaxComplete handler.");
});


$(".trigger").click(function() {
    //傳送ajax請求
    //
    $.ajax({
        url: "php.html",
        context: document.body,
        complete: function() {
            console.log(this)
        }
    }).done(function() {
        console.log(this)
    });
});

這裡實現比較特別的地方

針對ajax提供2種回撥方式,內部的complete回撥與外部的done回撥

全域性document上都能捕獲到ajax的每一步的回撥通知

換句話說,針對ajax的請求,每一步的狀態,成功或者失敗,我們有3種方式可以獲取,但是每一種還是有各自的區別

1 ajax的引數回撥

2 基於deferred方式的done回撥

3 全域性的的自定義事件的回撥

從設計的層面上來考下,這種事件組合的方式是如何實現?有什麼優勢?

設計一:

tAjax({
    url: "php.html",
    complete: function(data) {
        console.log(data)
    }
})

如果要實現這種介面呼叫

那麼我們就需要封裝下程式碼,把回撥通過實參傳遞

var tAjax = function(config) {
    var url      = config.url;
    var complete = config.complete; 
    var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');
    xhr.open('post', url);
    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
    xhr.onreadystatechange = function() {
        if (xhr.readyState == 4) {
            if (xhr.status == 200) {
                complete(xhr.responseText);
            }
        }
    }
    xhr.send();
}

這樣設計可以看做類似工廠模式的封裝,好處不用多說,在工廠模式裡面包含了物件的建立等必要的邏輯,客戶端根據傳參選擇動態的例項化相對的處理

對於客戶端來去除了具體的依賴,當然tAjax你也可以看作一個外觀模式提供的介面,其實就是隱藏了具體的複雜邏輯,提供一個簡單的介面,從而降低耦合

設計二:

tAjax({
    url: "php.html",
    complete: function(data) {
        console.log(data)
    }
}).done(function(data){
    console.log(data)
})

在之前加入了一個done鏈式處理,當然這裡done,其實是deferred的一個成功處理通知,如果之前沒有接觸,大家去了解一下關於deferred的概念

我們知道jQuery實現了鏈式,實現的原理無法就是返回本身物件的引用

var ajax = tAjax({
    url: "php.html",
    complete: function(data) {
        console.log(data)
    }
})

ajax.done(function(){
    
})

以上是分離的情況下,如果要合併成一條鏈式處理,只要在上一個方法中返回this即可

所以我們改改

var tAjax = function(config) {

    var doneFn;

    var url      = config.url;
    var complete = config.complete; 
    var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');
    xhr.open('post', url);
    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
    xhr.onreadystatechange = function() {
        if (xhr.readyState == 4) {
            if (xhr.status == 200) {
                doneFn(xhr.responseText);
                complete(xhr.responseText);
            }
        }
    }

    xhr.send(xhr.responseText);

    return {
        /**
         * 返回一個done物件
         */
        done: function(ourfn) {
             doneFn = ourfn;
        }
    };
}

我們返回了一個done物件,這裡一樣要是物件,因為鏈式的原因,我們看外部指定了內部的done,從而把外部函式給引用到內部的doneFn上 快取起來

xhr.staturs 成功後一起執行

當然這種設計是有問題的,如果在done之後我在鏈式就肯定不行,因為物件的引用錯了,那麼jQuery是如何處理?

設計三

提供document物件的全域性處理

$(document).ajaxComplete(function() {
    console.log('ajax請求成功')
});


tAjax({
    url: "php.html",
    complete: function(data) {
         console.log(data)
    }
}).done(function(data){
    console.log(data)
})

這裡的問題就是把ajax內部的事件,返回給全域性捕獲了,有點類似css3的全域性動畫事件

這裡的設計其實最簡單了因為本身之間沒有什麼耦合,就是傳送一個事件給document即可

jQuery利用了trigger自定義事件觸發的

globalEventContext.trigger("ajaxComplete", [jqXHR, s]);

具體每一種實現在後面的都會提到,在這裡需要大家有個整體的印象,

總結:

通過讀閱jQuery.ajax這段api我們可以看到jQuery對ajax的處理做的相當的全面

首先我們經常遇到某些耗時很長的javascript操作。其中,既有非同步的操作(比如ajax讀取伺服器資料),也有同步的操作(比如遍歷一個大型陣列),它們都不是立即能得到結果的。

通常的做法是,為它們指定回撥函式(callback)。即事先規定,一旦它們執行結束,應該呼叫哪些函式。

ajax引入了deferred方案,callback方案就是回撥函式解決方案,從而處理耗時操作的問題,對那些操作提供了更好的控制,以及統一的程式設計介面

伴隨Ajax請求會觸發若干事件,我們可以訂閱這些事件並在其中處理我們的邏輯。

在jQuery中有兩種Ajax事件:區域性事件和全域性事件。

區域性事件(回撥函式),在$.ajax()方法的options引數中宣告,可以用來設定請求資料和獲取、處理響應資料。

全域性事件,每次Ajax請求都會觸發,它會向DOM中的所有元素廣播,你只需為DOM中任意元素bind好全域性事件即會觸發(若繫結多次,則會依次觸發為事件註冊的回撥函式

除此之外,還提供了一系列的簡化介面,比如.load ,還有直接對資料物件序列化的能力,對跨域的處理,contentType的修復等等

深入待續。。。。。