web系列之Ajax

写在前面的话

本文就是对ajax方面的知识作一个总结,没有什么深刻的地方。虽然总结的文章有不少,可是看本身写的和看别人的文章感受终究仍是相去甚远的。因此若是读者以为内容重复请直接右上角。javascript


xhr & fetch

用法:html

let xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
	if(xhr.readyState === 4) {
		if(xhr.status >= 200 & xhr.status < 300 || xhr.status === 304) {
		}
	}
};
xhr.open('GET', 'http://localhost:8080', true);
// 若是是POST请求,则send的参数是具体的数据
xhr.send(null);
复制代码

xhr上的事件:(省略前缀on)

  • readystatechange:每当xhr.readyState改变时触发。
  • timeout:当请求发出超过xhr.timeout设置的时间后,依然没有收到响应,则会触发。若是在超时终止请求以后再调用访问status等属性就会致使错误,因此最好在onreadystatechange事件中使用try-catch
  • loadstart:收到响应1byte后触发。
  • progress:其event.tartget === xhrevent.lengthComputable表示进度信息是否可用,event.total:Content-Lengt的预期字节数。event.loaded:已接收的字节数。(须要服务器返回Content-Length头部,不然lengthComputable一直为false)。
  • error:请求出错触发。
  • abortxhr.abort(); 终止链接时触发。
  • load:接受到完整数据时触发,至关于readyState === 4时
  • loadend:通讯完成,不论是error、abort或者load,都会致使此事件的触发(没有浏览器实现)。

ps: 为确保兼容性、正常执行,onreadystatechange、progress最好在open以前绑定。前端

xhr上的属性:

  • responseText:做为响应主体被返回的文本
  • responseXML:若是返回类型是"text/xml" || "application/xml" 则这个属性保存这XML DOM文档。不然为null
  • statushttp状态码
  • statusText:状态码的说明
  • readyState:取值以下:
    • 0:未初始化,未调用open();
    • 1:已初始化,调用了open();
    • 2:发送。send();
    • 3:接收。已接收到部分响应。
    • 4:完成,所有over~ 须要注意的是,每当readyState变化的时候都会触发onreadystatechange事件,并且这个事件最好在open以前就绑定(为了兼容性)。
  • timeout:超时时间(ms)。

xhr上的方法:

  • abort:用于取消异步请求。
  • setRequestHeader(key, value)open()send()前调用。
  • getResponseHeader/getAllResponseHeaders:看名字,不解释了。
  • overrideMinmeType:重写xhr响应的MIME(最好在send以前调用,这样能够确保绝对有效)。

误区:

并非全部的事件都是异步的, xhr.onreadystatechange 和xhr.onloadstart就是同步事件。java

const xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => console.log('ready state change');
xhr.onloadstart = () => console.log('load start');
xhr.open(method, url);
xhr.send();
console.log('sync');
// 因此结果为 ready state change => load start => sync
复制代码

fetch

用法:web

/** * 此处的request、response见下文 */
fetch(request)
    .then(response => {
    	response.json()
    	    .then(data => console.log(data));
    })
    .catch(err => console.log(err));
复制代码

Request

能够经过new Request();建立request对象(固然也能够直接写)ajax

let request = new Request('http://localhost:8080', {
    // headers见下文
	headers,
	method: 'GET',
	mode: 'cors'
});
复制代码

request上的方法:json

  • method: 支持GET,POST,PUT,DELETE,HEAD
  • url:请求的 URL
  • headers: 对应的Headers对象
  • referrer: 请求的 referrer 信息
  • mode: 能够设置cors,no-cors,same-origin
  • credentials: 设置 cookies 是否随请求一块儿发送。能够设置:omit,same-origin
  • redirectfollow,error,manual
  • integritysubresource 完整性值(integrity value)
  • cache: 设置 cache 模式 (default,reload,no-cache)

headers

能够经过new Header(); 来建立请求头:跨域

let headers = new Headers({'Content-Type': 'text/plain'});
headers.append('accept', 'text/*');
复制代码

定义在Headers之上的一些方法以下:promise

Response

fetch().then(response);中的response就是一个Response对象 能够经过new Request();建立request对象浏览器

  • clone(): 建立一个新的 Response 克隆对象.
  • error(): 返回一个新的,与网络错误相关的 Response 对象.
  • redirect(): 重定向,使用新的 URL 建立新的 response 对象..
  • arrayBuffer(): Returns a promise that resolves with an ArrayBuffer.
  • blob(): 返回一个 promise, resolves 是一个 Blob.
  • formData(): 返回一个 promise, resolves 是一个 FormData 对象.
  • json(): 返回一个 promise, resolves 是一个 JSON 对象.
  • text(): 返回一个 promise, resolves 是一个 USVString (text).

跨域

同源策略

什么是同源?

同源就是拥有相同的协议(protocol) && 主机(hostname) && 端口(port),那么这两个页面称为同源。一切非同源的请求均为跨域。并跨域没法随意请求,只是说为了网站的安全性,浏览器才采起同源策略。

