1. 程式人生 > >一步一步DIY zepto庫,研究zepto原始碼4 -- ajax模組

一步一步DIY zepto庫,研究zepto原始碼4 -- ajax模組

上面的博文介紹的都是原始碼src下的基礎模組zepto.js檔案和事件模組event.js,下面接著看另外一個獨立的模組–ajax模組ajax.js

1.ajax的過程

  • 當global: true時。在Ajax請求生命週期內,以下這些事件將被觸發。
  • ajaxStart (global):如果沒有其他Ajax請求當前活躍將會被觸發。
  • ajaxBeforeSend (data: xhr, options):再發送請求前,可以被取消。
  • ajaxSend (data: xhr, options):像 ajaxBeforeSend,但不能取消。
  • ajaxSuccess (data: xhr, options, data):當返回成功時。
  • ajaxError (data: xhr, options, error):當有錯誤時。
  • ajaxComplete (data: xhr, options):請求已經完成後,無論請求是成功或者失敗。
  • ajaxStop (global):如果這是最後一個活躍著的Ajax請求,將會被觸發。

下面我們就首先來看這些過程的原始碼:
- 實現邏輯函式:

// trigger a custom event and return false if it was cancelled
function triggerAndReturn(context, eventName, data) {
    var
event = $.Event(eventName); //包裝成事件 $(context).trigger(event, data); //觸發 return !event.isDefaultPrevented(); } // trigger an Ajax "global" event function triggerGlobal(settings, context, eventName, data) { if (settings.global) return triggerAndReturn(context || document, eventName, data); } // Number of active Ajax requests
// 傳送中的ajax請求個數 $.active = 0; //如果沒有其他Ajax請求當前活躍將會被觸發
  • ajaxStart
//如果沒有其他Ajax請求當前活躍將會被觸發
function ajaxStart(settings) {
   if (settings.global && $.active++ === 0) triggerGlobal(settings, null, 'ajaxStart');
}
  • ajaxBeforeSend
// triggers an extra global event "ajaxBeforeSend" that's like "ajaxSend" but cancelable
// 觸發選項中beforeSend回撥函式和觸發ajaxBeforeSend事件
// 上述的兩步中的回撥函式中返回false可以停止傳送ajax請求,否則就觸發ajaxSend事件
function ajaxBeforeSend(xhr, settings) {
    var context = settings.context;
    if (settings.beforeSend.call(context, xhr, settings) === false ||
        triggerGlobal(settings, context, 'ajaxBeforeSend', [xhr, settings]) === false)
        return false;

    triggerGlobal(settings, context, 'ajaxSend', [xhr, settings]);
}
  • ajaxSuccess
function ajaxSuccess(data, xhr, settings, deferred) {
    var context = settings.context,
        status = 'success';
    settings.success.call(context, data, status, xhr);
    if (deferred) deferred.resolveWith(context, [data, status, xhr]);
    triggerGlobal(settings, context, 'ajaxSuccess', [xhr, settings, data]);
    ajaxComplete(status, xhr, settings);
}
  • ajaxError
// type: "timeout", "error", "abort", "parsererror"
function ajaxError(error, type, xhr, settings, deferred) {
    var context = settings.context;
    settings.error.call(context, xhr, type, error);
    if (deferred) deferred.rejectWith(context, [xhr, type, error]);
    triggerGlobal(settings, context, 'ajaxError', [xhr, settings, error || type]);
    ajaxComplete(type, xhr, settings);
}
  • ajaxComplete
// status: "success", "notmodified", "error", "timeout", "abort", "parsererror"
function ajaxComplete(status, xhr, settings) {
    var context = settings.context;
    settings.complete.call(context, xhr, status);
    triggerGlobal(settings, context, 'ajaxComplete', [xhr, settings]);
    ajaxStop(settings);
}
  • ajaxStop
// 所有ajax請求都完成後才觸發
function ajaxStop(settings) {
    if (settings.global && !(--$.active)) triggerGlobal(settings, null, 'ajaxStop');
}

我們注意到,我們只是自定義了ajaxXXX事件,並沒有實際的意義,這時候我們就需要將這些事件的邏輯實現。這部分邏輯放在了$.ajax中,在適當的時候觸發事件,這些很值得我們去思考的它的巧妙。

2.$.ajax

2.1 全域性變數,工具函式定義:

var jsonpID = +new Date(),
    document = window.document,
    key,
    name,
    rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, //用來除掉html程式碼中的<script>標籤
    scriptTypeRE = /^(?:text|application)\/javascript/i,
    xmlTypeRE = /^(?:text|application)\/xml/i, //用來判斷是不是js的mime
    jsonType = 'application/json',
    htmlType = 'text/html',
    blankRE = /^\s*$/,
    originAnchor = document.createElement('a');
originAnchor.href = window.location.href;

// Empty function, used as default callback
// 空函式,被用作預設的回撥函式
function empty() {}
function mimeToDataType(mime) {
    if (mime) mime = mime.split(';', 2)[0];
    return mime && (mime == htmlType ? 'html' :
        mime == jsonType ? 'json' :
        scriptTypeRE.test(mime) ? 'script' :
        xmlTypeRE.test(mime) && 'xml') || 'text';
}
//把引數新增到url上
function appendQuery(url, query) {
    if (query == '') return url;
    return (url + '&' + query).replace(/[&?]{1,2}/, '?');
    //將&、&&、&?、?、?、&?&? 轉化為 ?
}

2.2 $.ajaxSetting

$.ajaxSettings = {
    type: 'GET',
    beforeSend: empty,
    success: empty,
    error: empty,
    complete: empty,
    context: null,
    global: true,
    xhr: function() {
        return new window.XMLHttpRequest();
    },
    accepts: {
        script: 'text/javascript, application/javascript, application/x-javascript',
        json: jsonType,
        xml: 'application/xml, text/xml',
        html: htmlType,
        text: 'text/plain'
    },
    crossDomain: false,
    timeout: 0,
    processData: true,
    cache: true,
    dataFilter: empty
};

2.3 序列化

// 列化data引數,並且如果是GET方法的話把引數新增到url引數上
function serializeData(options) {
    //options.data是個物件
    if (options.processData && options.data && $.type(options.data) != "string")
        console.info(options.data);
    options.data = $.param(options.data, options.traditional);

    // 請求方法為GET,data引數新增到url上
    if (options.data && (!options.type || options.type.toUpperCase() == 'GET' || 'jsonp' == options.dataType))
        options.url = appendQuery(options.url, options.data), options.data = undefined;
}
var escape = encodeURIComponent;
//序列化
//在Ajax post請求中將用作提交的表單元素的值編譯成 URL編碼的 字串
function serialize(params, obj, traditional, scope) {
    var type, array = $.isArray(obj),
        hash = $.isPlainObject(obj);
    // debugger;

    $.each(obj, function(key, value) {
        type = $.type(value);

        if (scope) {
            key = traditional ? scope :
                scope + '[' + (hash || type == 'object' || type == 'array' ? key : '') + ']';
        }
        // handle data in serializeArray() format
        if (!scope && array) { //obj是個陣列
            params.add(value.name, value.value);
        }
        // obj的value是個陣列/物件
        else if (type == "array" || (!traditional && type == "object")) {
            serialize(params, value, traditional, key);
        } else {
            params.add(key, value);
        }
    });
}
$.param = function(obj, traditional) {
    var params = [];
    //serialize函式使用add
    params.add = function(key, value) {
        if ($.isFunction(value)) value = value();
        if (value == null) value = "";
        this.push(escape(key) + '=' + escape(value));
    };
    serialize(params, obj, traditional); //處理obj
    return params.join('&').replace(/%20/g, '+');
};

常用方法:

函式/屬性 作用
setRequestHeader() 給指定的HTTP請求頭賦值.在這之前,你必須確認已經呼叫 open() 方法打開了一個url
overrideMimeType() 重寫由伺服器返回的MIME type
onreadystatechange readyState屬性改變時會呼叫它
open() 初始化一個請求
send() 傳送請求. 如果該請求是非同步模式(預設),該方法會立刻返回. 相反,如果請求是同步模式,則直到請求的響應完全接受以後,該方法才會返回

readyState的狀態:

狀態 描述
0 UNSENT(未開啟) open()方法還未被呼叫.
1 OPENED (未傳送) send()方法還未被呼叫.
2 HEADERS_RECEIVED (已獲取響應頭) send()方法已經被呼叫, 響應頭和響應狀態已經返回.
3 LOADING (正在下載響應體) 響應體下載中; responseText中已經獲取了部分資料.
4 DONE (請求完成) 整個請求過程已經完畢.

2.5 $.ajax實現

$.ajax = function(options) {
    var settings = $.extend({}, options || {}),
        deferred = $.Deferred && $.Deferred(),
        urlAnchor, hashIndex;
    for (key in $.ajaxSettings)
        if (settings[key] === undefined) settings[key] = $.ajaxSettings[key];

    ajaxStart(settings); //[email protected]開始
    // 如果沒有傳入crossDomain引數,就通過檢測setting.url和網址的protocol、host是否一致判斷該請求是否跨域
    if (!settings.crossDomain) {
        // 通過設定a元素的href就可以很方便的獲取一個url的各組成部分
        urlAnchor = document.createElement('a');
        urlAnchor.href = settings.url;
        // cleans up URL for .href (IE only), see https://github.com/madrobby/zepto/pull/1049
        urlAnchor.href = urlAnchor.href;
        settings.crossDomain = (originAnchor.protocol + '//' + originAnchor.host) !== (urlAnchor.protocol + '//' + urlAnchor.host);
    }
    // 沒有傳入url引數,使用網站的網址為url引數
    // window.location.toString() 等於 window.location.href
    if (!settings.url) settings.url = window.location.toString();
    //去掉url上的hash部分
    if ((hashIndex = settings.url.indexOf('#')) > -1) settings.url = settings.url.slice(0, hashIndex);
    serializeData(settings); // 序列化data引數,並且如果是GET方法的話把引數新增到url引數上
    var dataType = settings.dataType,
        hasPlaceholder = /\?.+=\?/.test(settings.url); // 判斷url引數是否包含=?
    if (hasPlaceholder) dataType = 'jsonp'; //jsonp url 舉例http://www.xxx.com/xx.php?callback=?

    // 設定了cache引數為false,或者cache引數不為true而且請求資料的型別是script或jsonp,就在url上新增時間戳防止瀏覽器快取
    // (cache設定為true也不一定會快取,具體要看快取相關的http響應首部)
    if (settings.cache === false || (
            (!options || options.cache !== true) &&
            ('script' == dataType || 'jsonp' == dataType)
        ))
        settings.url = appendQuery(settings.url, '_=' + Date.now());

    // jsonp呼叫$.ajaxJSONP實現
    if ('jsonp' == dataType) {
        if (!hasPlaceholder)
            settings.url = appendQuery(settings.url,
                settings.jsonp ? (settings.jsonp + '=?') : settings.jsonp === false ? '' : 'callback=?');
        return $.ajaxJSONP(settings, deferred);
    }

    // 下面程式碼用來設定請求的頭部、相應的mime型別等
    var mime = settings.accepts[dataType],
        headers = {},
        setHeader = function(name, value) { headers[name.toLowerCase()] = [name, value]; },
        protocol = /^([\w-]+:)\/\//.test(settings.url) ? RegExp.$1 : window.location.protocol,
        xhr = settings.xhr(), //XMLHttpRequest
        nativeSetHeader = xhr.setRequestHeader,
        abortTimeout;

    if (deferred) deferred.promise(xhr);

    //不跨域
    if (!settings.crossDomain) setHeader('X-Requested-With', 'XMLHttpRequest');
    setHeader('Accept', mime || '*/*');
    if (mime = settings.mimeType || mime) {
        if (mime.indexOf(',') > -1) mime = mime.split(',', 2)[0];
        //重寫由伺服器返回的MIME type  注意,這個方法必須在send()之前被呼叫
        xhr.overrideMimeType && xhr.overrideMimeType(mime);
    }
    //設定contentType
    if (settings.contentType || (settings.contentType !== false && settings.data && settings.type.toUpperCase() != 'GET'))
        setHeader('Content-Type', settings.contentType || 'application/x-www-form-urlencoded');
    //如果配置中有對headers內容
    if (settings.headers)
        for (name in settings.headers) setHeader(name, settings.headers[name]);
    xhr.setRequestHeader = setHeader; //設定頭資訊

    xhr.onreadystatechange = function() {
        if (xhr.readyState == 4) { //請求完成
            xhr.onreadystatechange = empty;
            clearTimeout(abortTimeout);
            var result, error = false;
            //請求成功
            //在本地調動ajax,也就是請求url以file開頭,也代表請求成功
            if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304 || (xhr.status == 0 && protocol == 'file:')) {
                dataType = dataType || mimeToDataType(settings.mimeType || xhr.getResponseHeader('content-type'));

                // 根據xhr.responseType和dataType處理返回的資料
                if (xhr.responseType == 'arraybuffer' || xhr.responseType == 'blob') {
                    result = xhr.response;
                } else {
                    result = xhr.responseText;

                    try {
                        // http://perfectionkills.com/global-eval-what-are-the-options/
                        // (1,eval)(result) 這樣寫還可以讓result裡面的程式碼在全域性作用域裡面執行
                        result = ajaxDataFilter(result, dataType, settings);
                        if (dataType == 'script') {
                            (1, eval)(result);
                        } else if (dataType == 'xml') {
                            result = xhr.responseXML;
                        } else if (dataType == 'json') {
                            result = blankRE.test(result) ? null : $.parseJSON(result);
                        }
                    } catch (e) { error = e; }

                    if (error) return ajaxError(error, 'parsererror', xhr, settings, deferred);//[email protected]
                }

                ajaxSuccess(result, xhr, settings, deferred);//[email protected]
            } else {
                ajaxError(xhr.statusText || null, xhr.status ? 'error' : 'abort', xhr, settings, deferred);//[email protected]
            }
        }
    };
    //必須send()之前//[email protected]
    if (ajaxBeforeSend(xhr, settings) === false) {
        xhr.abort();
        ajaxError(null, 'abort', xhr, settings, deferred);
        return xhr;
    }

    var async = 'async' in settings ? settings.async : true;
    /**
     * void open(
           DOMString method,
           DOMString url,
           optional boolean async,
           optional DOMString user,
           optional DOMString password
        );
     */
    xhr.open(settings.type, settings.url, async, settings.username, settings.password);

    if (settings.xhrFields)
        for (name in settings.xhrFields) xhr[name] = settings.xhrFields[name];

    for (name in headers) nativeSetHeader.apply(xhr, headers[name]);

    // 超時丟棄請求
    if (settings.timeout > 0) abortTimeout = setTimeout(function() {
        xhr.onreadystatechange = empty;
        xhr.abort();
        ajaxError(null, 'timeout', xhr, settings, deferred);
    }, settings.timeout);

    // avoid sending empty string (#319)
    xhr.send(settings.data ? settings.data : null);
    return xhr;
};

