zepto源码ajax模块学习

野风技术札记 2019-06-28

在学习zepto的源码的时候,不得不称赞这些人的厉害,我虽然能看明白,但是要我写,估计吭哧吭哧写不出来。虽然现在很少人使用zepto了,但是学习这些源码我相信每次看都能给咱们不同的感受。Deferred对象待会讲,deferred我认为是zepto设计最巧妙的地方。先来看zepto的ajax模块。

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

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

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

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

这是ajax的初始化,默认是GET请求,xhr是新建的XMLHttpRequest()对象,cache表示浏览器是否应该被允许缓存GET响应。crossDomain表示是否可以来自另外一个域。
下面来看看最核心的$.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存储到本地,deferred咱们暂时可以把它看成一个promise对象,遍历$.ajaxSettings,如果用户没有设置里边有的属性,那就使用默认的属性。然后调用ajaxStart开始的函数。

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)
}

这里首先crossDomain为false的情况下,进入该逻辑,通过判断我们传入的url和当前window.location.href做对比,判断是不是跨域。

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。'#'代表网页中的一个位置,右边的字符,就是该位置的标识符,#是用来指导浏览器动作的,对浏览器没有用,所以在截取url的时候,没必要把后面的部分传给服务器。hasPlaceholder这里的正则表达式,用于匹配类似'?name=?'这种字符串,如果hasPlaceholder为true,则dataType为jsonp。下面的时不使用留在缓存的数据,第一种是设置cache为false,或者dataType为script和jsonp的情况,需要在url后面添加事件。下面是appendQuery方法,就是把字符串拼接到url后边,但是需要把'&'替换成'?'。如果我们的请求是jsonp请求,需要在url后面添加一些callback=?这种参数。

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类型,指定dataType的值,包括script、json、xml、html、text。mime存储的就是类似'application/json'的字符串。protocol就是匹配咱们类似'http://'的这种协议。
如果deferred存在,就把xhr转换为deferred对象。接着看下去,如果不是跨域的,那就是ajax请求。然后后面的判断条件查了一下是针对一些mozillar浏览器进行修正(有个问题就是他们上哪儿知道的用这种方式来修正啊)。

xhr.onreadystatechange = function() {
  if (xhr.readyState == 4) {
    xhr.onreadystatechange = empty
    if (/*如果成功*/){
      // 对返回结果进行处理
    }
  }
}

xhr.open(settings.type, settings.url, async, settings.username, settings.password)

没什么难的,就是根据传入的参数不同进行处理。

$.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)
  })
}

params是带有add方法的数组,遍历obj的键值对,属性值可能是对象也可能是数组或者字符串分别进行处理。如果obj的某个属性值是对象或者数组,scope就代表是该属性,那么这时候向params传入的key就需要变化,变成类似'a[b]'这里的b是obj的某个属性值是对象的一个属性。

相关推荐

mmywcoco / 0评论 2020-06-06