zepto源码学习-05 ajax

学习zeptoajax之前需要先脑补下,强烈推荐此文http://www.cnblogs.com/heyuquan/archive/2013/05/13/js-jquery-ajax.html

还有Aaron 的jquery源码分析系列,讲jquery的ajax的部分,当然其他也可以看,很值得学习。

zepto ajax部分的设计相对简单,毕竟zepto就是轻量级的库,没有jqeury那样复杂,jquery ajax是依赖于Deferred模块的,整个代码一千多行。而zepto只有几百行,整体设计相对简单,ajax部分可以不依赖于Deferred模块,独立运行。zepto ajax的使用相见 地址,先看懂使用然后再分析具体实现。

先看一个demo

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <title>Ajax</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0">
    <meta content="telephone=no" name="format-detection">
    <meta name="apple-mobile-web-app-capable" content="yes" />
    <meta name="apple-mobile-web-app-status-bar-style" content="black" />
    <meta name="description" content="">
    <meta name="keywords" content="">
    <style type="text/css" media="screen">
        button{
            width:150px;
            height:100px;
            line-height:100px;
            margin:5px;
        }
    </style>
</head>
<body>
    <button type="" id="btnTest">testajax</button>
    <script type="text/javascript" src="../zepto-full-1.1.6.js"></script>
    <script>
        // 全局事件
        $(document).on(‘ajaxStart‘,function (e) {
                log(‘触发ajaxStart回调函数‘);
        }).on(‘ajaxSend‘,function (e) {
                log(‘触发ajaxSend回调函数‘);
        }).on(‘ajaxBeforeSend‘,function (e) {
                log(‘触发ajaxBeforeSend回调函数‘);
        }).on(‘ajaxSuccess‘,function (e, jqXHR, s, data) {
                log(‘触发ajaxSuccess回调函数‘);
        }).on(‘ajaxError‘,function (e, jqXHR, s, errorData) {
                log(‘触发ajaxError回调函数‘);
        }).on(‘ajaxComplete‘,function (e, jqXHR, s) {
                log(‘触发ajaxComplete回调函数‘);
        }).on(‘ajaxStop‘,function (e) {
                log(‘触发ajaxStop回调函数‘);
        });

        $(‘#btnTest‘).on(‘click‘,bindLocalEvent);
        // 局部事件
        function bindLocalEvent(e) {
                var textareaid =‘txt_event‘;             
                $.ajax({
                //跨域地址http://192.168.11.198:8080/myTest/test/ajax-page.php
                //ajax.php
                url:‘ajax.php‘,//ajax.php
                            type: ‘get‘,
                            dataType: ‘json‘,//text、json、jsonp
                data:{
                    msg:‘testtest‘,
                    error:3
                },
                        //global: true,
                        //cache: false,
                //jsonpCallback:log2,
                        beforeSend: function (jqXHR, s) {
                            log(‘触发beforeSend回调函数‘);
                        },
                //zepto 不支持
                        dataFilter: function (data, dataType) {
                            log(‘触发dataFilter回调函数‘);
                    return data;
                        },
                        success: function (data, statusText, jqXHR) {
                                log(‘触发success回调函数‘);
                        },
                        error: function (jqXHR, textStatus, errorThrown) {
                                log(‘触发error回调函数‘);
                        },
                        complete: function (jqXHR, textStatus) {
                                log(‘触发complete回调函数‘);
                        }}).done(function(){
                    console.log(‘触发ajax done‘,this,[].slice.call(arguments));
                });
        }

        function log(txt) {
            console.log(txt);
        }
        function log2(txt) {
            console.log(‘------------jsonpCallback------------------‘);
            //return function name
            return ‘log3‘;
        }
        function log3(txt) {
            console.log(‘------------jsonpCallback done------------------‘);
        }
    </script>
</body>
</html>

ajax demo

点击按钮,控制台输出如下

以上图片基本上展示了正常情况下zepto ajax的处理流程,测试代码我是引入了Deferred模块的,所以可以链式调用done(后续会说)。

XHR那行输出是我默认开启chrome的Log XMLHttpRequests,所有的XMLHttpRequests都会在控制台输出。

再看一个发生错误的输出,一旦请求发生错误,后续的处理流程将发生变化。

再看跨域时候的情况,这个测试我指定了jsonpCallback为log2,dataType为jsonp,地址为http://192.168.11.198:8080/myTest/test/ajax-page.php。ajax请求部分参数如下

url:‘http://192.168.11.198:8080/myTest/test/ajax-page.php‘,//ajax.php
            type: ‘get‘,
            dataType: ‘jsonp‘,//text、json、jsonp
data:{
    msg:‘testtest‘,
    error:3
},
jsonpCallback:log2,

对应的php代码如下

<?php
$status = $_REQUEST[‘error‘] ? 1 : 0;
$message = $status == 1 ? "验证码好像不对哦" : "SUCCESS";
if (!empty($_REQUEST[‘msg‘])) {
    $msg=$_REQUEST[‘msg‘];
        $message = $msg;
        $status  = $_REQUEST[‘error‘];
 }
$d=array("result"=>$status,"message"=>$message,"status"=>$_REQUEST[‘error‘]);
$c=$_REQUEST[‘callback‘];
$rd;
if ($c===null || $c===‘‘) {
    echo json_encode($d);
}else{
    $rd=$c+"("+json_encode($d)+")";
    echo  $c."(".json_encode($d).")";
}
?>

php代码主要是取到callback,最后把数据组装为  callback(data)的形势返回。

此时页面控制台输入如下

$.get、$.getJSON、$.post、load最后都调用了$.ajax,

$.ajax(options) 内部处理流程。

把$.ajaxSettings[key]中的设置赋值到options中

执行全局ajaxStart(settings)

判断当前请求的url是否跨域

if (!settings.crossDomain) {
    urlAnchor = document.createElement(‘a‘)
    urlAnchor.href = settings.url
    urlAnchor.href = urlAnchor.href
        //内部判断当前的url是否跨域
    settings.crossDomain = (originAnchor.protocol + ‘//‘ + originAnchor.host) !== (urlAnchor.protocol + ‘//‘ + urlAnchor.host)
}

没有指定url默认给定当前页面url===》serializeData(settings)处理settings里面的 data,其实就是对data做相关转换

再次根据url判断dataType的类型,如果dataType是jsonp类型,就执行$.ajaxJSONP,并且返回,稍后看ajaxJSONP的实现

var dataType = settings.dataType,
    //如果请求的是jsonp,则将地址栏里的=?替换为callback=?,相当于一个简写
    hasPlaceholder = /\?.+=\?/.test(settings.url)
if (hasPlaceholder) dataType = ‘jsonp‘

if (settings.cache === false || (
        (!options || options.cache !== true) &&
        (‘script‘ == dataType || ‘jsonp‘ == dataType)
    ))
//不缓存,在url后面添加时间戳
    settings.url = appendQuery(settings.url, ‘_=‘ + Date.now())

//针对jsonp进行处理
if (‘jsonp‘ == dataType) {
    if (!hasPlaceholder)
        settings.url = appendQuery(settings.url, settings.jsonp ? (settings.jsonp + ‘=?‘) : settings.jsonp === false ? ‘‘ : ‘callback=?‘)
    return $.ajaxJSONP(settings, deferred)
}

if (deferred) deferred.promise(xhr) 如果引入了Deferred这里会执行deferred.promise(xhr)。这句话的里面其实是

promise: function(obj) {
    return obj != null ? $.extend(obj, promise) : promise
}

把当前deferred的一些行为附加到了xhr对象上,通过 deferred.promise() 方法返回的 deferred promise 对象,是没有 resolve ,reject, progress , resolveWith, rejectWith , progressWith 这些可以改变状态的方法,只能使用 done, then ,fail 等方法添加 handler 或者判断状态。所以返回xhr对象后面只能链式调用 done, then ,fail,且不能改变当前Deferred的状态。那什么时候改变Deferred的状态呢,其实是内部执行ajaxSuccess、ajaxError方法时,内部deferred对象调用resolveWith、rejectWith,改变deferred的状态,一个执行成功resolveWith,一个执行失败rejectWith。

function ajaxSuccess(data, xhr, settings, deferred) {
        var context = settings.context,
            status = ‘success‘
            //获取上下文,调用success
        settings.success.call(context, data, status, xhr)
            //如果引用了Deferred模块,这里执行成功的通知,并传递相关参数
        if (deferred) deferred.resolveWith(context, [data, status, xhr])
            //触发全局的成功
        triggerGlobal(settings, context, ‘ajaxSuccess‘, [xhr, settings, data])
            //调用ajaxComplete
        ajaxComplete(status, xhr, settings)
    }
    // type: "timeout", "error", "abort", "parsererror"
function ajaxError(error, type, xhr, settings, deferred) {
    var context = settings.context
        //获取上下文,调用error
    settings.error.call(context, xhr, type, error)
        //如果引用了Deferred模块,这里执行失败的通知,并传递相关参数
    if (deferred) deferred.rejectWith(context, [xhr, type, error])
        //触发全局的失败
    triggerGlobal(settings, context, ‘ajaxError‘, [xhr, settings, error || type])
        //调用ajaxComplete
    ajaxComplete(type, xhr, settings)
}

如果我们设置了超时时间、超时之后就会调用xhr.abort取消异步请求、并触发ajaxError

//当设置了settings.timeout,则在超时后取消请求,并执行timeout事件处理函数,并处罚error
if (settings.timeout > 0) abortTimeout = setTimeout(function() {
    xhr.onreadystatechange = empty
    xhr.abort()
    ajaxError(null, ‘timeout‘, xhr, settings, deferred)
}, settings.timeout)

xhr.readyState == 4,readyState有五种状态:、01、2、3、4;当值为4的时候,表示数据接收完毕,此时可以通过responseXml和responseText获取完整的回应数据。

判断处理状态,拿到相关数据,调用相关的处理函数。

xhr.onreadystatechange = function() {
    if (xhr.readyState == 4) {
        xhr.onreadystatechange = empty
            //清楚setTimeout
        clearTimeout(abortTimeout)
        var result, error = false
            //根据状态来判断请求是否成功
            //状态>=200 && < 300 表示成功
            //状态 == 304 表示文件未改动过,也可认为成功
            //如果是取要本地文件那也可以认为是成功的,xhr.status == 0是在直接打开页面时发生请求时出现的状态,也就是不是用localhost的形式访问的页面的情况
        if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304 || (xhr.status == 0 && protocol == ‘file:‘)) {
            //取到dataType
            dataType = dataType || mimeToDataType(settings.mimeType || xhr.getResponseHeader(‘content-type‘))
                //取到返回的数据
            result = xhr.responseText

            try {
                // http://perfectionkills.com/global-eval-what-are-the-options/
                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
            }
            //调用ajaxError
            if (error) ajaxError(error, ‘parsererror‘, xhr, settings, deferred)
            else ajaxSuccess(result, xhr, settings, deferred)
        } else {
            ajaxError(xhr.statusText || null, xhr.status ? ‘error‘ : ‘abort‘, xhr, settings, deferred)
        }
    }
}

$.ajaxJSONP、jsonp的处理

内部处理就是创建一个script标签去请求,对传入的callback做了一些处理,重写了callback

window[callbackName] = function() {
  responseData = arguments
}

最终执行的时候,先是拿到返回的数据,赋值给内部变量responseData。在load或者error的时候,执行响应函数,先是移除创建script对象上的事件数据,然后从页面上移除这个对象。然后调用调用ajaxError或者ajaxSuccess,最后调用最初传入的callback

$.ajaxJSONP = function(options, deferred) {
    //没有指定type
    if (!(‘type‘ in options)) return $.ajax(options)

    var _callbackName = options.jsonpCallback,
        //调用options.jsonpCallback,得到返回的字符串。
        //如果没有任何返回就生成一个callbackName=jsonpXXX.
        callbackName = ($.isFunction(_callbackName) ?
            _callbackName() : _callbackName) || (‘jsonp‘ + (++jsonpID)),
        //创建一个script标签,这里貌似没有jq的简洁啊
        script = document.createElement(‘script‘),
        //保存最初指定的window[callbackName] callback
        //如果调用options.jsonpCallback,但都没返回这里就是随机生成的jsonpXXX函数名,window[callbackName]应该是不存在的
        originalCallback = window[callbackName],
        responseData,
        abort = function(errorType) {
            $(script).triggerHandler(‘error‘, errorType || ‘abort‘)
        },
        xhr = {
            abort: abort
        },
        abortTimeout

    if (deferred) deferred.promise(xhr)

    $(script).on(‘load error‘, function(e, errorType) {
        clearTimeout(abortTimeout)
            //销毁事件,移除元素
        $(script).off().remove()

        if (e.type == ‘error‘ || !responseData) {
            ajaxError(null, errorType || ‘error‘, xhr, options, deferred)
        } else {
            ajaxSuccess(responseData[0], xhr, options, deferred)
        }
        //赋值回原来的callback
        window[callbackName] = originalCallback
            //最初保存的window[callbackName]
        if (responseData && $.isFunction(originalCallback))
          //调用
            originalCallback(responseData[0])
            //销毁
        originalCallback = responseData = undefined
    })

    if (ajaxBeforeSend(xhr, options) === false) {
        abort(‘abort‘)
        return xhr
    }
    //生成的jsonpXXX函数,指定callback=jsonpXXX。 请求成功返回,就会调用这个函数,给responseData赋值
    //之前originalCallback = window[callbackName] 保存过了,这里重新指定
    window[callbackName] = function() {
            responseData = arguments
        }
        //处理简写 callback的情况
    script.src = options.url.replace(/\?(.+)=\?/, ‘?$1=‘ + callbackName)
        //append到页面上
    document.head.appendChild(script)
        //处理超时的情况
    if (options.timeout > 0) abortTimeout = setTimeout(function() {
        abort(‘timeout‘)
    }, options.timeout)

    return xhr
}

代码简单,看完api,然后组合相关情况去测试,打断点一步一步看内部的处理,很多还得自己去领悟。最后附上全部注释代码

  1 ;(function($) {
  2     var jsonpID = 0,
  3         document = window.document,
  4         key,
  5         name,
  6         rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
  7         scriptTypeRE = /^(?:text|application)\/javascript/i,
  8         xmlTypeRE = /^(?:text|application)\/xml/i,
  9         jsonType = ‘application/json‘,
 10         htmlType = ‘text/html‘,
 11         blankRE = /^\s*$/,
 12         originAnchor = document.createElement(‘a‘)
 13
 14     originAnchor.href = window.location.href
 15         /*
 16         触发ajaxStart回调函数 testAjax.html:109
 17         触发beforeSend回调函数 testAjax.html:109
 18         触发ajaxBeforeSend回调函数 testAjax.html:109
 19         触发ajaxSend回调函数 testAjax.html:109
 20         XHR finished loading: GET "http://192.168.11.198:8080/sourceCode/zepto/testPage/ajax.php?_=1420612854381". zepto-full-1.1.6.js:1403
 21         触发success回调函数 testAjax.html:109
 22         触发ajax done
 23         触发ajaxSuccess回调函数 testAjax.html:109
 24         触发complete回调函数 testAjax.html:109
 25         触发ajaxComplete回调函数 testAjax.html:109
 26         触发ajaxStop回调函数
 27          */
 28         // trigger a custom event and return false if it was cancelled
 29     function triggerAndReturn(context, eventName, data) {
 30             //初始化一个Event对象
 31             var event = $.Event(eventName)
 32                 //触发context这个对象的该事件
 33             $(context).trigger(event, data)
 34                 //返回是否调用过阻止默认行为,初始为false,掉用过就是true
 35             return !event.isDefaultPrevented()
 36         }
 37         //触发 ajax的全局事件
 38         // trigger an Ajax "global" event
 39     function triggerGlobal(settings, context, eventName, data) {
 40         //默认context为document
 41         if (settings.global) return triggerAndReturn(context || document, eventName, data)
 42     }
 43
 44     // Number of active Ajax requests
 45     $.active = 0
 46
 47     function ajaxStart(settings) {
 48         //设置了global 为true
 49         if (settings.global && $.active++ === 0) triggerGlobal(settings, null, ‘ajaxStart‘)
 50     }
 51
 52     function ajaxStop(settings) {
 53         if (settings.global && !(--$.active)) triggerGlobal(settings, null, ‘ajaxStop‘)
 54     }
 55
 56     // triggers an extra global event "ajaxBeforeSend" that‘s like "ajaxSend" but cancelable
 57     function ajaxBeforeSend(xhr, settings) {
 58         var context = settings.context
 59             //先触发beforeSend 再触发ajaxBeforeSend ,其中一个返回false就停止往下执行
 60         if (settings.beforeSend.call(context, xhr, settings) === false ||
 61             triggerGlobal(settings, context, ‘ajaxBeforeSend‘, [xhr, settings]) === false)
 62             return false
 63                 //触发ajaxSend
 64         triggerGlobal(settings, context, ‘ajaxSend‘, [xhr, settings])
 65     }
 66
 67     function ajaxSuccess(data, xhr, settings, deferred) {
 68             var context = settings.context,
 69                 status = ‘success‘
 70                 //获取上下文,调用success
 71             settings.success.call(context, data, status, xhr)
 72                 //如果引用了Deferred模块,这里执行成功的通知,并传递相关参数
 73             if (deferred) deferred.resolveWith(context, [data, status, xhr])
 74                 //触发全局的成功
 75             triggerGlobal(settings, context, ‘ajaxSuccess‘, [xhr, settings, data])
 76                 //调用ajaxComplete
 77             ajaxComplete(status, xhr, settings)
 78         }
 79         // type: "timeout", "error", "abort", "parsererror"
 80     function ajaxError(error, type, xhr, settings, deferred) {
 81             var context = settings.context
 82                 //获取上下文,调用error
 83             settings.error.call(context, xhr, type, error)
 84                 //如果引用了Deferred模块,这里执行失败的通知,并传递相关参数
 85             if (deferred) deferred.rejectWith(context, [xhr, type, error])
 86                 //触发全局的失败
 87             triggerGlobal(settings, context, ‘ajaxError‘, [xhr, settings, error || type])
 88                 //调用ajaxComplete
 89             ajaxComplete(type, xhr, settings)
 90         }
 91         // status: "success", "notmodified", "error", "timeout", "abort", "parsererror"
 92     function ajaxComplete(status, xhr, settings) {
 93         var context = settings.context
 94             //调用setting里面的complete
 95         settings.complete.call(context, xhr, status)
 96             //触发ajaxComplete
 97         triggerGlobal(settings, context, ‘ajaxComplete‘, [xhr, settings])
 98             //调用ajaxStop
 99         ajaxStop(settings)
100     }
101
102     // Empty function, used as default callback
103     function empty() {}
104
105     $.ajaxJSONP = function(options, deferred) {
106             //没有指定type
107             if (!(‘type‘ in options)) return $.ajax(options)
108
109             var _callbackName = options.jsonpCallback,
110                 //调用options.jsonpCallback,得到返回的字符串。
111                 //如果没有任何返回就生成一个callbackName=jsonpXXX.
112                 callbackName = ($.isFunction(_callbackName) ?
113                     _callbackName() : _callbackName) || (‘jsonp‘ + (++jsonpID)),
114                 //创建一个script标签,这里貌似没有jq的简洁啊
115                 script = document.createElement(‘script‘),
116                 //保存最初指定的window[callbackName] callback
117                 //如果调用options.jsonpCallback,但都没返回这里就是随机生成的jsonpXXX函数名,window[callbackName]应该是不存在的
118                 originalCallback = window[callbackName],
119                 responseData,
120                 abort = function(errorType) {
121                     $(script).triggerHandler(‘error‘, errorType || ‘abort‘)
122                 },
123                 xhr = {
124                     abort: abort
125                 },
126                 abortTimeout
127
128             if (deferred) deferred.promise(xhr)
129
130             $(script).on(‘load error‘, function(e, errorType) {
131                 clearTimeout(abortTimeout)
132                     //销毁事件,移除元素
133                 $(script).off().remove()
134
135                 if (e.type == ‘error‘ || !responseData) {
136                     ajaxError(null, errorType || ‘error‘, xhr, options, deferred)
137                 } else {
138                     ajaxSuccess(responseData[0], xhr, options, deferred)
139                 }
140                 //赋值回原来的callback
141                 window[callbackName] = originalCallback
142                     //最初保存的window[callbackName]
143                 if (responseData && $.isFunction(originalCallback))
144                 //调用
145                     originalCallback(responseData[0])
146                     //销毁
147                 originalCallback = responseData = undefined
148             })
149
150             if (ajaxBeforeSend(xhr, options) === false) {
151                 abort(‘abort‘)
152                 return xhr
153             }
154             //生成的jsonpXXX函数,指定callback=jsonpXXX。 请求成功返回,就会调用这个函数,给responseData赋值
155             //之前originalCallback = window[callbackName] 保存过了,这里重新指定
156             window[callbackName] = function() {
157                 responseData = arguments
158             }
159             //处理简写 callback的情况
160             script.src = options.url.replace(/\?(.+)=\?/, ‘?$1=‘ + callbackName)
161                 //append到页面上
162             document.head.appendChild(script)
163             //处理超时的情况
164             if (options.timeout > 0) abortTimeout = setTimeout(function() {
165                 abort(‘timeout‘)
166             }, options.timeout)
167
168             return xhr
169         }
170         //默认的setting
171     $.ajaxSettings = {
172         // Default type of request
173         type: ‘GET‘,
174         // Callback that is executed before request
175         beforeSend: empty,
176         // Callback that is executed if the request succeeds
177         success: empty,
178         // Callback that is executed the the server drops error
179         error: empty,
180         // Callback that is executed on request complete (both: error and success)
181         complete: empty,
182         // The context for the callbacks
183         context: null,
184         // Whether to trigger "global" Ajax events
185         global: true,
186         // Transport
187         xhr: function() {
188             return new window.XMLHttpRequest()
189         },
190         // MIME types mapping
191         // IIS returns Javascript as "application/x-javascript"
192         accepts: {
193             script: ‘text/javascript, application/javascript, application/x-javascript‘,
194             json: jsonType,
195             xml: ‘application/xml, text/xml‘,
196             html: htmlType,
197             text: ‘text/plain‘
198         },
199         // Whether the request is to another domain
200         crossDomain: false,
201         // Default timeout
202         timeout: 0,
203         // Whether data should be serialized to string
204         processData: true,
205         // Whether the browser should be allowed to cache GET responses
206         cache: true
207     }
208
209     function mimeToDataType(mime) {
210         if (mime) mime = mime.split(‘;‘, 2)[0]
211         return mime && (mime == htmlType ? ‘html‘ :
212             mime == jsonType ? ‘json‘ :
213             scriptTypeRE.test(mime) ? ‘script‘ :
214             xmlTypeRE.test(mime) && ‘xml‘) || ‘text‘
215     }
216
217     //参数拼接
218     function appendQuery(url, query) {
219         if (query == ‘‘) return url
220         return (url + ‘&‘ + query).replace(/[&?]{1,2}/, ‘?‘)
221     }
222
223     // serialize payload and append it to the URL for GET requests
224     function serializeData(options) {
225         //要处理data,data存在、不是string类型
226         if (options.processData && options.data && $.type(options.data) != "string")
227             options.data = $.param(options.data, options.traditional)
228         if (options.data && (!options.type || options.type.toUpperCase() == ‘GET‘))
229             options.url = appendQuery(options.url, options.data), options.data = undefined
230     }
231
232     $.ajax = function(options) {
233         //处理外出传入的options,如果加载了Deferred 模块,这里创建一个Deferred实例对象
234         var settings = $.extend({}, options || {}),
235             deferred = $.Deferred && $.Deferred(),
236             urlAnchor
237             //把settings中没有$.ajaxSettings中的设置 都给赋值到settings里面
238         for (key in $.ajaxSettings)
239             if (settings[key] === undefined) settings[key] = $.ajaxSettings[key]
240
241             //调用ajaxStart
242         ajaxStart(settings)
243
244         //如果不是跨域
245         if (!settings.crossDomain) {
246             urlAnchor = document.createElement(‘a‘)
247             urlAnchor.href = settings.url
248             urlAnchor.href = urlAnchor.href
249                 //内部判断当前的url是否跨域
250             settings.crossDomain = (originAnchor.protocol + ‘//‘ + originAnchor.host) !== (urlAnchor.protocol + ‘//‘ + urlAnchor.host)
251         }
252         //没有设置url  默认为当前页面的地址
253         if (!settings.url) settings.url = window.location.toString()
254
255         //处理里面的data
256         serializeData(settings)
257
258         var dataType = settings.dataType,
259             //如果请求的是jsonp,则将地址栏里的=?替换为callback=?,相当于一个简写
260             hasPlaceholder = /\?.+=\?/.test(settings.url)
261         if (hasPlaceholder) dataType = ‘jsonp‘
262
263         if (settings.cache === false || (
264                 (!options || options.cache !== true) &&
265                 (‘script‘ == dataType || ‘jsonp‘ == dataType)
266             ))
267         //不缓存,在url后面添加时间戳
268             settings.url = appendQuery(settings.url, ‘_=‘ + Date.now())
269
270         //针对jsonp进行处理
271         if (‘jsonp‘ == dataType) {
272             if (!hasPlaceholder)
273                 settings.url = appendQuery(settings.url, settings.jsonp ? (settings.jsonp + ‘=?‘) : settings.jsonp === false ? ‘‘ : ‘callback=?‘)
274             return $.ajaxJSONP(settings, deferred)
275         }
276
277         var mime = settings.accepts[dataType],
278             headers = {},
279             setHeader = function(name, value) {
280                 headers[name.toLowerCase()] = [name, value]
281             },
282             protocol = /^([\w-]+:)\/\//.test(settings.url) ? RegExp.$1 : window.location.protocol,
283             xhr = settings.xhr(),
284             nativeSetHeader = xhr.setRequestHeader,
285             abortTimeout
286             //引入了Deferred没款,这里xhr就附加了promise对象的相关行为,最后返回的是一个promise对象,后续就可以链式done
287         if (deferred) deferred.promise(xhr)
288             //设置header
289         if (!settings.crossDomain) setHeader(‘X-Requested-With‘, ‘XMLHttpRequest‘)
290         setHeader(‘Accept‘, mime || ‘*/*‘)
291
292         if (mime = settings.mimeType || mime) {
293             if (mime.indexOf(‘,‘) > -1) mime = mime.split(‘,‘, 2)[0]
294             xhr.overrideMimeType && xhr.overrideMimeType(mime)
295         }
296
297         //设置contentType
298         if (settings.contentType || (settings.contentType !== false && settings.data && settings.type.toUpperCase() != ‘GET‘))
299             setHeader(‘Content-Type‘, settings.contentType || ‘application/x-www-form-urlencoded‘)
300
301         if (settings.headers)
302             for (name in settings.headers) setHeader(name, settings.headers[name])
303
304         xhr.setRequestHeader = setHeader
305
306         xhr.onreadystatechange = function() {
307             if (xhr.readyState == 4) {
308                 xhr.onreadystatechange = empty
309                     //清楚setTimeout
310                 clearTimeout(abortTimeout)
311                 var result, error = false
312                     //根据状态来判断请求是否成功
313                     //状态>=200 && < 300 表示成功
314                     //状态 == 304 表示文件未改动过,也可认为成功
315                     //如果是取要本地文件那也可以认为是成功的,xhr.status == 0是在直接打开页面时发生请求时出现的状态,也就是不是用localhost的形式访问的页面的情况
316                 if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304 || (xhr.status == 0 && protocol == ‘file:‘)) {
317                     //取到dataType
318                     dataType = dataType || mimeToDataType(settings.mimeType || xhr.getResponseHeader(‘content-type‘))
319                         //取到返回的数据
320                     result = xhr.responseText
321
322                     try {
323                         // http://perfectionkills.com/global-eval-what-are-the-options/
324                         if (dataType == ‘script‘)(1, eval)(result)
325                         else if (dataType == ‘xml‘) result = xhr.responseXML
326                         else if (dataType == ‘json‘) result = blankRE.test(result) ? null : $.parseJSON(result)
327                     } catch (e) {
328                         error = e
329                     }
330                     //调用ajaxError
331                     if (error) ajaxError(error, ‘parsererror‘, xhr, settings, deferred)
332                     else ajaxSuccess(result, xhr, settings, deferred)
333                 } else {
334                     ajaxError(xhr.statusText || null, xhr.status ? ‘error‘ : ‘abort‘, xhr, settings, deferred)
335                 }
336             }
337         }
338
339         //ajaxBeforeSend 返回false
340         if (ajaxBeforeSend(xhr, settings) === false) {
341             xhr.abort()
342             ajaxError(null, ‘abort‘, xhr, settings, deferred)
343             return xhr
344         }
345         //设置请求头信息
346         if (settings.xhrFields)
347             for (name in settings.xhrFields) xhr[name] = settings.xhrFields[name]
348
349         var async = ‘async‘ in settings ? settings.async : true
350             //发起请求
351         xhr.open(settings.type, settings.url, async, settings.username, settings.password)
352
353         for (name in headers) nativeSetHeader.apply(xhr, headers[name])
354
355         //当设置了settings.timeout,则在超时后取消请求,并执行timeout事件处理函数,并处罚error
356         if (settings.timeout > 0) abortTimeout = setTimeout(function() {
357             xhr.onreadystatechange = empty
358             xhr.abort()
359             ajaxError(null, ‘timeout‘, xhr, settings, deferred)
360         }, settings.timeout)
361
362         // avoid sending empty string (#319)
363         xhr.send(settings.data ? settings.data : null)
364             //如果引入了Deferred模块这里返回promise对象
365         return xhr
366     }
367
368     //处理多个参数组合的情况
369     // handle optional data/success arguments
370     function parseArguments(url, data, success, dataType) {
371         if ($.isFunction(data)) dataType = success, success = data, data = undefined
372         if (!$.isFunction(success)) dataType = success, success = undefined
373         return {
374             url: url,
375             data: data,
376             success: success,
377             dataType: dataType
378         }
379     }
380
381     $.get = function( /* url, data, success, dataType */ ) {
382         return $.ajax(parseArguments.apply(null, arguments))
383     }
384
385     $.post = function( /* url, data, success, dataType */ ) {
386         var options = parseArguments.apply(null, arguments)
387         options.type = ‘POST‘
388         return $.ajax(options)
389     }
390
391     $.getJSON = function( /* url, data, success */ ) {
392         var options = parseArguments.apply(null, arguments)
393         options.dataType = ‘json‘
394         return $.ajax(options)
395     }
396
397     $.fn.load = function(url, data, success) {
398         if (!this.length) return this
399         var self = this,
400             parts = url.split(/\s/),
401             selector,
402             options = parseArguments(url, data, success),
403             callback = options.success
404         if (parts.length > 1) options.url = parts[0], selector = parts[1]
405         options.success = function(response) {
406             //创建一个div,把返回的字符串script替换掉,把放字符串入div中,通过选择器找到对应元素
407             self.html(selector ?
408                     $(‘<div>‘).html(response.replace(rscript, "")).find(selector) : response)
409                 //调用传入的回调函数
410             callback && callback.apply(self, arguments)
411         }
412         $.ajax(options)
413         return this
414     }
415
416     var escape = encodeURIComponent
417
418     function serialize(params, obj, traditional, scope) {
419         var type, array = $.isArray(obj),
420             hash = $.isPlainObject(obj)
421         $.each(obj, function(key, value) {
422             type = $.type(value)
423                 //scope用作处理value也是object或者array的情况
424                 //traditional表示是否以传统的方式拼接数据,
425                 //传统的意思就是比如现有一个数据{a:[1,2,3]},转成查询字符串后结果为‘a=1&a=2&a=3‘
426                 //非传统的的结果则是a[]=1&a[]=2&a[]=3
427             if (scope) key = traditional ? scope :
428                 scope + ‘[‘ + (hash || type == ‘object‘ || type == ‘array‘ ? key : ‘‘) + ‘]‘
429                 // handle data in serializeArray() format
430                 //当处理的数据为[{},{},{}]这种情况的时候,一般指的是序列化表单后的结果
431             if (!scope && array) params.add(value.name, value.value)
432                 // recurse into nested objects
433                 // 当value值是数组或者是对象且不是按传统的方式序列化的时候,需要再次遍历value
434             else if (type == "array" || (!traditional && type == "object"))
435                 serialize(params, value, traditional, key)
436             else params.add(key, value)
437         })
438     }
439
440     $.param = function(obj, traditional) {
441         var params = []
442         params.add = function(key, value) {
443             if ($.isFunction(value)) value = value()
444             if (value == null) value = ""
445             this.push(escape(key) + ‘=‘ + escape(value))
446         }
447         serialize(params, obj, traditional)
448         return params.join(‘&‘).replace(/%20/g, ‘+‘)
449     }
450 })(Zepto); 

zepto ajax

本文地址:http://www.cnblogs.com/Bond/p/4210808.html

时间: 2024-09-29 08:30:55

zepto源码学习-05 ajax的相关文章

zepto源码学习-01-整体感知

在公司一直做移动端的项目,偶尔会做点PC端的东西,但基本上都是和移动端打交道,移动端嘛必须上zepto. 简单介绍下Zepto,它是一个面向高级浏览器的JavaScript框架的,实现JQuery的大部分API,尤其针对手机上web开发(流量金贵,JQ又太重了,体积太大,考虑太多性能不好),因此选择Zepto.js一个非常不错的选择!纵观各大网站选用zepto的特别多. 做移动端为了让页面更轻巧,大多都是自己写原生代码,遇到难题,我一般都是去看zepto的实现,把其中的一些搬到自己代码中,久而久

zepto源码学习-03 $()

在第一篇的时候提到过关于$()的用法,一个接口有很多重载,用法有很多种,总结了下,大概有一以下几种 1.$(selector,context?) 传入一个选择器返回一个zepto对象 2.$(function(){}) 传入一个函数,dom ready时执行 3.$(html,attrs?) 传入一个html字符串,构建元素,返回一个或zepto对象 4.$(dom obj)传入dom对象返回zepto对象 $()最终调用的zepto.init方法,对以上四种情况做相应处理,该方法有6个retu

zepto源码学习-06 touch

先上菜,看这个模块的最后一段代码,一看就明白. ['swipe', 'swipeLeft', 'swipeRight', 'swipeUp', 'swipeDown', 'doubleTap', 'tap', 'singleTap', 'longTap' ].forEach(function(eventName) { $.fn[eventName] = function(callback) { return this.on(eventName, callback) } }) tap —元素tap

非常适合新手的jq/zepto源码分析05

zepto的原型  $.fn  属性: constructor //构造行数 forEach: emptyArray.forEach, //都是原生数组的函数reduce: emptyArray.reduce,push: emptyArray.push,sort: emptyArray.sort,splice: emptyArray.splice,indexOf: emptyArray.indexOf, concat //合并数组,这里还包含了合并节点集合 add //添加节点集合 is //匹

zepto源码学习-04 event

之前说完$(XXX),然后还有很多零零碎碎的东西需要去分析,结果一看代码,发现zepto的实现都相对简单,没有太多可分析的.直接略过了一些实现,直接研究Event模块,相比JQuery的事件系统,zepto的设计相对简单很多,看起来也就相对轻松,整个event模块也就300行代码. 先看事件的相关接口以及用法 $.Event $.Event(type, [properties]) ⇒ event 创建并初始化一个指定的DOM事件.如果给定properties对象,使用它来扩展出新的事件对象.默认

zepto源码--几个判断函数--学习笔记

几个需要经常用到的类型判断: 自定义一个类似于typeof的函数,提供更多的类型判断. 如果传入的参数为null,则类型返回'null',基本上可以返回各种常用对象类型,如'function', 'array','regexp'--而不是统一返回object. 判断是否为函数类型: 判断是不是window对象: 判断是不是document对象: 判断是否为object对象: 判断是否为{}对象: 判断是否为类数组:arguments就属于类数组,或者$('div')这种,可以用下标读取,看起来像

jquery源码学习

jQuery 源码学习是对js的能力提升很有帮助的一个方法,废话不说,我们来开始学习啦 我们学习的源码是jquery-2.0.3已经不支持IE6,7,8了,因为可以少学很多hack和兼容的方法. jquery-2.0.3的代码结构如下 首先最外层为一个闭包, 代码执行的最后一句为window.$ = window.jquery = jquery 让闭包中的变量暴露倒全局中. 传参传入window是为了便于压缩 传入undefined是为了undifined被修改,他是window的属性,可以被修

jQuery源码学习感想

还记得去年(2015)九月份的时候,作为一个大四的学生去参加美团霸面,结果被美团技术总监教育了一番,那次问了我很多jQuery源码的知识点,以前虽然喜欢研究框架,但水平还不足够来研究jQuery源码,那时我不明白他们为何要求那么高,现在才知道,原来没那么高,他问的都是jQuery最基本的框架架构,不过对于不知道的来说,再简单我也是不知道,那时写了一篇博文去吐槽了一下,那时候也是我自己真正激发自己的时候,那时候我说我一定要搞好自己的jQuery基础,没想到那么快就实现了,一个月的源码学习时间就结束

nginx源码学习资源

http://www.cnblogs.com/yjf512/archive/2012/06/13/2548515.html 2012-06-13 21:32 by 轩脉刃, 26499 阅读, 5 评论, 收藏, 编辑 nginx源码学习是一个痛苦又快乐的过程,下面列出了一些nginx的学习资源. 首先要做的当然是下载一份nginx源码,可以从nginx官方网站下载一份最新的. 看了nginx源码,发现这是一份完全没有注释,完全没有配置文档的代码. 现在你最希望要的是一份注释版的nginx源码,