若是是协议和端口形成的跨域问题,前端是无能为力的。 跨域问题中的域,浏览器只是用url首部来区分的,并不会对DNS以后获得的IP进行判断。

ps:url首部 = protocol + host;

严格的说,浏览器并非拒绝全部的跨域请求,实际上拒绝的是跨域的读操做。浏览器的同源限制策略是这样执行的:

  • 一般浏览器容许进行跨域写操做(Cross-origin writes),如连接,重定向;
  • 一般浏览器容许跨域资源嵌入(Cross-origin embedding),如 img、script 标签;
  • 一般浏览器不容许跨域读操做(Cross-origin reads)。

同源策略呢,限制了如下行为:

  • Cookie、LocalStorage、IndexDB
  • 浏览器中不一样域的框架之间是不能进行js的交互操做
  • ajax请求发不出去(其实能够发出去,只不过浏览器将响应给拦截了)

跨域方式:JSONP、CORS、postMessage等

1、JSONP

JSONP,JSON with Padding 参数式JSONJSONP的原理其实就是利用了<script>标签的src引入外部脚本时不受同源策略的限制,经过手动添加DOM并赋予src请求的url,在请求的url中填写接收数据的回调,再加上服务器对callback的支持便可。

2、CORS

Cross-Origin Resource Sharing, CORS 跨域资源共享CORS是一种web 浏览器的技术规范,它为web 服务器定义了一种容许从不一样域访问其资源的方式。而这种跨域访问是被同源策略所禁止的。CORS系统定义了一种浏览器和服务器交互的方式来肯定是否容许跨域请求, 有更大的灵活性,比起简单地容许这些操做来讲更加安全。 CORS须要浏览器和服务器共同配合、支持。整个CORS通讯过程都是由浏览器来完成的,除了一些限制之外,代码和普通的ajax没有什么不一样,实现CORS的关键是服务器,只要服务器支持、实现了CORS接口就能实现CORS

简单请求(simple request)和非简单请求(not-so-simple request)

知足如下两大条件的请求就是simple request

    1. Request method是如下三种方法之一的:
    • HEAD
    • GET
    • POST
    1. Http头部信息只能(没有Access-Control-Allow-Origin的前提下)是如下几种:
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type: oneOf['application/x-www-form-urlencoded', 'multipart/form-data', 'text/plain']
    • Last-Event-ID 浏览器对简单请求和非简单请求的处理是不同的。

①简单请求

对于简单请求(如下都是跨域状况)浏览器直接发出CORS, 增长一个Origin头部。

这个Origin字段做用是告诉服务器,本次跨域请求是源自哪一个主机、端口、协议,服务器以此来判断是否容许这次跨域。

若是Origin不在服务器的许可范围内,那服务器就返回正常的HTTP响应。浏览器发现响应的Access-Control-Allow-Origin和发起请求的源不相等,或者根本没有这个字段,则浏览器拒绝这次请求。会被xhronerror事件捕获,这种错误没法经过状态码识别。

不然服务器返回的响应会多出(所谓多出,其实就是浏览器设置了这些头部)等头部信息。

能够看到多出了'Access-Control-Allow-Credentials'、'Access-Control-Allow-Headers'等头部,这些头部具体意义见下文。

Access-Control-Allow-Origin

服务器必须设置的值,不然不能实现CORS,它的值要么是精确的请求的Origin,要么是通配符*(在须要Cookie的时候不支持*)。

Access-Control-Allow-Credentials

可选。意为是否容许发送cookie,默认为不容许,不过这个字段只能设置为true。若是浏览器不容许发送cookie,删除该字段便可。 注意:浏览器在请求的时候也必须设置:xhr.withCredentials = true;不过有的浏览器省略还会自动带上cookie,能够手动关闭。

Access-Control-Allow-Headers

可选。在CORS中,用于设置浏览器能够发送的头部。

res.setHeader('Access-Control-Allow-Headers', 'Your-Fucking-Header');
复制代码
Access-Control-Expose-Headers

可选。CORS返回请求的时候,xhr.getAllResponseHeaders();只能拿到6个基本头部字段:'Cache-Control'、'Content-Language'、'Content-Type'、'Expires'、'Last-Modified'、'Pragma'。经过res.setHeader('Access-Control-Expose-Headers', 'some headers');能够得到容许的header

②非简单请求

非简单请求指的是那种对服务器有特殊要求的请求,如:request methodput、delete,或者Content-Typeapplication/json等。 非简单请求的CORS请求会在正式通讯以前进行一次HTTP查询请求,称之为 预检请求(preflight)。浏览器先询问服务器,当发送方的域名在服务器容许之列,而且发送方使用的头部、请求方法都是服务器容许的时候才会发送正式的Ajax请求,不然报错。

非简单请求除了Origin之外,还会发送两个特殊的头部:'Access-Control-Request-Method','Access-Control-Request-Headers'

Access-Control-Request-Method

浏览器这次CORS会用到的HTTP方法。

Access-Control-Request-Headers

指出浏览器会发送的额外的头部

浏览器根据服务器返回的 'Access-Control-Allow-Origin''Access-Control-Allow-Headers'来判断服务器是否容许 CORS,除此以外还有如下头部:

Access-Control-Allow-Methods

必需。值由','分割的String,意为支持的CORS请求方法,返回的是全部支持的方法,不是浏览器设置的那个方法,避免屡次preflight

Access-Control-Max-Age

可选。单位: s(秒)。意为本次preflight的有效时间。在有效时间内不用再次发送预检请求,即容许缓存该回应。

CORS用到的HTTP头部

Headers Server Browser
Access-Control-Allow-Orgin ×
Access-Control-Allow-Headers ×
Access-Control-Allow-Methods ×
Access-Control-Max-Age ×
Access-Control-Allow-Credentials
Access-Control-Expose-Headers ×
Access-Control-Request-Method ×
Access-Control-Request-Headers ×

CORS与JSONP的比较

-- 目的 支持方法 优点 不足
CORS 跨域 全部HTTP请求方法 请求方法不只仅局限于GET,支持全部HTTP请求方法。安全性高。 老版本浏览器不支持CORS,有必定兼容性问题,好比IE10及更早版本、Safari4及更早版本、FireFox3.5及更早版本都不支持。
JSONP 跨域 GET 能够向老式、不支持CORS的网站请求数据。并且设置简单,无需设置过多的响应、请求头部。 ①只能支持GET方法
②对于存在恶意行为的服务器存在必定的安全隐患。
③须要一个接收数据的全局函数,污染了全局做用域。
④判断请求是否失败不容易(H5给script新增error事件,可是等浏览器实现还需以时日)。

其它的一些跨域方法(感受没diao用)

①document.domain

若是两个网页的主域名相同,这个时候能够令document.domain都为其主域名(document.domain只能将其设置为自身和更高一级的父域名)。 因为同源限制的第二条,不一样域的iframe之间不能进行js交互。因此经过iframe.contentWindow获取到的window对象,它的方法和属性几乎都是不可用的,而且不容许获取此window.document
这个时候:

document.domain = /* 两个页面共同的父级域名 */复制代码

而后就能够获得iframe.contentWindow的属性了。也能够经过iframe里面的方法请求数据,以此也能够达到跨域的目的。

②location.hash

它的原理是父窗口能够对iframeURL进行读写,而和祖先窗口(不只仅是父窗口)同源iframe也能够读写父窗口的URL,而hash部分不会发送到服务器(不会产生http请求),因此能够经过修改hash来实现双向的通讯。 具体操做是:
super窗口中有一个跨域的iframe0
iframe0中又有一个和super同源的iframe1
如图所示,颜色表示是否同源。

    1. iframe0想要发送数据的时候,能够直接修改iframe1hash(跨域也能够)
    1. iframe1监听onhashchange事件,拿到hash部分后,再修改superhash(由于iframe1super同源,因此能够)
    1. super也监听onhashchange事件,就能够拿到数据了。 代码以下:
super:
<iframe id = "iframe" src="http://localhost:8080/iframe0.html"></iframe>
<script type="text/javascript"> let counter = 0; let url = "http://localhost:8080/iframe0.html#"; const iframe = document.getElementById('iframe'); window.onhashchange = function(event) { console.log('_我获得数据:', event.newURL.split('#')[1]); } </script>

iframe0:
<iframe src="http://localhost/iframe1.html" frameborder="0"></iframe>
<script> let counter = 0; let url = 'http://localhost/iframe1.html#'; const iframe = document.querySelector('iframe'); setInterval(() => { console.log('我发送数据:', + counter); iframe.src = url + counter ++; }, 2000); </script>

iframe1:
<script> window.onhashchange = function() { let data = event.newURL.split('#')[1]; // 修改super的hash window.parent.parent.location.hash = data; } </script>
复制代码

结果:

④postMessage

要使用postMessage这个API必需要有其余窗口的引用otherWindow 发送方:

otherWindow.postMessage(data, targetOrigin, [transfer]);
复制代码

参数说明:

  • data:发送的数据
  • targetOrigin:指定哪些窗口接收消息,*表示任何窗口, '/'表示当前域下的窗口。
  • transfer:可选,和message同时传递的对象,这些对象的全部权被转移给消息的接收方,而发送方再也不拥有全部权。

接收方:

window.addEventListener('message', e => {
	console.log(e);
}, false);
复制代码

e中有4个属性比较重要:

  • data:发送来的消息对象
  • type:发送消息的类型
  • source:发送消息的window
  • origin:发送消息的origin 直接经过给e.source添加引用类型的属性,能够直接给发送端的window添加数据。

总结

其实比较经常使用的跨域方法就是CORS、JSONP,其余的有个大概了解知道就行了。其余的关于XSS、CSRF等内容回头待续。

参考

正确面对跨域,别慌
fetch简介: 新一代Ajax API
ajax跨域,这应该是最全的解决方案了

相关文章
相关标签/搜索