阅读 15

zepto源码ajax模块学习

在学习zepto的源码的时候,即使现在zepto使用的很少,但是能够让我们学习的地方还是很多,无论是项目结构,还是细微的地方。

triggerAndReturn

;(function($){
  var jsonpID = +new Date();

  function triggerAndReturn(context, eventName, data) {
    var event = $.Event(eventName);
    $(context).trigger(event, data);
    return !event.isDefaultPrevented();
  }
})
复制代码

这里为什么文件开头都要使用';'这是因为在对多个js文件进行打包的时候,如果使用换行分隔代码,当合并压缩多个文件之后,换行符会被删掉,连在一起可能出错,加上分号就保险了。
jsonpID在跨域jsonp的时候会使用到,这是为了禁止使用cachetriggerAndReturn()的目的在于创建一个Event事件,然后context作为上下文触发event事件,如果默认行为被阻止,则返回true

ajaxSettings

$.ajaxSettings = {
  type: 'GET',
  success: empty,
  xhr: function () {
    return new window.XMLHttpRequest()
  },
  cache: true,
  crossDomain: false
}
复制代码

这是ajax的一些默认设置,请求默认是GETxhrXMLHttpRequest对象,cache表示浏览器是否应该被允许缓存GET响应。crossDomain表示是否能够向另外的一个域请求数据。

$.ajax

下面来看看最核心的$.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)
    ...
  }
复制代码

首先传入options,然后将传入的options存储到settingsdeferred对象是来自$.Deferred,deferred对象近似看成promise对象,具体的可以看这篇文章zepto源码deferred模块学习。这里如果用户传入相关的设置,则使用$.ajaxSettings中默认的设置。

if (!settings.crossDomain) {
  urlAnchor = document.createElement('a');
  urlAnchor.href = settings.url;
  urlAnchor.href = urlAnchor.href;
  settings.crossDomain = (originAnchor.protocol + '//' + originAnchor.host) !== (urlAnchor.protocol + '//' + urlAnchor.host)
}
复制代码

进入该逻辑的条件是settings.crossDomainfalse,这里前面三行是为了修复IE浏览器的bug不用管,这里主要是通过判断当前域名和传入域名是否相同,来设置crossDomain的值

if ((hashIndex = settings.url.indexOf('#')) > -1) settings.url = settings.url.slice(0, hashIndex)

var dataType = settings.dataType,
  hasPlaceholder = /\?.+=\?/.test(settings.url);
if (hasPlaceholder) dataType = 'jsonp';

if (settings.cache === false || (
    (!options || options.cache !== true) &&
    ('script' == dataType || 'jsonp' == dataType)
  ))
  settings.url = appendQuery(settings.url, '_=' + Date.now())

function appendQuery(url, query) {
  if (query == '') return url;
  return (url + '&' + query).replace(/[&?]{1,2}/, '?')
}
if ('jsonp' == dataType) {
  if (!hasPlaceholder)
    settings.url = appendQuery(settings.url,
      settings.jsonp ? (settings.jsonp + '=?') : settings.jsonp === false ? '' : 'callback=?')
  return $.ajaxJSONP(settings, deferred)
}
复制代码

如果我们没有传入url,那么url赋值为window.location.href。'#'代表网页中的一个位置,右边的字符,就是该位置的标识符,例如此时我们页面的A位置,那么'#'后面接的可以是'A',如'#A'。#是用来指导浏览器动作的,对服务器来说没有用,所以在截取url的时候,没必要把后面的部分传给服务器。hasPlaceholder这里的正则表达式,用于匹配类似'?name=?'这种字符串,这里的就是用'?'进行占位,我们并没有给name赋值,如果hasPlaceholdertrue,则dataType设置为jsonp, dataType表示的是从服务器返回的数据类型。 下面是禁用缓存的情况,如果设置cachefalse,或者 dataType设置为script或者jsonp的情况下,禁用缓存,方法就是在url后边添加时间,这样每次请求地址都不一样,浏览器自然就没有缓存了。appendQuery方法的作用在url添加参数,同时把&&&???这三种字符替换为?

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(),
    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]
  xhr.overrideMimeType && xhr.overrideMimeType(mime)
}
复制代码

