在 web 开发中,如果要实现下载功能,往往都是使用新开 web 页面或者是使用 iframe 的形式。实现起来其实很简单:
- <a target="_blank" href="download.zip">
- 点击下载
- </a>
- 或者
- <iframe style="display:none" src="download.zip">
- </iframe>
用户点击 a 标签弹出一个新页签后,或者是打开了 iframe 后,浏览器就会接受一个下载响应,并下载附件。其实所谓附件下载,就是在浏览器读到响应报文的头之后,浏览器生成一个下载提示框,在用户确定后会继续下载文件。文件其实就是个流,所谓流就是一个传输的过程,浏览器会自动管理这个传输过程,会自动生成进度条、停止下载按钮、继续继续按钮、取消下载按、显示更新下载的字节数钮等。这些都是浏览器自动为我们做的,整个过程不受我们控制。
浏览器对下载的支持基本上已经能满足我们的需求,一般场景下再探索其他下载方式意义不大。但是还是有些场景是浏览器下载不能满足的,比如要求我们的 web 应用对下载进度的进行监控,或者下载完成后触发特定事件,或者 web 应用可以自动取消下载过程,或者使用 worker 创建一个后台运行的下载等等。对于以上情况我们都可以采用基于 Blod 对象的 ajax 下载。
ajax 下载附件和 ajax 上传附件一样,需要浏览器支持 ajax2.0。其实所谓下载和和普通的 ajax 请求没什么区别,都是对一个 url 地址做请求,但是下载一般都是二进制文件,不是文本对象或者 json 对象,需要 JavaScript 提供一个对够封装这个二进制文件的类型,这就是 blod。因此要设置响应的类型 responseType 的值为 "blod":
- var xhr =new XMLHttpRequest();
- xhr.open(option.type ? option.type.toUpperCase() : 'GET', url, true);
- xhr.responseType = 'blob';
要求 XMLHttpRequest 对象的 responseType 字段值是。那么 blod 对象又是什么?
- MDN对其描述为:
- Blob对象是包含有只读原始数据的类文件对象。Blob对象中的数据并不一定得是JavaScript 中的原生形式。File接口基于 Blob,继承了Blob的功能,并且扩展支持用户计算机上的本地文件。通过Blob对象我们可以将一个二进制流封装为一个对象。
如果你了解过 html5 的 file 相关的 api,你对于 blod 对象应该不会是陌生。blod 能够把一个字节流封装为一个文件,将 XMLHttpRequest 对象的 responseType 值是 blob,我们可以把响应体当做是一个 blob 对象处理。
- xhr.onload = function() {
- //对于重定向的文件不予理会
- if (this.status >= 200 && this.status < 300) {
- var blob = new Blob([this.response], {
- type: this.response.type
- });
- }
- }
使用 ajax 下载文件,再将文件保存为 blob 对象,缓存在浏览器中。那么如何让用户将文件保存到硬盘上呢?
- //ie的下载
- if (window.navigator.msSaveOrOpenBlob) {
- navigator.msSaveBlob(blob, fileName);
- } else {
- //非ie的下载
- var link = document.createElement('a');
- link.href = window.URL.createObjectURL(blob);
- link.download = fileName;
- link.click();
- window.URL.revokeObjectURL(link.href);
- }
然后就是进度条和下载取消功能了,其实 XMLHttpRequest 对象是有个的,只是我们平时做 ajax 请求的时候都忽略他,毕竟一般请求都是瞬时的,不需要为其设置进度条。但是 ajax 下载却不一样,下载附件是需要时间的,因此为其开发个进度条还是很有必要的,监听 progress 事件,我们就可以获取下载进度。
使用 XMLHttpRequest 对象的 abort 函数可以取消下载,此外 load 事件可以监听到下载完成,error 事件可以监听到下载失败。总之,ajax 下载和一个普通的 ajax 请求的事件和方法是完全一样的。
ajax 下载和长连接一样,会比普通请求占用更多带宽,尤其下载对带宽占用更是严重。因此下载过程中可能会阻塞其他的 ajax 请求,所以建议 ajax 下载的资源和其他请求的资源使用不同的域名,但是这样又会带来新的问题——同源策略问题。
同源策略是浏览器安全的基石,如果没有同源策略随便一个网站都可以发出 csrf 攻击。如果不能保证下载的资源的 url 和当前页面的 url 同源,就会触发同源策略导致下载失败,因此需要做 ajax 跨域处理。相比 iframe 和新页签的下载方法 (事实上 iframe 也有同源策略,要求 iframe 里面的页面和父页面不能访问对方的内容,但是下载功能不涉及这种访问对方的内容操作,所以 iframe 下载是不受同源策略影响的),ajax 下载本质上还是 ajax,因此会受到浏览器同源策略的影响。所以如果下载非同源的附件,就需要附件所在的服务器支持 cors,如果服务器需要访问 cookie,还要将 XMLHttpRequest 对象的 withCredentials 设置为 true。
同时出于同源策略的原因,我们不能使用 ajax 的形式去下载第三方资源,因为通常的下载服务都不会做 cors 处理的,比竟 iframe 下载或者新页签下载的方式是不受同源策略影响的,所以无需做 cors 处理。这大大限制了 ajax 下载的适用度。
最后我们再总结一下 ajax 下载的使用场景:
1. 需求对下载进度的进行监控的场景,比如发现用户下载进度过慢,主动提供其他解决方案。
2. 需要下载完成后触发特定事件,例如弹出一个桌面提示 Notification。
3. 需要提供一个后台下载。例如我们可以在用户打开网页后将附件偷偷地下载下来再缓存起来,等到用户真的想下载附件时候直接保存在本地。我们甚至可以借助 worker 创建一个后台线程,从而还能保证下载过程不会影响页面正常渲染。
4. 需要下载后不保存在硬盘中,而是 webapp 直接处理附件。例如,就是采用的 ajax 下载。
最后奉上笔者的一个 ajax 下载的 demo:
来源: http://www.cnblogs.com/laden666666/p/6409868.html