Http请求和XMLHttpRequest对象

XMLHttpRequest用于同HTTP和HTTPS协议一起工作。

2008年2月,就提出了 XHR2 草案。

目前,IE9+和主流浏览器支持 XHR2。

一、XHR1 和 XHR2 对比

1. XHR1的缺点

老版本的XMLHttpRequest对象有以下几个缺点:

  • 只支持文本数据的传送,无法用来读取和上传二进制文件
  • 传送和接收数据时,没有进度信息,只能提示有没有完成
  • 受到"同域限制"(Same Origin Policy),只能向同一域名的服务器请求数据。

2. XHR2的功能

新版本的XMLHttpRequest对象,针对老版本的缺点,做出了大幅改进。

  • 可以设置HTTP请求的超时时间
  • 可以使用FormData对象作为请求参数
  • 可以跨域请求(CORS)
  • 可以传输二进制数据
  • 可以获得数据传输的进度信息

1. HTTP请求的超时时间

// 将最长等待时间设为3000毫秒,过了这个时限,就自动停止HTTP请求
xhr.timeout = 3000;
// 与之配套的还有一个timeout事件
xhr.ontimeout = function(event){
  alert('请求超时!');
}

2. 接收二进制数据(responseType属性)

你可以把responseType设为blob,表示服务器传回的是二进制对象。

var xhr = new XMLHttpRequest();
xhr.open('GET', '/path/to/image.png');
xhr.responseType = 'blob';

文件类型是更通用的二进制大对象(Blob)类型中的一个子类型。XHR2允许向 send() 方法传入任何 Blob 对象。如果没有显示设置“Content-Type”头,这个Blob对象的type属性用于设置待上传的“Content-Type”头。如果需要上传已经产生的二进制数据,可以把数据转换为Blob并将其作为请求主体。

3. 进度信息

新版本的XMLHttpRequest对象,传送数据的时候,有一个progress事件,用来返回进度信息。

它分成上传和下载两种情况。下载的progress事件属于XMLHttpRequest对象,上传的progress事件属于XMLHttpRequest.upload对象。

我们先定义progress事件的回调函数。

xhr.onprogress = updateProgress;
xhr.upload.onprogress = updateProgress;

然后,在回调函数里面,使用这个事件的一些属性。

function updateProgress(event) {
  if (event.lengthComputable) {
    var percentComplete = event.loaded / event.total;
  }
}

上面的代码中,event.total是需要传输的总字节,event.loaded是已经传输的字节。如果event.lengthComputable不为真,则event.total等于0

progress事件相关的,还有其他五个事件,可以分别指定回调函数:

  • load事件:传输成功完成
  • abort事件:传输被用户取消
  • error事件:传输中出现错误
  • loadstart事件:传输开始
  • loadEnd事件:传输结束,但是不知道成功还是失败

二、封装AJAX 请求

xhr请求基本步骤:

// 实例化
let xhr = new XMLHttpRequest()
// 初始化
xhr.open(method, url, async)
// 设置状态变化回调处理请求结果
xhr.onreadystatechange = () => {
  if (xhr.readyStatus === 4 && xhr.status === 200) {
    console.log(xhr.responseText)
  }
}
// 发送请求
xhr.send(data)

封装Ajax请求:

// 2. 基于promise实现 
function ajax (options) {
    const url = options.url
    const method = options.method.toLocaleLowerCase() || 'get'
    // 默认为异步true
    const async = options.async === undefined ? true : options.async
    // 请求参数
    const data = options.data
    const xhr = window.XMLHttpRequest 
          ? new XMLHttpRequest() 
          : ActiveXObject("microsoft.XMLHttp");
    // 请求超时
    if (options.timeout && options.timeout > 0) {
      xhr.timeout = options.timeout
    }

    return new Promise ((resolve, reject) => {
      xhr.ontimeout = () => reject && reject('请求超时')
      xhr.onreadystatechange = () => {
        if (xhr.readyState == 4) {
          // 200-300 之间表示请求成功,304资源未变,取缓存
          if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
            resolve && resolve(xhr.responseText)
          } else {
            reject && reject()
          }
        }
      }
      xhr.onerror = err => reject && reject(err)
      let paramArr = []
      let encodeData
      // 处理请求参数
      if (data instanceof Object) {
        for (let key in data) {
          // 参数拼接需要通过 encodeURIComponent 进行编码
          paramArr.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key]))
        }
        encodeData = paramArr.join('&')
      }
      if (method === 'get') {
        // 检测url中是否已存在 ? 及其位置
        const index = url.indexOf('?')
        if (index === -1) {url += '?'}
        else if (index !== url.length -1) {url += '&'}
        url += encodeData
      }
      // 初始化
      xhr.open(method, url, async)
      // 发送请求
      if (method === 'get') {xhr.send(null)}
      else {
        // post 方式需要设置请求头
        xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded;charset=UTF-8')
        xhr.send(encodeData)
      }
    })
  }

三、XMLHttpRequest 对象的属性、方法和事件

1. XMLHttpRequest不是协议级的HTTP API而是浏览器级的API。

2. HTTP请求的各部分有指定顺序:请求方法和URL首先到达,然后是请求头,最后是请求主体。调用XMLHttpRequest方法的顺序必须匹配HTTP请求的架构。例如,setRequestHeader() 方法的调用必须在调用 open() 之后 send() 之前,否则它将抛出异常。

1. 属性

1. xhr.readyState

HTTP请求的状态以及服务器的响应,分为5种:

状态描述
0UNSENT这是XMLHttpRequest对象刚创建或被abort()方法重置时readyState属性的初始值。
1OPENED已经调用open()方法,但还没调用send()方法。还没有发送请求。
2HEADERS_RECEIVEDsend()方法已调用,已经接收到响应头,但还没有接收到响应主体。
3LOADING正在接收响应主体,但还没有完成。
4DONEHTTP响应已全部接收,或由于错误而停止。

理论上,每当这个属性的值改变时都会分发一个readystatechange事件。但实际上,只有readystate改变为4时才保证会触发这个事件。(XHR2的进度事件提供了一种更可靠的跟踪请求处理的方法。)

2. xhr.responseType

在XHR2中,这个属性指明期望的响应类型,并判断response属性的类型。如果设置这个属性, responseText和responsexML属性将抛出异常,必须使用XHR2的response属性来获得服务器的响应。

注意:给一个同步请求设置responseType会抛出一个InvalidAccessError 的异常。

responseType支持以下几种值:

response 返回数据类型
“” 或 “text”默认类型 (DOMString)。
“arraybuffer”一个包含二进制数据的 JavaScript ArrayBuffer 
"blob"一个包含二进制数据的 Blob 对象
"document"HTML Document 或 XML XMLDocument ,取决于接收到的数据的 MIME 类型。
"json"JSON 对象,这个对象是通过将接收到的数据类型视为 JSON 解析得到的。

3. xhr.response

response 属性返回响应的正文。返回的类型可以是 ArrayBuffer 、 Blob 、 Document 、 JavaScript Object 或 DOMString 。 这取决于 responseType 属性。

4. xhr.responseText

如果readyState小于3,这个属性将是空字符串。当readystate为3时,这个属性将返回目前已接收到的响应部分。如果readystate为4,这个属性的值为响应的全部主体。

如果服务器想发送诸如对象或数组这样的结构化数据作为其响应,它应该传输JSON字符串。当接收它时,可以把 responseText 属性传递给 JSON.parse()。

5. responseXML

请求对应的响应,已解析为一个XML或HTML Document对象,如果响应主体还没有就绪或不是一个有效的XML或HTML文档则为null。

5. xhr.status

服务器返回的HTTP状态码,比如200代表成功, 404代表“页面未找到”错误,如果服务器还设有设置状态码则为0。

在请求完成前,status的值为0。如果 XMLHttpRequest 出错,浏览器返回的 status 也为0。 

6. xhr.statusText

这个属性使用名字而不是数字来指定请求的HTTP状态码,就是说,当状态为200时它将是“OK",状态是404时它将是"Not Found" .如果服务器还没有设置状态码,这个属性将为空字符串。

6. xhr.timeout

这个XHR2属性指定一个超时时间,单位为毫秒,默认值为 0,意味着没有超时。如果HTTP请求花费的时间超过这个时间,它将中止,同时触发一个超时事件。只能在调用open()之后以及调用send()之前设置这个属性。

注意:同步请求不能设置 timeout,否则将会抛出一个 InvalidAccessError 类型的错误。

7. xhr.upload

这个XHR2属性指代一个XMLHttpRequestUpload对象,用来表示上传的进度。

8. xhr.withCredentials

用来指定跨域的请求是否应该使用证书(如cookie或授权header头)。同源时使用withCredentials属性是无效的。

此外,这个指示也会被用做响应时能不能设置 cookies 的标示。默认值是false。

通过设置withCredentials 为true获得的第三方cookies,将会依旧享受同源策略,因此不能被通过document.cookie或者从头部相应请求的脚本等访问。

注意:withCredentials 属性永远不会影响到同源请求。

2. 方法

1. xhr.open()

open() 用于初始化一个请求。

xhr.open(method, url, [async, user, password]);
  • method:是请求方法,这个字符串不区分大小写,但通常大家用大写字母。已可靠实现的值包括GET, POST以及HEAD,有些浏览器可能也实现了CONNECT, DELETE, OPTIONS, PUT, TRACE以及TRACK方法。
  • url:url为正在请求的URL,相对URL将按常规方法根据包含当前脚本的文档的URL解析。同源安全策略要求这个URL与包含发起当前请求的脚本的文档拥有同样的主机和端口。XHR2允许向支持CORS的服务器发起跨城请求。
  • async:表示请求是否异步,默认为 true 异步。如果指定async参数并且值为false,则请求将为同步方式, send()方法将阻塞页面,直到响应完成。除非是在Worker中使用XMLHtpRequest,否则不推荐使用这个方法。如果multipart属性为true则这个必须为true,否则将引发异常。
  • 可选的user和pass参数指定用于HTTP请求的用户名和密码。

2. xhr.setRequestHeader(name, value)

这个方法只能在readystate为1时调用,即在调用open()之后,但在调用send()之前。
如果指定name的头信息已经定义了,则这个头信息的新值将是它之前的值加一个逗号、一个空格之后再加上在这次调用中指定的value。

如果对open()的调用指定了授权凭证,则XMLHttpRequest将自动发送一个适当的Authorization请求头信息。不过,也可以使用setRequestHeader()方法手动追加头信息。

XMLHttpRequest自动设置"Content-Length"、"Date"、"Referer"以及"UserAgent",不允许谎报它们。还有一些其他头信息,包括与cookie相关的信息,无法使用这个方法来设置。

自定义一些header属性进行跨域请求时,可能会遇到"not allowed by Access-Control-Allow-Headers in preflight response",你可能需要在你的服务端设置"Access-Control-Allow-Headers"。

3. xhr.send(body)

这个方法发出一个HTTP请求。如果之前没有调用过open(),或更一般一些,如果readystate不是1, send()将抛出一个异常。否则,它将发起一个包含以下内容的HTTP请求:

  • 之前调用open()时设置的HTTP方法、URL以及授权凭证(如果有的话)。
  • 之前调用setRequestHeader()定义的请求头信息(如果有的话)。
  • 传入到这个方法的body参数。body可以是一个指定请求主体的字符串或一个Document对象,如果请求没有主体(例如GET请求就不会有主体)也可以省略或为null。在XHR2中,主体也可以是一个ArrayBuffer,一个Blob或一个FormData对象。

如果之前调用open()时的async参数为false,则这个方法将阻塞进程,直到readystate为4并且服务器的响应已经完全接收时才会返回。否则, send()将立即返回,同时服务器的响应将通过事件处理程序提供的通知异步地处理。

POST请求通常拥有主体。同时它应该匹配使用 setRequestHeader() 指定的“Content-Type”头。

4. xhr.abord()

这个方法将当前XMLHttpRequest对象重置为readyState为0的状态,同时取消所有推迟的网络活动。例如,如果一个请求占用太长的时间,但对应的响应已经不再需要了,就可以调用这个方法。

示例:

// 如果请求超时则中止请求(超时也可以利用xhr的timeout属性,这里用一次性定时器代替)
function timedGetText(url, timeout, cb) {
  let timedout = false;
  let xhr = new XMLHttpRequest();
  let timer = setTimeout(function() {
    timedout = true;
    xhr.abort();
  }, timeout);
  xhr.open('GET', url);
  xhr.onreadystatechange = function() {
    if (xhr.readyState == 4 && !timedout) {
      clearTimeout(timer);
      if (xhr.status == 200) {
        cb && cb(xhr.responseText);
      }
    }
  }
  xhr.send();
}

5. xhr.getAllResponseHeaders()

这个方法返回服务器发送的HTTP响应头信息(过滤掉cookie及CORS头信息) ,如果头信息还没接收到则为null。所有的头信息以一个单独的字符串的形式返回,每行一个头信息,每个头信息通过CRLF(即,\r\n)进行分割。对于复合请求,这个方法返回当前请求的头部,而不是最初的请求的头部。

示例:

xhr.onreadystatechange = function() {
  if(this.readyState == this.HEADERS_RECEIVED) {
    var headers = xhr.getAllResponseHeaders();
    var arr = headers.trim().split(/[\r\n]+/);

    var headerMap = {};
    arr.forEach(function (line) {
      var parts = line.split(': ');
      var header = parts.shift();
      var value = parts.join(': ');
      headerMap[header] = value;
    });
  }
}

6. xhr.getResponseHeader() 

返回指定名字(不区分大小写)的HTTP响应header,如果该响应头还没有接收到或者在或响应中不存在指定header则为null. cookie和CORS相关的头信息已过滤,不可通过这个方法查询。如果响应包含多个这个指定名字的头,则返回的字符串将包含所有这些头的值,多个值之间使用一个逗号和一个空格连接与分隔。

xhr.getResponseHeader("Content-Type"))

7. xhr.overrideMimeType(mimeType)

这个方法指明使用指定的mime类型而不是使用响应的头信息中的Content-Type来解析服务器的响应。

此方法必须在 send() 方法之前调用方为有效。

3. 事件

1. onreadystatechange

理论上,每次 readyState 属性改变都会触发 readystatechange 事件。实际中,当 readyState 改变为 0 或 1 时可能没有触发这个事件。当调用 send() 时,即使 readyState 仍处于 OPENED 状态,也通常触发它。某些浏览器在 LOADING 状态时能触发多次事件来给出进度反馈。当 readyState 值改变为 4 或服务器的响应完成时,所有浏览器都触发 readystatechange 事件。

当一个 XMLHttpRequest 请求被 abort() 方法取消时,readystatechange 事件不会触发。

注意:这个方法不该用于同步的requests对象。

2. onloadstart

当调用 send() 时,触发单个 loadstart 事件。

3. onprogress

当响应主体正在下载时重复触发(约每50ms一次) 。所以可以使用这个事件给用户反馈请求的进度。如果请求快速完成,它可能不会触发 progress 事件。

与 progress 事件相关联的事件对象还有3个有用的属性:

  • loaded:目前传输的字节数值
  • total:自“Content-Type”头传输的数据的整体长度(单位字节),如果不知道内容长度则为0。
  • lengthComputable:如果知道内容长度 lengthComputable 则为 true;否则为 false。
xhr.onprogress = function (e) {
  if (e.lengthComputable) {
    progress.innerHTML = Math.round(100 * e.loaded / e.total) + '% Complete';
  }
}

4. onload

当请求成功完成时触发。

5. ontimeout

当timeout属性指定的时间已经过去但响应依旧没有完成时触发。

6. onabort

当一个请求中止时触发(IE尚不支持)。

7. onerror

当请求因错误失败时触发,注意,如404等HTTP状态码不会造成错误,因为响应仍然成功地完成了。不过,解析URL时发生DNS错误或一个无限循环的重定向都将引发这个事件。

8. onloadend

在load, abort, error或timeout事件之后当前请求成功或失败时触发。

四、XMLHttpRequestUpload 对象

XMLHtpRequestUpload对象定义一系列事件处理程序注册属性,用于监视HTTP请求主体上传的进度。在实现XMLHtpRequest Level 2标准的浏览器中,每个XMLHtpRequest对象都有一个指代这类对象的upload属性。要监视请求上传的进度,只须简单地将这个属性设置为一个适当的事件处理函数或调用EventTarget方法。

事件处理程序

1. onabort
上传中止时触发。

2. onload
当上传成功针触发。

3. onloadstart
当上传开始时触发。
4. onprogress
上传过程中重复触发(约每50ms一次)
5. ontimeout
当上传因为XMLHtpRequest超时而中止时触发。

6. onerror
当上传因为网络错误失败时触发。
7. onloadend
当上传结束时触发,无论上传上否成功. loadend事件总是跟在一个load. abort.
errorimeout事件之后.

五、常用请求头

1. Accept

告诉服务器,客户端支持的资源类型。客户端支持的资源类型服务器可能没有,所以 accept 可以设置多种类型用","号隔开,并且设置优先级,服务器根据优先级寻找相应的资源返回给客户端。

Accept: text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8

q 代表优先级,取值范围是 [0-1],如果没有 q,默认 q=1,也就是最高优先级。

2. Accept-Encoding

客户端支持的数据压缩格式

3. Accept-Language

客户端的语言环境

Accept-Language: zh-CN,en-US;q=0.8,zh-TW;q=0.6

表示大陆简体中文优先,其次英语,再其次台湾繁体中文

4. Host

想访问的主机名

5. Origin

请求的来源域名和端口号 (跨域请求时,浏览器会自动带上这个头信息)

Origin: http://www.example-social-network.com

6. User-Agent

浏览器信息

7. Connection

告诉服务器,请求完后是关闭还是保持链接。

8. Cache-Control

缓存控制器。(主要是在响应头设置)

9. Content-Length

设置请求体的字节长度。

GET 请求因为没有 Body,所以不需要这个头。

Content-Length: 348

10. Content-Type

请求体的 MIME 类型(适用POST和PUT请求)。

可选值有:

  • 文本字符串:text/plain;charset=UTF-8;
  • 文本html:text/html;charset=UTF-8;
  • name=value形式:application/x-www-form-urlencoded
  • FormData数据格式:multipart/form-data; boundary=something
  • JSON字符串:application/json;charset=UTF-8

boundary:对于多部分实体,boundary 是必需的,它用于封装消息的多个部分的边界。

11. Cookie

设置服务器使用Set-Cookie发送的http cookie。

12. If-Match

If-Match 的值一般是响应头里的 ETag 的值。客户端先 GET 这个资源得到 ETag 中的版本号,然后发起一个资源修改请求 PUT|PATCH 时通过 If-Match 头来指定资源的版本号,如果服务器资源满足 If-Match 中指定的版本号,请求就会被执行。如果不满足,说明资源被并发修改了,就需要返回状态码为 412 Precondition failed 的错误。客户端可以选择放弃或者重试整个过程。

13. If-Modified-Since

具体流程如下:

  1. 客户端第一次向服务器发起请求,服务器将最后的修改日期(Last-Modified)附加到所提供的资源上去

  2. 当再一次请求资源时,如果没有命中强缓存,在执行再验证时,会包含一个If-Modifed-Since首部,值为资源的 Last-Modified 日期,询问服务器该资源自从这个 Last-Modified 日期之后有没有被修改过。

  3. 如果内容被修改了,服务器回送新的资源,返回200状态码和最新的修改日期

  4. 如果内容没有被修改,会返回一个304 Not Modified响应

14. If-None-Match

跟响应头里的 Etag 字段配合使用,流程和 If-Modified-Since 一致,只是 Last-Modified 字段和它所表示的更新时间改变成了 Etag 字段和它所表示的文件 hash,把 If-Modified-Since 变成了 If-None-Match。服务器对比已缓存标签与服务器文档中的标签是否有所不同,相同返回 304, 不相同返回新资源和 200。

15. If-Range

在断点续传时,为确保连续 2 个请求之间服务器资源本身没有发生变化,需要 If-Range 头带上 ETag 的资源版本号。服务器资源根据这个版本号来判定资源是否改变了。如果没变,就返回 206 Partial Content 将部分资源返回。如果资源变了,那就相当于一个普通的 GET 请求,返回 200 OK 和整个资源内容。

16. Range

支持断点续传的服务器必须接收的 Range 头,它表示客户端请求资源哪个字节范围的那一部分。

Range: bytes=500-999

17. Referer

Referer 表示当前请求页面的来源页面的地址。服务端一般使用 Referer 请求头识别访问来源,可能会以此进行统计分析、日志记录以及缓存优化等。也可用于资源防盗链。(这是MDN给的解释,但实际测试结果请求头的 Referer 好像就是当前页面的URL,document.referrer 表现跟MDN的解释一致)

六、常用响应头

1. Access-Control-Allow-Origin

指定哪些站点可以参与跨站资源共享。

Access-Control-Allow-Origin: *

2. Status

响应状态码。

3. Date

服务端发送资源时的服务器时间。

4. Content-Type

响应资源的 MIME 类型。帮助浏览器去处理接收到的数据。

5. Content-Length

响应体的字节长度。

6. Content-Range

断点续传时,标识响应体内容属于完整消息体中的哪一部分。对应请求的 Range 头。比如下面的例子表示该资源总共有 47022 字节,当前响应的内容是 21010-47021 字节之间的内容。

Content-Range: bytes 21010-47021/47022

之所以是 47021 而不是 47022 是因为 offset 是以 0 开始的,47021 就是最后一个字节。

7. Content-Encoding

告诉客户端,应该采用哪种方式对资源进行解码

8. Age

表示资源缓存的年龄,也就是资源自缓存以来到现在已经过去了多少时间,单位是秒。

Age: 86400

9. Cache-Control

缓存控制器。

Cache-Control: max-age=<seconds>
Cache-Control: no-cache 
Cache-Control: no-store

Cache-Control: private

Cache-Control: must-revalidate

10. Expires

用来告知客户端资源何时失效。如果它的值等于 Date 头的值,就表示资源已经失效。

11. ETag

资源标签(一般都是 hash 生成的),每个资源可以提供多个标签信息,存储在服务器。它一般用来和请求头的 If-Match 和 If-None-Match 配合使用,用来判断缓存资源的有效性。

12. Last-Modified

标记资源的最后一次修改时间,它和 Date 比较类似,区别是 Last-Modified 代表修改时间,而 Date 是创建时间。和 If-Modified-Since 配合使用。

13. Set-Cookie

设置浏览器Cookie。

Set-Cookie: UserID=JohnDoe; Max-Age=3600; Version=1

七、GET和POST请求的区别

get 和 post 本身对参数大小没有限制,这个限制是浏览器和服务器设置的。

Get请求:

  • 一般大小限制 2K(根据浏览器的不同而不同)
  • 请求可被缓存
  • 请求会保留在浏览器历史记录中
  • 请求不应在处理敏感数据时使用

Post请求:

  • 服务器有时候会对 post 请求参数大小做限制,超过限制的请求参数会被截断。(服务器 tomcat server.xml 的 maxPostSize 决定了 post 请求的大小,默认2M)
  • 请求不会被缓存
  • 请求不会保留在浏览器历史记录中

八、FormData 格式

一次HTTP multipart/form-data 请求体的内容。

FormData 是 XHR2 的一个特性,在请求参数包含二进制内容时非常方便。

使用构造函数创建一个 FormData 对象,然后使用 append() 方法来为它添加名/值对(值可以是字符串、File或Blob对象)。在添加完所有的请求内容后,就可以将该 FormData 传入一个 XHR 的 send() 方法。

1. FormData 构造函数

FormData 构造函数允许接收一个“表单”作为参数,也可以不传参数。

var form = document.getElementById('form1');
var formdata = new FormData(form);

2. FormData API

1. FormData.append()

FormData.append(name, value [, filename]);

append() 方法可以向 FormData 对象中添加键值对,如果添加的属性已存在,append() 会把新值添加到已有值集合的后面。

  • value可以是字符串、File或Blob对象。
  • filename:传给服务器的文件名称, 当一个 Blob 或 File 被作为第二个参数的时候,Blob 对象的默认文件名是 "blob"。 File 对象的默认文件名是该文件的名称。

传递数组的单个值:

files.forEach(file => {
  formData.append('files[]', file);
});

2. FormData.set()

给 FormData 设置属性值,如果FormData 对应的属性值存在则覆盖原值,否则新增一项属性值。

3. FormData.delete()

删除一个键值对。

4. FormData.entries()
返回一个包含所有键值对的iterator对象。
5. FormData.get()
返回在 FormData 对象中与给定键关联的第一个值。
6. FormData.getAll()
返回一个包含 FormData 对象中与给定键关联的所有值的数组。
7. FormData.has()
返回一个布尔值表明 FormData 对象是否包含某些键。
8. FormData.keys()
返回一个包含所有键的iterator对象。
9. FormData.values()
返回一个包含所有值的iterator对象。

3. 用 for...of 遍历 FormData

let formData = new FormData();
formData.append('name', 'Jim');
formData.append('name', 'Lily');
formData.append('age', '25');
for(let key of formData) {
  console.log(key)
}

//  ["name", "Jim"]
//  ["name", "Lily"]
//  ["age", "25"]

4. 使用 POST 方法发送 multiple/form-data 请求主体

function postFormData(url, data, cb) {
  let xhr = new XMLHttpRequest();
  xhr.open('POST', url);
  xhr.onreadystatechange = function() {
    if (xhr.readyState == 4 && cb) {
      cb(xhr);
    }
  }
  let formData = new FormData();
  for(let name in data) {
    if (!data.hasOwnProperty(name)) continue;
    formData.append(name, data[name]);
  }
  xhr.send(formData);
}

5. formData 的值如果是数组,在Network看到的请求参数可能会被拆分成这种形式:

companyId[]: 9428
companyId[]: 102120

九、上传进度事件

XHR2 对象将有 upload 属性,upload 属性值是一个对象,它定义了整个 progress 事件集合,比如 onprogress和onload。你可以像使用常见的 progress 事件处理程序一样使用 upload 事件处理程序。

以下例子演示了如何使用 upload progress 事件把上传进度反馈给用户。这个示例也演示了如何从拖放API中获得FIle对象和如何使用FormData API在单个 XMLHttpRequest 请求中上传多个文件。

<div class="fileDropTarget" data-uploadto="upload.php"></div>
function dropToUpload() {
  // 含有“fileDropTarget”类的元素是拖放目标容器
  const elts = document.querySelectorAll('.fileDropTarget');
  for (let i = 0; i < elts.length; i++) {
    const target = elts[i],
      url = target.getAttribute('data-uploadto');
    if (!url) continue;
    creatFileUploadDropTarget(target, url);
  }
}
function creatFileUploadDropTarget(target, url) {
  // 跟踪当前是否正在上传,因此我们能拒绝放下
  let uploading = false;

  target.ondragenter = function(e) {
    if (uploading) return; // 如果正在忙,忽略拖放
    const types = e.dataTransfer.types;
    if (types && (types.indexOf('Files') != -1)) {
      target.classList.add('wantdrop');
    }
  }
  target.ondragover = function(e) {
    // 用return false阻止默认行为才能使drop事件触发
    if (!uploading) return false;
  }
  target.ondragleave = function(e) {
    if (!uploading) {
      target.classList.remove('wantdrop');
    }
  }
  target.ondrop = function(e) {
    e.preventDefault();
    if (uploading) return;
    target.classList.remove('wantdrop');
    const files = e.dataTransfer.files;
    if (files && files.length) {
      uploading = true;
      let filesLis = files.map(file => `<li>${file.name}</li>`);
      let msg = `Uploading files: <ul>${filesLis.join('')}</ul>`;
      target.innerHTML = msg;
      target.classList.add('uploading');

      let xhr = new XMLHttpRequest();
      xhr.open('POST', url);
      let body = new FormData();
      for (let i = 0; i < files.length; i++) {
        body.append(i, files[i]);
      }
      xhr.upload.onprogress = function(e) {
        if (e.lengthComputable) {
          target.innerHTML = msg + (Math.round(e.loaded / e.total * 100) + '% Complete');
        }
      }
      xhr.upload.onload = function(e) {
        uploading = false;
        target.classList.remove('uploading');
        target.innerHTML = 'Drop files to upload';
      }
      xhr.send(body);
    }
  }
}

十、使用CORS跨域的安全细节

如果给 xhr.open() 方法传入用户名和密码,那么它们绝对不会通过跨域请求发送(这使分布式密码破解攻击成为可能)。除外,跨域请求通常也不会包含其他任何的用户证书:cookie和HTTP身份验证令牌(token)通常不会作为请求的内容部分发送且任何作为跨域响应来接收的cookie都会丢弃。如果跨域请求需要这几种凭证才能成功,那么必须在用send()发送请求前设置 XMLHttpRequest 的 withCredentials 属性为 true。

示例:使用HEAD和CORS请求链接详细信息

function linkDetails() {
  // 是否支持跨域请求
  const supportsCORS = (new XMLHttpRequest()).withCredentials !== undefined;

  const links = document.querySelectorAll('a');
  for (let i = 0; i < links.length; i++) {
    const link = links[i];
    if (!link.href) continue;
    if (link.title) continue;
    // 如果是跨域链接
    if (link.protocol !== location.protocol || link.host !== location.host) {
      link.title = '站外链接';
      if (!supportsCORS) continue; // 如果不支持CORS就退出
    }
    link.addEventListener('mouseover', mouseoverHandle, false);
  }

  function mouseoverHandle(e) {
    let link = e.target;
    let url = link.href;
    let xhr = new XMLHttpRequest();
    xhr.open('HEAD', url);
    xhr.onreadystatechange = function() {
      if (xhr.readyState != 4) return;
      if (xhr.status == 200) {
        const type = xhr.getResponseHeader('Content-Type');
        const size = xhr.getResponseHeader('Content-Length');
        const date = xhr.getResponseHeader('Last-Modified');
        link.title = `类型:${type},大小:${size},时间:${date}`;
      } else {
        if (!link.title) {
          link.title = '链接详情请求失败!';
        }
      }
    }
    xhr.send();
  }

  // 移除处理程序:仅想请求一次
  link.removeEventListener('mouseover', mouseoverHandle, false);
}

六、Get 和 Post 分别进行了几次数据交互?

get请求过程:(2次交互)

  • 浏览器请求 tcp 连接(第一次握手)
  • 服务器答应进行 tcp 连接(第二次握手)
  • 浏览器确认,并发送 get 请求头和数据(第三次握手,这个报文比较小,所以 http 会在此时进行第一次数据发送)
  • 服务器返回200 ok 响应

post 请求过程:(3次交互)

  • 浏览器请求 tcp 连接(第一次握手)
  • 服务器答应进行 tcp 连接(第二次握手)
  • 浏览器确认,并发送 post 请求头(第三次握手,这个报文比较小,所以 http 会在此时进行第一次数据发送)
  • 服务器返回100 continue 响应
  • 浏览器开始发送数据
  • 服务器返回200 ok 响应

七、Form表单提交

1. <form>表单默认使用 get 请求,请求参数以“?key1=val1&key2=val2”的形式跟在接口后面。

1. enctype

1. <form>表单中 enctype 属性规定在发送到服务器之前应该如何对表单数据进行编码,只有 method="post" 时才有用。

2. <form>表单同时包含文件上传元素和其他元素时,Chrome控制台看不到请求体 form-data(但在 Edge 上可以看到),Response 中只有响应文本,原因可能是用 form 表单提交是属于 document 类型,出于安全策略 Chrome 浏览器把请求体隐藏掉了。

enctype可选值:

  • application/x-www-form-urlencoded:(默认值)将表单数据转换成“name=value”形式,中间用“&”连接,将空格转换为“+”符号,特殊字符转换为 ASCII HEX 值,还需要对于内容中非数字、字母部分用 encodeURIComponent() 转译。jQuery中的 ajax请求,“Content-Type”默认值也是这个。
  • multipart/form-data:不对字符编码。在使用文件上传时,必须使用该值。
  • text/plain:空格转换为“+”加号,但不对特殊字符编码。

2. Form表单提交示例

<form action="/upload" method="post" enctype="multipart/form-data" target="multipartFrame">
  <input type="text" name="person" value="Jim"/>
  <input type="file" name="file">
  <input type="submit" value="保存">
</form>
<iframe style="display: none;" name="multipartFrame"></iframe>

八、HTTP请求示例

1. 使用XML文档作为其主体的HTTP POST请求

例如要传递的XML如下:

<query>
  <find zipcode="01234" radius="1km">pizza</find>
</query>

ajax 请求如下:

function postQuery(url, what, where, radius, cb) {
  var xhr = new XMLHttpRequest();
  xhr.open('POST', url);
  xhr.onreadystatechange = function() {
    if (xhr.readyState == 4 && cb) {
      cb(xhr);
    }
  }
  let doc = document.implementation.createDocument('', 'query', null);
  let query = doc.documentElement;
  let find = doc.createElement('find');
  query.appendChild(find);
  find.setAttribute('zipcode', where);
  find.setAttribute('radius', radius);
  find.appendChild(doc.createTextNode(what));
  xhr.send(doc);
}

2. 使用HTTP POST请求上传文件

<input class="file-input" type="file" data-url="upload.php">
function upload(success) {
  const inputs = document.querySelectorAll('.file-input');
  for (let i = 0; i < inputs.length; i++) {
    const input = inputs[i],
      url = input.getAttribute('data-url');
    if (!url) continue;

    input.addEventListener('change', function(e) {
      const file = this.files[0];
      if (!file) return;
      let xhr = new XMLHttpRequest();
      xhr.open('POST', url);
      xhr.onreadystatechange = function() {
        if (xhr.readyState == 4 && xhr.status == 200) {
          success && success(xhr.responseText);
        }
      }
      xhr.send(file);
    });
  }
}

八、其他

1. 一个post请求,可以同时用2种传参方式:一部分参数可以跟get请求一样跟在url后面,另一部分跟正常的post传参一样。

2. get请求的时候最好把请求参数的值用 encodeURIComponent() 编码一下,,防止 & 这种特殊符号拼接到请求后面以后影响参数格式。

3. post 请求的3种 content-type:

- `application/x-www-form-urlencoded`: post请求默认的请求参数类型

- `application/json`: json格式

- `multipart/form-data`: 传输二进制数据

十、参考文章

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值