XMLHttpRequest详解

204 阅读5分钟

简单事情重复做,每天进步一点点

记录自己的学习过程。

XMLHttpRequest(XHR)对象用于与服务器交互。通过 XMLHttpRequest 可以在不刷新页面的情况下请求特定 URL,获取数据。这允许网页在不影响用户操作的情况下,更新页面的局部内容。XMLHttpRequest 在 AJAX 编程中被大量使用。

所有现代浏览器 (IE7+、Firefox、Chrome、Safari 以及 Opera) 都内建了 XMLHttpRequest 对象。通过一行简单的 JavaScript 代码,我们就可以创建 XMLHttpRequest 对象。

const xhr = new XMLHttpRequest();

老版本的 Internet Explorer (IE5 和 IE6)使用 ActiveX 对象:

const xhr = new ActiveXObject("Microsoft.XMLHTTP");

使用xhr对象的时候,浏览器首先会检测原生的xhr是否存在,如果存在则返回它的新实例,如果原生对象不存在就检测ActiveX对象,如果都不存在,就抛出一个错误。

var xmlhttp=null;
if (window.XMLHttpRequest){ //code for all new browsers
  xmlhttp=new XMLHttpRequest();
}else if (window.ActiveXObject){ //code for IE5 and IE6
  xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
}

if (xmlhttp!=null){
  xmlhttp.onreadystatechange=state_Change;  //监听请求状态
  xmlhttp.open("GET",url,true); //初始化请求参数
  xmlhttp.setRequestHeader('Content-type','application/x-www-form-urlencode');  //设置请求头
  xmlhttp.send(null);  //发送请求
}else{
  alert("Your browser does not support XMLHTTP.");
}

function state_Change(){
    if (xmlhttp.readyState==4){// 4 = "loaded"
        if (xmlhttp.status==200){// 200 = OK
            // ...our code here...
        }else{
            alert("Problem retrieving XML data");
        }
    }
}

其中open()方法接收三个参数: 1、要发送的请求类型 GET || POST

2、请求的URL

3、是否异步请求 true || false

onreadystatechange 是一个事件句柄。它的值 (state_Change) 是一个函数的名称,当 XMLHttpRequest 对象的状态发生改变时,会触发此函数。状态从 0 (uninitialized) 到 4 (complete) 进行变化。仅在状态为 4 时,我们才执行代码。

abort(); //取消请求

XMLHttpRequest属性:

readyState: HTTP 请求的状态.当一个 XMLHttpRequest 初次创建时,这个属性的值从 0 开始,直到接收到完整的 HTTP 响应,这个值增加到 4。

0:未初始化,XMLHttpRequest 对象已创建,但尚未调用open()方法

1:启动,open() 方法已调用,但是 send() 方法未调用。请求还没有被发送。

2:发送,Send() 方法已调用,HTTP 请求已发送到 Web 服务器。未接收到响应。

3:接收,所有响应头部都已经接收到。响应体开始接收但未完成。

4:完成,HTTP 响应已经完全接收。

responseText: 响应体(不包括头部),如果还没有接收到数据的话,就是空字符串。如果 readyState 小于 3,这个属性就是一个空字符串。当 readyState 为 3,这个属性返回目前已经接收的响应部分。如果 readyState 为 4,这个属性保存了完整的响应体。

responseXML: 如果响应的内容类型是"text/xml"或者"application/xml",这个属性中将保存包含着响应数据的xml dom文档

status: 由服务器返回的 HTTP 状态代码,如 200 表示成功,而 404 表示 "Not Found" 错误。当 readyState 小于 3 的时候读取这一属性会导致一个异常。

statusText: http状态说明 

以下是AXIOS请求的源码:
封装一个xhrAdapter方法,xhrAdapter方法接收一个config配置对象,并返回一个Promise
大致分为以下几个方面:
1、通过var request = new XMLHttpRequest()创建request对象
2、鉴权:if (config.auth) {...}, 判断config中是否携带auth参数
3、request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true)
调用open()方法来初始化请求参数,第一个参数是将所有的请求类型转换为大写,第二个参数构建url,第三个参数是否异步请求
4、request.onreadystatechange方法处理请求状态
5、if ('setRequestHeader' in request) { //此代码段添加请求头 } 
6、if (config.cancelToken) { //此代码段取消请求 返回一个Promise  } 
7、最后调用send()方法发送请求
其它的就是一些异常错误处理,我们可以看到很多地方都会调用request = null,这个主要是在取消请求中使用,if (!request) {return;}请求不存在时就不会执行后面的代码。

完整代码:
function xhrAdapter(config) {
  return new Promise(function dispatchXhrRequest(resolve, reject) {
    var requestData = config.data;
    var requestHeaders = config.headers;

    if (utils.isFormData(requestData)) {
      delete requestHeaders['Content-Type']; // Let the browser set it
    }

    var request = new XMLHttpRequest();

    // HTTP basic authentication
    if (config.auth) {
      var username = config.auth.username || '';
      var password = config.auth.password ? unescape(encodeURIComponent(config.auth.password)) : '';
      requestHeaders.Authorization = 'Basic ' + btoa(username + ':' + password);
    }

    var fullPath = buildFullPath(config.baseURL, config.url);
    request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true);

    // Set the request timeout in MS
    request.timeout = config.timeout;

    // Listen for ready state
    request.onreadystatechange = function handleLoad() {
      if (!request || request.readyState !== 4) {
        return;
      }

      // The request errored out and we didn't get a response, this will be
      // handled by onerror instead
      // With one exception: request that using file: protocol, most browsers
      // will return status as 0 even though it's a successful request
      if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) {
        return;
      }

      // Prepare the response
      var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null;
      var responseData = !config.responseType || config.responseType === 'text' ? request.responseText : request.response;
      var response = {
        data: responseData,
        status: request.status,
        statusText: request.statusText,
        headers: responseHeaders,
        config: config,
        request: request
      };

      settle(resolve, reject, response);

      // Clean up request
      request = null;
    };

    // Handle browser request cancellation (as opposed to a manual cancellation)
    request.onabort = function handleAbort() {
      if (!request) {
        return;
      }

      reject(createError('Request aborted', config, 'ECONNABORTED', request));

      // Clean up request
      request = null;
    };

    // Handle low level network errors
    request.onerror = function handleError() {
      // Real errors are hidden from us by the browser
      // onerror should only fire if it's a network error
      reject(createError('Network Error', config, null, request));

      // Clean up request
      request = null;
    };

    // Handle timeout
    request.ontimeout = function handleTimeout() {
      var timeoutErrorMessage = 'timeout of ' + config.timeout + 'ms exceeded';
      if (config.timeoutErrorMessage) {
        timeoutErrorMessage = config.timeoutErrorMessage;
      }
      reject(createError(timeoutErrorMessage, config, 'ECONNABORTED',
        request));

      // Clean up request
      request = null;
    };

    // Add xsrf header
    // This is only done if running in a standard browser environment.
    // Specifically not if we're in a web worker, or react-native.
    if (utils.isStandardBrowserEnv()) {
      // Add xsrf header
      var xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath)) && config.xsrfCookieName ?
        cookies.read(config.xsrfCookieName) :
        undefined;

      if (xsrfValue) {
        requestHeaders[config.xsrfHeaderName] = xsrfValue;
      }
    }

    // Add headers to the request
    if ('setRequestHeader' in request) {
      utils.forEach(requestHeaders, function setRequestHeader(val, key) {
        if (typeof requestData === 'undefined' && key.toLowerCase() === 'content-type') {
          // Remove Content-Type if data is undefined
          delete requestHeaders[key];
        } else {
          // Otherwise add header to the request
          request.setRequestHeader(key, val);
        }
      });
    }

    // Add withCredentials to request if needed
    if (!utils.isUndefined(config.withCredentials)) {
      request.withCredentials = !!config.withCredentials;
    }

    // Add responseType to request if needed
    if (config.responseType) {
      try {
        request.responseType = config.responseType;
      } catch (e) {
        // Expected DOMException thrown by browsers not compatible XMLHttpRequest Level 2.
        // But, this can be suppressed for 'json' type as it can be parsed by default 'transformResponse' function.
        if (config.responseType !== 'json') {
          throw e;
        }
      }
    }

    // Handle progress if needed
    if (typeof config.onDownloadProgress === 'function') {
      request.addEventListener('progress', config.onDownloadProgress);
    }

    // Not all browsers support upload events
    if (typeof config.onUploadProgress === 'function' && request.upload) {
      request.upload.addEventListener('progress', config.onUploadProgress);
    }

    if (config.cancelToken) {
      // Handle cancellation
      config.cancelToken.promise.then(function onCanceled(cancel) {
        if (!request) {
          return;
        }

        request.abort();
        reject(cancel);
        // Clean up request
        request = null;
      });
    }

    if (!requestData) {
      requestData = null;
    }

    // Send the request
    request.send(requestData);
  });
}