6

zepto源码之ajax源码

 3 years ago
source link: https://zwkang.com/?p=469
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

zepto源码之ajax源码

//     Zepto.js
//     (c) 2010-2016 Thomas Fuchs
//     Zepto.js may be freely distributed under the MIT license.

;(function($){
  var jsonpID = +new Date(),
      document = window.document, // 缓存一波
      key, 
      name,
      rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, //
      scriptTypeRE = /^(?:text|application)\/javascript/i, // 获得script的类型
      xmlTypeRE = /^(?:text|application)\/xml/i, // xml类型正则
      jsonType = 'application/json', // json application
      htmlType = 'text/html', // html类型
      blankRE = /^\s*$/, // 判断空格
      originAnchor = document.createElement('a') // 锚点

  originAnchor.href = window.location.href // 默认连接是当前页面地址

  // trigger a custom event and return false if it was cancelled
  // TODO: 2 看看啥回事event
  // 注册事件
  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 开始 对活跃数加1
  function ajaxStart(settings) {
    if (settings.global && $.active++ === 0) triggerGlobal(settings, null, 'ajaxStart')
  }
  // ajax请求结束 对活跃数减1
  function ajaxStop(settings) {
    if (settings.global && !(--$.active)) triggerGlobal(settings, null, 'ajaxStop')
  }

  // triggers an extra global event "ajaxBeforeSend" that's like "ajaxSend" but cancelable
  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])
  }
  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)
  }
  // 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)
  }
  // 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)
  }
  // 
  function ajaxDataFilter(data, type, settings) {
    if (settings.dataFilter == empty) return data
    var context = settings.context
    return settings.dataFilter.call(context, data, type)
  }

  // Empty function, used as default callback
  function empty() {}

  $.ajaxJSONP = function(options, deferred){
    if (!('type' in options)) return $.ajax(options)

    var _callbackName = options.jsonpCallback,
      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)

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

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

      originalCallback = responseData = undefined
    })

    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
  }
  // 默认设定
  $.ajaxSettings = {
    // Default type of request
    // 默认请求类型
    type: 'GET',
    // 以下就是文档对应的一些ajax请求的生命周期
    // Callback that is executed before request
    // 在请求前的触发
    beforeSend: empty,
    // Callback that is executed if the request succeeds
    // 当请求成功后的触发
    success: empty,
    // Callback that is executed the the server drops error
    error: empty,
    // Callback that is executed on request complete (both: error and success)
    // 当成功或失败的回调
    complete: empty,
    // 回调函数的上下文环境(this指向)
    // The context for the callbacks
    context: null,
    // 是否触发全局ajax请求
    // Whether to trigger "global" Ajax events
    global: true,
    // Transport
    // SEE: 这里好像没兼容耶 
    xhr: function () {
      return new window.XMLHttpRequest()
    },
    // MIME types mapping
    // MIME类型映射
    // IIS服务器会返回Javascript为 "application/x-javascript"
    // IIS returns Javascript as "application/x-javascript"
    accepts: {
      script: 'text/javascript, application/javascript, application/x-javascript',
      json:   jsonType,
      xml:    'application/xml, text/xml',
      html:   htmlType,
      text:   'text/plain'
    },
    // Whether the request is to another domain
    // 是否为另一个域的请求
    // 这个判断是否在本地就可以判断了
    crossDomain: false,
    // Default timeout
    // 默认超时时间
    timeout: 0,
    // data是否需要被序列化为字符串
    // Whether data should be serialized to string
    processData: true,
    // 是否允许浏览器缓存GET请求响应
    // 最开始的new Date() 用法
    // Whether the browser should be allowed to cache GET responses
    cache: true,
    // 使用一个过滤器来处理未经处理过的原始数据
    // 默认是空的,不处理
    //Used to handle the raw response data of XMLHttpRequest.
    //This is a pre-filtering function to sanitize the response.
    //The sanitized response should be returned
    dataFilter: 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'
  }

  function appendQuery(url, query) {
    if (query == '') return url
    return (url + '&' + query).replace(/[&?]{1,2}/, '?')
    // 这个正则有点意思,这样的情况下,容易出现&?a= | ?&a= 之类的组合。而在这些结合点,直接replace成?
  }

  // serialize payload and append it to the URL for GET requests
  // get 请求  序列化payload并且把它添加到URL链接
  function serializeData(options) {
    if (options.processData && options.data && $.type(options.data) != "string")// 判断是不是真的要序列化
      options.data = $.param(options.data, options.traditional)
    if (options.data && (!options.type || options.type.toUpperCase() == 'GET' || 'jsonp' == options.dataType))
      options.url = appendQuery(options.url, options.data), options.data = undefined

      // 将param转义过的参数添加到url内,在将其放在url内,此时的data置空
  }

  $.ajax = function(options){
    // 主体函数 获取options 配置
    var settings = $.extend({}, options || {}),
    // 复制一层extend TODO: 浅复制深复制?
    // 读配置,与默认配置相错,获得配置信息
        deferred = $.Deferred && $.Deferred(),
        urlAnchor, hashIndex
        // Object.keys($.ajaxSettings).forEach((v)=>{settings[key]||settings[key] = $.ajaxSettings[key]})
    for (key in $.ajaxSettings) if (settings[key] === undefined) settings[key] = $.ajaxSettings[key]
    // 开始了开始了
    ajaxStart(settings)

    if (!settings.crossDomain) {
      // 不跨域
      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)
    }

    if (!settings.url) settings.url = window.location.toString()
    // 判断是否有url地址。 没有的话则为当前地址
    if ((hashIndex = settings.url.indexOf('#')) > -1) settings.url = settings.url.slice(0, hashIndex)
    // 判断是否有#在url内,如果有的话会进行slice 扫出http://a.com#test ==> http://a.com
    // WOW: 奇技淫巧 在判断处condistion赋值,这样的话返回值是赋值右值,那么此时判断仍然可以走。而且做了一次赋值在if 内部处理也可以用,不过也只是少了一行代码
    serializeData(settings)
    // 判断是否对data进行序列化 如果序列化且GET方法,则此时get参数会自动序列化了

    var dataType = settings.dataType, hasPlaceholder = /\?.+=\?/.test(settings.url)
    // hasPlaceholder是否有多个?? 对url进行判断
    if (hasPlaceholder) dataType = 'jsonp' // 为jsonp类型 // jsonpendding

    if (settings.cache === false || ( // 是否不缓存 
         (!options || options.cache !== true) && // option是否要不缓存且dataType类型为script或者为jsonp
         ('script' == dataType || 'jsonp' == dataType) // 
        ))
      settings.url = appendQuery(settings.url, '_=' + Date.now())  // 缓存,给时间戳
    // setting的dataType判断一下。且判断下hasPlaceholder
    if ('jsonp' == dataType) {
      if (!hasPlaceholder)
        settings.url = appendQuery(settings.url,
          settings.jsonp ? (settings.jsonp + '=?') : settings.jsonp === false ? '' : 'callback=?')
          // 后者是决出到底谁特么才 jsonp回调函数的函数名(JSONP回调查询参数的名称)//这里appendQuery一下进行拼接。不然就用默认了。亦或者你特娘不需要回调了
      return $.ajaxJSONP(settings, deferred)
      // 这边牵扯到zepto的打包策略  默认打包的模块是没有Deferred
      // 而上面的233行也表示了。如果$.Deferred不存在则deferred为undefined
    }

    var mime = settings.accepts[dataType], // 获得一下mime。settings.accepts 下面应该还要判断一下当mime为空时该怎办
        headers = { }, // 头头头头头头
        setHeader = function(name, value) { headers[name.toLowerCase()] = [name, value] },// 设置头头头头头头  key-value对 //toLowerCase会将header都转化为小写。koa底层也有这个类似的,应该是标准
        // 这里的setHeader后对header对象输入   后面应该要组装
        protocol = /^([\w-]+:)\/\//.test(settings.url) ? RegExp.$1 : window.location.protocol,
        // 这边是正则筛选一下协议类型,如果不存在的话,则用window.location.protocol拿
        xhr = settings.xhr(),
        // 初始化一个xhr对象使用
        nativeSetHeader = xhr.setRequestHeader,
        // 拿到setRequestHeader 设置请求头
        abortTimeout
        // 应该是超时了

    if (deferred) deferred.promise(xhr)
    // 如果存在用deferred包裹xhr 这样的好处在于我们可以爽起来了。类promise

    // 标识为ajax异步请求
    // TODO: crossDomain是啥
    // TODO: X-Requested-With HTTP规范?
    if (!settings.crossDomain) setHeader('X-Requested-With', 'XMLHttpRequest')
    // 如果之前的mime是存在的话则使用mime  没有就用*/*代替所有类型(真的有判断处理)
    setHeader('Accept', mime || '*/*')
    // 用过用户有设置mimeType 或者mime不为undefined的话
    if (mime = settings.mimeType || mime) {
      // 有多个的话。则使用第一个即可
      if (mime.indexOf(',') > -1) mime = mime.split(',', 2)[0]
      // overrideMimeType 重写这次xhr的MimeType
      xhr.overrideMimeType && xhr.overrideMimeType(mime)
    }
    // 是否有设定请求内容contentType? 没有的话默认为
    // WOW:这里多了一层对contentType有可能为undefined 的处理
    // 默认为application/x-www-form-urlencoded
    if (settings.contentType || (settings.contentType !== false && settings.data && settings.type.toUpperCase() != 'GET'))
      setHeader('Content-Type', settings.contentType || 'application/x-www-form-urlencoded')
    // 是否有设置头部header 有的话则for in 循环写入暂存header对象内
    if (settings.headers) for (name in settings.headers) setHeader(name, settings.headers[name])
    // TODO: 为什么这里要转化一下?
    // 可能性1 因为之前的setRequestHeader已经代理出去了。有助于我们在以后使用相当于将setRequestHeader私有了
    xhr.setRequestHeader = setHeader
    // 主要的监听变化函数
    xhr.onreadystatechange = function(){
      // readState === 4 为成功
      if (xhr.readyState == 4) {
        // 成功后onreadystatechange 不需要啦
        xhr.onreadystatechange = empty
        // 也将超时监听器去除
        clearTimeout(abortTimeout)
        // result error 初始化为false 这种类似开关吧。
        var result, error = false
        // 判断类型 在2xx状态码范围 或者为304或者为(status=0且协议为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'))

          if (xhr.responseType == 'arraybuffer' || xhr.responseType == 'blob')
            result = xhr.response
          else {
            result = xhr.responseText

            try {
              // http://perfectionkills.com/global-eval-what-are-the-options/
              // sanitize response accordingly if data filter callback provided
              // 过滤过滤
              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)
          }

          ajaxSuccess(result, xhr, settings, deferred)
        } else {
          ajaxError(xhr.statusText || null, xhr.status ? 'error' : 'abort', xhr, settings, deferred)
        }
      }
    }
    // 这边是ajaxBeforeSend  它接受xhr对象 以及配置参数  
    // 这里的这种写法显示,我们可以在send之前,就是ajax请求发出之前对请求进行取消。
    if (ajaxBeforeSend(xhr, settings) === false) {
      xhr.abort()
      ajaxError(null, 'abort', xhr, settings, deferred)
      return xhr
    }
    // 同步还是异步。
    var async = 'async' in settings ? settings.async : true
    // 打开连接。 这里username password 代表授权用户的用户名与密码(较为不常见,所以提出)
    xhr.open(settings.type, settings.url, async, settings.username, settings.password)
    // 配置xhrFields  讲一个对象的值写入xhr实例中
    if (settings.xhrFields) for (name in settings.xhrFields) xhr[name] = settings.xhrFields[name]
    // 还记得吗nativeSetHeader之前是代理了setHeader函数的
    // 这边调用一下 写入请求头部
    for (name in headers) nativeSetHeader.apply(xhr, headers[name])
    // 如果设置了超时时间的话,起一个setTimeout来定时来处理xhr.abort() 并且清空监听函数 触发ajaxError Timeout事件
    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)
    // 做判断,不要发送空data字符串
    xhr.send(settings.data ? settings.data : null)
    return xhr
  }

  // handle optional data/success arguments
  // 处理一下传进来的参数 url data success dataType 封装的方法只有这些配置
  // 
  function parseArguments(url, data, success, dataType) {
    if ($.isFunction(data)) dataType = success, success = data, data = undefined
    if (!$.isFunction(success)) dataType = success, success = undefined
    return {
      url: url
    , data: data
    , success: success
    , dataType: dataType
    }
  }

  $.get = function(/* url, data, success, dataType */){
    // 默认就是get请求啦
    return $.ajax(parseArguments.apply(null, arguments))
  }

  $.post = function(/* url, data, success, dataType */){
    // 手动设置一下type即可使用post

    var options = parseArguments.apply(null, arguments)
    options.type = 'POST'
    return $.ajax(options)
  }
  // 手动设置下dataType
  $.getJSON = function(/* url, data, success */){
    var options = parseArguments.apply(null, arguments)
    // 设置预期服务器传回值类型
    options.dataType = 'json'
    return $.ajax(options)
  }
  // local
  //通过GET Ajax载入远程 HTML 内容代码并插入至 当前的集合 中。另外,一个css选择器可以在url中指定,像这样,可以使用匹配selector选择器的HTML内容来更新集合。
  // 挂载位置在$.fn.load证明是一个大家伙方法
  $.fn.load = function(url, data, success){
    // 参数URL data success 函数
    if (!this.length) return this
    var self = this, parts = url.split(/\s/), selector,
        // 解析参数
        options = parseArguments(url, data, success),
        // 设置callback
        callback = options.success

        // 这里有趣WOW 这里在url隔一个空格可以加一个元素选择器
        // https://a.com div
        // 然后html放回会将这个东西插给你 // 没有的 slector话就直接插给你
    if (parts.length > 1) options.url = parts[0], selector = parts[1]
    // success回调是将回调的值传入html方法
    // 相当于将片段加载为节点。
    options.success = function(response){
      self.html(selector ?
        $('<div>').html(response.replace(rscript, "")).find(selector) // /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, 可以看到是replace掉<script>xxxx</script> 保证为html
        : response)
        // WOW 这里有意思了。
      callback && callback.apply(self, arguments)
    }
    // ajax请求
    $.ajax(options)

    return this
    // 链式
  }
  // encodeURIComponent
  var escape = encodeURIComponent

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

  $.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, '+')
  }
})(Zepto)

Comments

发表评论 取消回复

电子邮件地址不会被公开。 必填项已用*标注

评论

姓名 *

电子邮件 *

站点

在此浏览器中保存我的名字、电邮和网站。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK