jQuery 源码系列(十九)ajax 的相关操作

喜儿
• 阅读 5225

欢迎来我的专栏查看系列文章。

关于 ajax,东西太多了,我本来想避开 ajax,避而不提,但觉得 ajax 这么多内容,不说又少点什么,就简单点来介绍吧,加上最近准备内推面试的时候,看了不少 ajax 的相关知识。

jQuery 源码系列(十九)ajax 的相关操作

写在 jQuery 的 ajax 之前

首先,我们还是来了解一下 js 中的 http 请求。http 协议中有请求体和响应体,对于请求的一方,无论是哪一种语言,我比较关心如下几个方面:请求的配置参数包括 url,post/get 等;请求有请求头,那么请求头的参数又该由哪个函数来设置;如何判断请求已经成功;响应状态码和响应数据该如何获得等等。

XMLHttpRequest 对象

每天都喊着要写原生的 js 请求,那么来了,就是这个函数 XMLHttpRequest,它是一套可以在Javascript、VbScript、Jscript等脚本语言中通过http协议传送或接收XML及其他数据的一套API,万恶的低版本 IE 有个兼容的 ActiveXObject

它有两个版本,第一个版本的功能很少,在不久之后又有了点一个更完善的版本 2.0,功能更全。如果你感兴趣,可以来这里看一下XMLHttpRequest。如果你对 http 协议有着很好的掌握的话,也可以看下面的内容。

实现一个简单的 ajax 请求

如果你碰到面试官,让你手写一个原生的 ajax 请求,那么下面的东西可能对你非常有帮助:

// myAjax
var myAjax = (function(){
  var defaultOption = {
    url: false,
    type: 'GET',
    data: null,
    success: false,
    complete: false
  }

  var ajax = function(options){
    for(var i in defaultOption){
      options[i] = options[i] || defaultOption[i];
    }
    // http 对象
    var xhr = new XMLHttpRequest();
    var url = options.url;
    xhr.open(options.type, url);
    // 监听
    xhr.onreadystatechange = function(){
      if(xhr.readyState == 4){
        var result, status = xhr.status;
        if(status >= 200 && status < 300 || status == 304){
          result = xhr.responseText;
          if(window.JSON){
            result = JSON.parse(result);
          }else{
            result = eval('(' + result + ')');
          }
          ajaxSuccess(result)
        }
      }
    }
    // post
    if(options.type.toLowerCase() === 'post'){
      xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencode');
    }
    xhr.send(options.data);

    function ajaxSuccess(data){
      var status = 'success';
      options.success && options.success(data, options, status, xhr);
      options.complete && options.complete(status);
    }
  }
  // 闭包返回
  return ajax;
})()

测试在下面:

var success = function(data){
  console.log(data['blog'])
}
var complete = function(status){
  if(status == 'success'){
    console.log('success')
  }else{
    console.log('failed')
  }
}
myAjax( {
  url: 'https://api.github.com/users/songjinzhong',
  success: success,
  complete: complete
} );

可以得到 XMLHttpRequest 的简单用法:

  1. 通过 new XMLHttpRequest() 建立一个 http 请求对象;

  2. open 函数的作用是设置要打开的 url 和类型,建立一个连接,但此时请求并没有发送;

  3. setRequestHeader 来设置请求头信息;

  4. send 函数像服务器发送数据请求;

  5. onreadystatechange 是一个监听函数,当 readyState 改变的时候执行,1-2-3-4,4 表示成功返回。xhr.responseText 是返回的响应数据,很明显,这里是 json 格式,实际要通过响应头来判断,这里省去了这一步,getAllResponseHeaders 可以获得所有响应头;

  6. success 函数和 complete 函数执行的位置和顺序问题。

jQuery ajax 的特点

通过上面的例子,应该可以对 js 的 http 请求有个大致的了解,而 jQuery 的处理则复杂的多,也涉及到和上面功能类似的一些函数,而对于 callback 和 deferred,jQuery 本身就支持:

var deferred = jQuery.Deferred(),
  completeDeferred = jQuery.Callbacks( "once memory" );

所以说 jQuery 是一个自给自足的库,一点也不过分,前面有 Sizzle,整个源码到处都充满着 extend 函数,等等。

jQuery.ajaxSetup

ajaxSetup 是在 ajax 函数里比较早执行的一个函数,这个函数主要是用来校准参数用的;

jQuery.extend( {
  ajaxSetup: function( target, settings ) {
    return settings ?

      // 双层的 ajaxExtend 函数
      ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :

      // Extending ajaxSettings
      ajaxExtend( jQuery.ajaxSettings, target );
  },
} );

ajaxSettings 是一个对象,具体是干什么用的,看看就知道了:

jQuery.ajaxSettings = {
  url: location.href,
  type: "GET",
  isLocal: rlocalProtocol.test( location.protocol ),
  global: true,
  processData: true,
  async: true,
  contentType: "application/x-www-form-urlencoded; charset=UTF-8",

  accepts: {
    "*": allTypes,
    text: "text/plain",
    html: "text/html",
    xml: "application/xml, text/xml",
    json: "application/json, text/javascript"
  },

  contents: {
    xml: /\bxml\b/,
    html: /\bhtml/,
    json: /\bjson\b/
  },

  responseFields: {
    xml: "responseXML",
    text: "responseText",
    json: "responseJSON"
  },

  // Data converters
  // Keys separate source (or catchall "*") and destination types with a single space
  converters: {

    // Convert anything to text
    "* text": String,

    // Text to html (true = no transformation)
    "text html": true,

    // Evaluate text as a json expression
    "text json": JSON.parse,

    // Parse text as xml
    "text xml": jQuery.parseXML
  },

  // For options that shouldn't be deep extended:
  // you can add your own custom options here if
  // and when you create one that shouldn't be
  // deep extended (see ajaxExtend)
  flatOptions: {
    url: true,
    context: true
  }
}

ajaxSettings 原来是一个加强版的 options。

ajaxExtend 是用来将 ajax 函数参数进行标准化的,看看哪些参数没有赋值,让它等于默认值,由于 ajaxExtend 是双层的,具体要调试了才能更明白。

function ajaxExtend( target, src ) {
  var key, deep,
    flatOptions = jQuery.ajaxSettings.flatOptions || {};

  for ( key in src ) {
    if ( src[ key ] !== undefined ) {
      ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ];
    }
  }
  if ( deep ) {
    jQuery.extend( true, target, deep );
  }

  return target;
}

ajax.jqXHR

在 ajax 中有一个非常重要的对象,jqXHR,它虽然是一个简称,但通过缩写也大致能猜出它是 jquery-XMLHttpRequest

jqXHR = {
  readyState: 0, // 0-4

  // 熟悉响应头的对这个应该不陌生,将响应头数据按照 key value 存储起来
  getResponseHeader: function( key ) {
    var match;
    if ( completed ) {
      if ( !responseHeaders ) {
        responseHeaders = {};
        while ( ( match = /^(.*?):[ \t]*([^\r\n]*)$/mg.exec( responseHeadersString ) ) ) {
          responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ];
        }
      }
      match = responseHeaders[ key.toLowerCase() ];
    }
    return match == null ? null : match;
  },

  // Raw string
  getAllResponseHeaders: function() {
    return completed ? responseHeadersString : null;
  },

  // 手动设置请求头
  setRequestHeader: function( name, value ) {
    if ( completed == null ) {
      name = requestHeadersNames[ name.toLowerCase() ] =
        requestHeadersNames[ name.toLowerCase() ] || name;
      requestHeaders[ name ] = value;
    }
    return this;
  },

  // Overrides response content-type header
  overrideMimeType: function( type ) {
    if ( completed == null ) {
      s.mimeType = type;
    }
    return this;
  },

  // Status-dependent callbacks
  statusCode: function( map ) {
    var code;
    if ( map ) {
      if ( completed ) {

        // Execute the appropriate callbacks
        jqXHR.always( map[ jqXHR.status ] );
      } else {

        // Lazy-add the new callbacks in a way that preserves old ones
        for ( code in map ) {
          statusCode[ code ] = [ statusCode[ code ], map[ code ] ];
        }
      }
    }
    return this;
  },

  // Cancel the request
  abort: function( statusText ) {
    var finalText = statusText || strAbort;
    if ( transport ) {
      transport.abort( finalText );
    }
    done( 0, finalText );
    return this;
  }
};

jqXHR 已经完全可以取代 XHR 对象了,函数都进行扩展了。

ajaxTransport

那么 XMLHttpRequest 这个函数到底在哪呢?

jQuery 中有两个属性,分别是 ajaxPrefilterajaxTransport,它们是由 addToPrefiltersOrTransports 函数构造的。主要来看 ajaxTransport 函数:

jQuery.ajaxTransport( function( options ) {
  var callback, errorCallback;

  // Cross domain only allowed if supported through XMLHttpRequest
  if ( support.cors || xhrSupported && !options.crossDomain ) {
    return {
      send: function( headers, complete ) {
        var i,
          xhr = options.xhr();// xhr() = XMLHttpRequest()
        xhr.open(
          options.type,
          options.url,
          options.async,
          options.username,
          options.password
        );

        // Apply custom fields if provided
        if ( options.xhrFields ) {
          for ( i in options.xhrFields ) {
            xhr[ i ] = options.xhrFields[ i ];
          }
        }

        // Override mime type if needed
        if ( options.mimeType && xhr.overrideMimeType ) {
          xhr.overrideMimeType( options.mimeType );
        }

        // X-Requested-With header
        // For cross-domain requests, seeing as conditions for a preflight are
        // akin to a jigsaw puzzle, we simply never set it to be sure.
        // (it can always be set on a per-request basis or even using ajaxSetup)
        // For same-domain requests, won't change header if already provided.
        if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) {
          headers[ "X-Requested-With" ] = "XMLHttpRequest";
        }

        // Set headers
        for ( i in headers ) {
          xhr.setRequestHeader( i, headers[ i ] );
        }

        // Callback
        callback = function( type ) {
          return function() {
            if ( callback ) {
              callback = errorCallback = xhr.onload =
                xhr.onerror = xhr.onabort = xhr.onreadystatechange = null;

              if ( type === "abort" ) {
                xhr.abort();
              } else if ( type === "error" ) {

                // Support: IE <=9 only
                // On a manual native abort, IE9 throws
                // errors on any property access that is not readyState
                if ( typeof xhr.status !== "number" ) {
                  complete( 0, "error" );
                } else {
                  complete(

                    // File: protocol always yields status 0; see #8605, #14207
                    xhr.status,
                    xhr.statusText
                  );
                }
              } else {
                complete(
                  xhrSuccessStatus[ xhr.status ] || xhr.status,
                  xhr.statusText,

                  // Support: IE <=9 only
                  // IE9 has no XHR2 but throws on binary (trac-11426)
                  // For XHR2 non-text, let the caller handle it (gh-2498)
                  ( xhr.responseType || "text" ) !== "text"  ||
                  typeof xhr.responseText !== "string" ?
                    { binary: xhr.response } :
                    { text: xhr.responseText },
                  xhr.getAllResponseHeaders()
                );
              }
            }
          };
        };

        // Listen to events
        xhr.onload = callback();
        errorCallback = xhr.onerror = callback( "error" );

        // Support: IE 9 only
        // Use onreadystatechange to replace onabort
        // to handle uncaught aborts
        if ( xhr.onabort !== undefined ) {
          xhr.onabort = errorCallback;
        } else {
          xhr.onreadystatechange = function() {

            // Check readyState before timeout as it changes
            if ( xhr.readyState === 4 ) {

              // Allow onerror to be called first,
              // but that will not handle a native abort
              // Also, save errorCallback to a variable
              // as xhr.onerror cannot be accessed
              window.setTimeout( function() {
                if ( callback ) {
                  errorCallback();
                }
              } );
            }
          };
        }

        // Create the abort callback
        callback = callback( "abort" );

        try {

          // Do send the request (this may raise an exception)
          xhr.send( options.hasContent && options.data || null );
        } catch ( e ) {

          // #14683: Only rethrow if this hasn't been notified as an error yet
          if ( callback ) {
            throw e;
          }
        }
      },

      abort: function() {
        if ( callback ) {
          callback();
        }
      }
    };
  }
} );

ajaxTransport 函数返回值有两个,其中 send 就是发送函数了,一步一步,发送下来,无需多说明。

另外,ajax 对于 jQuery 对象在 ajax 过程提供了很多回调函数:

jQuery.each( [
  "ajaxStart",
  "ajaxStop",
  "ajaxComplete",
  "ajaxError",
  "ajaxSuccess",
  "ajaxSend"
], function( i, type ) {
  jQuery.fn[ type ] = function( fn ) {
    return this.on( type, fn );
  };
} );

jQuery.event.trigger( "ajaxStart" );
...
globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
...
globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",[ jqXHR, s, isSuccess ? success : error ] );
...
globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
...
jQuery.event.trigger( "ajaxStop" );

ajax 东西太多了,至少有 1000 行的代码吧。

总结

关于 ajax,不想去深入研究了,最近暑假实习校招已经开始启动了,暂时先放一放吧,以后有时间再来填坑吧。

参考

jQuery源码分析系列(30) : Ajax 整体结构
jQuery源码分析系列(37) : Ajax 总结
触碰jQuery:AJAX异步详解

本文在 github 上的源码地址,欢迎来 star。

欢迎来我的博客交流。

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Wesley13 Wesley13
4年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Wesley13 Wesley13
4年前
java将前端的json数组字符串转换为列表
记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang
Wesley13 Wesley13
4年前
layim的websocket消息撤回功能实现
我的大概思路就是,前端根据选取的内容获得他的cid,我的cid是js生成的uuid,然后:1、通过websocket广播给对应的人去删除localstorage里的缓存,2、ajax异步请求删除数据库里的数据记录3、如果对方此时也打开了聊天面板就要用jquery找到那条消息然后remove。由于目前发现layim3.6版本并没有给自己
Karen110 Karen110
4年前
​一篇文章总结一下Python库中关于时间的常见操作
前言本次来总结一下关于Python时间的相关操作,有一个有趣的问题。如果你的业务用不到时间相关的操作,你的业务基本上会一直用不到。但是如果你的业务一旦用到了时间操作,你就会发现,淦,到处都是时间操作。。。所以思来想去,还是总结一下吧,本次会采用类型注解方式。time包importtime时间戳从1970年1月1日00:00:00标准时区诞生到现在
Stella981 Stella981
4年前
Django框架 之 Ajax
Django框架之Ajax浏览目录AJAX准备知识AJAX与XML的比较AJAX简介jQuery实现的ajaxAJAX参数AJAX请求如何设置csrf\_token序列化
Stella981 Stella981
4年前
Django (二)使用 JQuery、Ajax
一、作业内容1、班级表的操作,包括增加、编辑、删除。要求(1)增加、编辑,弹出对话框;(2)这些操作用JQuery、Ajax实现。2、学生表的操作,包括增加、编辑、删除。要求(1)增加、编辑,弹出对话框;(2)这些操作用Jquery、Ajax实现。3、教师表的操作,包括增加、编辑、删除。要求(1)增加、编辑,弹出对话框;(2)这些操作用Jq
Wesley13 Wesley13
4年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Stella981 Stella981
4年前
Ajax快速入门
最近需要使用ajax,json传数据,快速学习了下ajax,把基本的东西拿出来分享一下,打算以问题的形式来进行文章的编写~go!(一)什么是Ajax?Ajax是一种无需加载整个网页,快速刷新局部网页的技术。Ajax不是新的编程语言,而是一些老技术的融合。(二)Ajax用到了什么技术?异步数据获取技术:使用XMLHttpReques
Wesley13 Wesley13
4年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。