settings中的accepts表示从服务器请求的MIME类型,这其中的对应关系如下:

script: "text/javascript, application/javascript"
json: "application/json"
xml: "application/xml, text/xml"
html: "text/html"
text: "text/plain"
复制代码

protocol就是匹配类似'http://'的这种协议(不过不明白为什么要加'-',难道还有前面加'-'的协议?)
如果deferred对象存在,就把xhr扩展为一个具有promise方法的对象。然后通过crossDomain的值,来设置头部信息,请求是否应该是ajax请求。然后后面的判断条件查了一下是针对一些mozillar浏览器进行修正(有个问题就是他们上哪儿知道的用这种方式来修正啊)。

xhr.onreadystatechange = function() {
  if (xhr.readyState == 4) {
    xhr.onreadystatechange = empty;
    clearTimeout(abortTimeout);
    if (/*如果成功*/){
      // 对返回结果进行处理
      if (dataType == 'script') (1, eval)(result)
      else if (dataType == 'xml') result = xhr.responseXML
      ...
    }
  }
}

xhr.open(settings.type, settings.url, async, settings.username, settings.password)
复制代码

xhr是具有promise方法的对象,这里就是设置xhronreadystatechange处理函数,如果readystate改变就触发这个函数,清除定时器,这个定时器根据我们是否传入timeout而定。if条件就是如果回调成功,但是需要对传回来的值做处理,因为可能是json数据,也可能是script格式,或者xml格式。

 if (ajaxBeforeSend(xhr, settings) === false) {
   xhr.abort()
   ajaxError(null, 'abort', xhr, settings, deferred)
   return xhr
 }
 xhr.send(settings.data ? settings.data : null)
复制代码

这里表示如果发送请求出错了,就终止该请求调用abort方法,调用ajaxError方法。然后后面是防止传递空的字符,如果没有数据,我们就传null到服务端。最后返回xhr对象,然后发送数据到服务端

$.param

 $.param = function (obj, traditional) {
   var params = []
   params.add = function (key, value) {
     if ($.isFunction(value)) value = value()
     if (value == null) value = ""
     this.push(escape(key) + '=' + escape(value))
   }
   serialize(params, obj, traditional)
   return params.join('&').replace(/%20/g, '+')
 }
复制代码

传入一个对象,和一个标记,这个traditional表示激活传统的方式通过$.param来得到data。首先定义一个空数组,如果在上面添加方法,这个方法的主要作用向params里边添加序列化的对象,escape=encodeURIComponent,然后调用serialize,将obj对象添加到params中,最后返回将params数组用'&'拼接,然后这里的%20表示空格,意思是将空格替换成'+'

serialize

function serialize(params, obj, traditional, scope) {
  var type, array = $.isArray(obj),
    hash = $.isPlainObject(obj)
  $.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) params.add(value.name, value.value)
    // recurse into nested objects
    else if (type == "array" || (!traditional && type == "object"))
      serialize(params, value, traditional, key)
    else params.add(key, value)
  })
}
复制代码

这个函数的作用主要是序列化参数。如果obj是数组,则arraytrue,如果为纯粹对象,则hashtrue。遍历需要序列化的对象obj,判断value的类型type,这个type后面会用到。scope是记录深层嵌套时的key值,这个key值受traditional的影响。如果traditionaltrue,则key为原始的scope值,即对象第一层的key值。否则,用[]拼接当前循环中的key,最终的key值会是这种形式scope[key][key2]...。如果obj为数组,并且scope不存在,即为第一层,直接调用params.add方法 否则如果value的类型为数组或者非传统序列化方式下为对象,则递归调用serialize方法,用来处理key。其他情况调用params.add方法。

关注下面的标签,发现更多相似文章
评论