2.6 示例Demo

ajax讀取本地的json,安裝個小伺服器,或者使用其他伺服器,如apache,tomcat等。

npm install anywhere -g      //安裝
anywhere 8860                //開啟埠(任意沒被佔用的)為伺服器使用
$.ajax({
    type: 'GET',
    url: '/projects.json',
    data: {
        name: 'Hello'
    },
    dataType: 'json',
    timeout: 300,
    success: function(data) {
        console.log(data);
    },
    error: function(xhr, type) {
        alert('Ajax error!');
    }
})

3.jsonp

使用jsonp跨域的核心思想是:

script = document.createElement('script')
script.src = options.url.replace(/\?(.+)=\?/, '?$1=' + callbackName);
document.head.appendChild(script)

具體實現的程式碼:

$.ajaxJSONP = function(options, deferred) {
    // 沒有type選項,呼叫$.ajax實現
    if (!('type' in options)) return $.ajax(options);

    var _callbackName = options.jsonpCallback,
        //options配置寫了jsonpCallback,那麼回撥函式的名字就是options.jsonpCallback
        //沒有就是'Zepto' + (jsonpID++)
        callbackName = ($.isFunction(_callbackName) ?
            _callbackName() : _callbackName) || ('Zepto' + (jsonpID++)),
        script = document.createElement('script'),
        originalCallback = window[callbackName],
        responseData,
        abort = function(errorType) {
            $(script).triggerHandler('error', errorType || 'abort');
        },
        xhr = { abort: abort },
        abortTimeout;

    if (deferred) deferred.promise(xhr);
    // 載入成功或者失敗觸發相應的回撥函式
    // load error 在event.js
    $(script).on('load error', function(e, errorType) {
        clearTimeout(abortTimeout);
        // 載入成功或者失敗都會移除掉新增到頁面的script標籤和繫結的事件
        $(script).off().remove();

        if (e.type == 'error' || !responseData) { //失敗
            ajaxError(null, errorType || 'error', xhr, options, deferred);
        } else { //成功
            ajaxSuccess(responseData[0], xhr, options, deferred);
        }

        window[callbackName] = originalCallback;
        if (responseData && $.isFunction(originalCallback))
            originalCallback(responseData[0]);

        originalCallback = responseData = undefined;
    });
    // 在beforeSend回撥函式或者ajaxBeforeSend事件中返回了false,取消ajax請求
    if (ajaxBeforeSend(xhr, options) === false) {
        abort('abort');
        return xhr;
    }

    window[callbackName] = function() {
        responseData = arguments;
    };
    // 引數中新增上變數名
    script.src = options.url.replace(/\?(.+)=\?/, '?$1=' + callbackName);
    document.head.appendChild(script);
    //超時處理
    if (options.timeout > 0) abortTimeout = setTimeout(function() {
        abort('timeout');
    }, options.timeout);

    return xhr;
};

4.get、post、getJSON、load 方法

篇幅所限,這裡就不再單列出來了,他們都是處理好引數,呼叫$.ajax方法。詳細的實現方式去我的github 下載。

相關推薦

DIY zepto研究zepto原始碼3 -- event模組

上面的博文介紹的都是原始碼src下的zepto.js檔案,接著我們來看看zepto的事件模組,對應檔案是event.js 1.繫結事件 例項Demo <div id="foo1">foo1</div> &

DIY zepto研究zepto原始碼4 -- ajax模組

上面的博文介紹的都是原始碼src下的基礎模組zepto.js檔案和事件模組event.js,下面接著看另外一個獨立的模組–ajax模組ajax.js 1.ajax的過程 當global: true時。在Ajax請求生命週期內,以下這些事件將被觸發。

例mysql主從數據宕機後無法啟動的解決方案

mysql starting 啟動時報錯信息: Starting MySQL... ERROR! The server quit without updating PID file (/usr/local/mysql/data/qkzhi-appzookeeper-1.novalocal.pid

Light libraries是組通用的C基礎目標是為減少重復造輪子而寫(全部用POSIX C實現)

six clas 原子操作 roi 實現 class 動態庫 readme tps Light libraries是一組通用的C基礎庫,目標是為減少重復造輪子而寫實現了日誌、原子操作、哈希字典、紅黑樹、動態庫加載、線程、鎖操作、配置文件、os適配層、事件驅動、工作隊列、RP

如何在 webpack 中引入未模組化的Zepto

前言 最近我在研究多頁面 webpack 模組打包的完整解決方案時,發現用 import 匯入 Zepto 時,會報 Uncaught TypeError: Cannot read property 'createElement' of undefined 錯誤,

用Html5/CSS3做Winform教你搭建CefSharp開發環境(附JavaScript異步調用C#例子及全部源代碼)上

轉載 界面設計 右鍵 異步 一個 由於 編寫 scrip 調用 本文為雞毛巾原創,原文地址:http://www.cnblogs.com/jimaojin/p/7077131.html,轉載請註明 CefSharp說白了就是Chromium瀏覽器的嵌入式核心,我們用此開發W

1.學開發(遊戲賬服數據的使用 Erlang 服務器)

http ats 日誌收集 yield data obj 開發 用戶 nbsp mysql 與mongodb的特點與優劣 http://www.cnblogs.com/eternal1025/p/5419905.html 首先我們來分析下mysql 與mongodb的特

有印為證

有物為證 之前一段時間告誡自己和家人:"路很長也很短,很短也很長,每天進步一點點,自律",最近沈下心態,思索最近半年的工作經歷和當下生活狀態,借用馬未都先生節目中的一句話“觀復嘟嘟,有物為證”稍作修改來描述近來心態變化,那就是“一步一腳印,有印為證”,思考良久其實二者的關系並不矛盾,之前強調的是放慢角度

利用 Siblings實現多個同級div只改變當前點擊的div樣式

ima 可選參數 cto 一個 五個 點擊 rem wid bin 記錄一點,小技巧。直接上代碼嘍,因為今天還沒有功夫扯皮呢。 <!DOCTYPE html> <html> <head&g

微信小程序的認證流程極限工坊教會你

nag 進行 text 根據 服務號 登陸 dad 訂閱號 手機號碼 微信小程序作為一種輕巧靈活的手機應用,改變著手機互聯網形態的同時,也在改變著我們的生活方式。 下面淘小咖具體教大家如何輕松快捷註冊小程序,看圖教程一步一步來! 1、小程序賬號註冊 百度搜索或者直接在公眾平

U盤安裝蘋果系統教程菜鳥也能成大牛

女朋友蘋果電腦遇到問題,女朋友不會整,又怕自己整壞了,就跑去電腦城修,結果被坑了150塊,本來說要雙系統Mac和Windows,結果店家拿著電腦就跑出去了(並不知道幹啥去了),回來發現只有win7,沒有Mac,並且索要正版的價格,我尼瑪,就坑我小女朋友,還好小女朋友沒給他,本來就是盜版系

Spring的前世今生: Spring5.0已經出來了Springboot已經風靡全球 Spring怎麼走過來的 讓我們看看其前世今生~~~

 Spring5.0已經出來了,Springboot已經風靡全球, Spring怎麼一步一步走過來的, 讓我們看看其前世今生~~~   這是關於什麼是Spring Framework,但它是如何開發的?嗯,背後有一個有趣的歷史。讓我們一瞥Spring Framework的

中國正攜區塊鏈下新金融的大棋深圳極可能成為落子的第一

當眾人還在為加密貨幣的漲跌而喋喋不休時,中國已悄然攜區塊鏈下一盤關乎中國新金融新經濟的大棋,而深圳極有可能成為落子的第一步。   金融業務一直被視為區塊鏈技術的第一大應用,8月中旬以來,儘管監管層接連收緊了對加密貨幣的監管,但對區塊鏈在金融創新上的推動卻始終低調而急促,國

Jquery 分頁外掛 帶你接入後臺資料

目錄   一、效果圖 二、分頁 js 原始碼講解 三、分頁樣式 css 原始碼 三、實現前後臺對接 一、效果圖 二、分頁 js 原始碼講解 新建一個 js 檔案,基本直接複製貼上就行,記得引入到需要的頁面中。 需要注意的是: 前面的建構函式

抖音、吃雞、王者榮耀:你的自律是如何被頂級產品經理毀掉的

  作者:書單君 來源:書單(ID:BookSelection)   01 你的沉迷 跟這個時代有關   這是個特別容易沉迷的時代。   抖音、煲劇、王者榮耀、吃雞遊戲……你的時間和注意力悄悄被它們偷走,卻從不說再

(原創)超詳細在eclipse中配置Struts2環境無基礎也能看懂

    (原創)超詳細一步一步在eclipse中配置Struts2環境,無基礎也能看懂  1. 在官網https://struts.apache.org下載Struts2,建議下載2.3系列版本。從圖中可以看出,既可以分開下載子檔案,又可以一次全部下載。 這裡我後

分享我這8年是如何走向架構師的

摘要:心血經驗分享,架構師更多的是一個不斷學習,不斷積累的過程,希望可以幫到同行業的朋友們 前言 成為優秀的架構師是大部分初中級工程師的階段性目標。優秀的架構師往往具備七種核心能力:程式設計能力、除錯能力、編譯部署能力、效能優化能力、業務架構能力、線上運維能力、專案管理能力和規劃能力。 這幾種能力之

自定義實現SpringMvc框架自定義@Controller、@RequestMapping註解自己也是的對程式碼的理解出來的只是比較簡單的例子

1.自定義的DispatcherServlet,就是SpringMvc執行載入類 /*** * 手寫SpringMvc框架<br> * 思路:<br> * 1.手動建立一個DispatcherServlet 攔截專案的所有請求 SpringMv

stm8 觸控使用教程

配套的檔案資料會上傳 1、檢視觸控庫說明文件,根據需要選取晶片(主要支援幾個鍵):STMTouch Driver User Manual 一個channel為一個按鈕,比如STM8L101F 就支援3個按鈕,通過按鈕擴充方式可以增加按鈕,參照: 增加觸控感測按鈕數量