ajax

ajax

1、简单地说,ajax就是一种能够在页面不刷新的情况下从外部获取数据的技术。

var xhr = new XMLHttpRequest();
xhr.open(‘get’,‘a.php’,true);
xhr.send(null);

以上代码中,我们向a.php发送了一个get请求,首先,我们创建了一个XMLHttpRequest对象的实例,
然后调用实例的open方法,open方法接受三个参数,
第一个参数表示请求方式,第二个参数表示请求的文件路径,
第三个参数如果是false,表示为执行同步请求,后续的代码将会在请求的数据返回来之后才被执行,如果是true,则表示执行异步请求,即后续的代码不会等待数据返回,而是立即执行。
最后,需要调用send方法发送请求,send方法接受一个参数,该参数值将会被添加到请求主体当中,如果不需要携带数据,则必须传入null。

2、XMLHttpRequest对象的属性

responseText 表示服务端返回的数据
responseXML 表示服务端返回的XML格式的数据,如果返回的资源不是此格式类型的,该属性值为null
status 代表通讯的状态码,状态码有数字表示,如200表示请求成功,此时可以访问服务端返回的数据了
statusText 代表通讯的状态的文本形式,如’ok’表示请求成功,等同于status的200状态码

status属性可能返回的状态码有很多,但常用的其实就那么几个,除了表示请求成功的200状态码之外,还有304状态码,表示请求的资源未被修改,浏览器会根据这个状态码从缓存中读取相应的资源。

请求的方式分为两种,一种是同步请求,这种方式会等待服务端返回响应后才会继续执行后续的代码,这样的方式显示会造成糟糕的用户体验,因此在现代浏览器中已经被废弃。
我们使用的通常是异步请求,就是后续代码的执行不需要等待服务端的响应。
在这种方式下,我们如何在服务端返回数据后再做点什么呢?事实上,整个通讯流程被分为多个阶段,每个阶段发生变化的时候都会调用xhr对象上的readystatechange事件,并更新xhr对象上readystate属性,这个属性上保存着一个数字,用数字来表示通讯流程中的各个阶段,如:

0 表示未调用open方法
1 表示已经调用open方法
2 表示已经调用send方法
3 表示接受到部分响应数据
4 表示接受完成,可以访问响应的数据了。

因此,我们可以通过readystatechange事件属性指定一个事件处理函数,在函数中通过readystate属性来判断数据是否接受完成了。

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {

	if (xhr.readyState === 4) {
		if (xhr.status === 200 || xhr.status === 304) {
			console.log(xhr.responseText)
		}else {
			console.log(xhr.status)
		}
	}
}

xhr.open('get','a.php',true);
xhr.send();

上述代码中,通过onreadystatechange事件,我们让readystate属性值等于4,也就是接受完全部响应数据之后,再通过查看status属性来查看服务端是否返回了我们想要的数据,再根据此条件来打印出数据,或是抛出错误。
需要注意的是,务必在open方法之前监听onreadystatechange事件,否则在不同的浏览器中可能会出现兼容性问题导致错误发生。

3、readyState和status的区别

有的人可能会不太清楚这两者的区别,首先,readyState可能返回0-4的数值,这5个数值分别对应的是一个请求从初始化,创建请求,发送请求,接受响应,接受完毕这个五个步骤。而status属性包含的是有关响应内容的具体信息。
当readyState属性值等于4的时候,表示可以访问响应内容了,但响应的内容却不一定是你想要的。
打个比方,比如你写了一封信,邮寄给远方的亲戚,你在信中表达了自身的困难处境,希望能得到一笔资金援助,几天之后,一个包裹邮寄回来了。这一系列的阶段,可以用一些数字来表示,1表示你写了封信,2表示你发送了信件,3表示你接受到了邮件,事实上,这个3代表的是你接受到包裹的那个阶段(对应readyState等于4),之后,当你兴奋地打开包裹的时候,却发现里面并没有人民币,只有一张白纸,写着:不借!所以,当包裹里装的是人民币的时候,我们可以让status值等于200,表示包裹里是你想要的东西,而不是其他的。

因此,如果你只使用readyState来判断的话,就会产生问题:

xhr.onreadystatechange = function () {

	if (xhr.readyState === 4) {
		var res = xhr.responseText;
		console.log(res.name)
	}
}

以上代码可能发生错误,因为虽然你收到了包裹,但其中装的并不一定是你想要的东西,也就是responseText可能是空的。

或者,只使用status来判断,同样也会产生问题

xhr.onreadystatechange = function () {

	if (xhr.status === 200 || xhr.status === 304) {
		var res = xhr.responseText;
		console.log(res.name)
	}
}

以上代码有两个误区,第一,当通讯阶段发生变化的时候,都会调用onreadystatechange事件,因此,函数会被执行多次,第二,在通讯流程处于0-3的阶段的时候,由于响应数据还没有接受或接受不完整,这是对其进行操作就会发生错误。

需要注意的是,在onreadystatechange事件中应避免使用this,因为在有的浏览器中会由于作用域问题导致错误发生。

4、abort方法

abort方法用于取消请求。请求取消后,http连接会断开,响应也不会返回,xhr会停止触发事件,并且不再允许访问有关xhr对象的属性。

5、对报文头部信息的操作

5.1 setRequestHeader方法 :
该方法用于设置请求报文的头部信息,方法接受两个参数,第一个参数为字段名称,第二个参数为字段值。想要设置头部字段信息,需要在调用open方法与send方法之间进行设置才有效,如下所示:

var xhr = new XMLHttpRequest();
xhr.open('get','a.php',true);

xhr.setRequestHeader("key","value");

xhr.send();

5.2 getResponseHeader方法
该方法用于获取响应头部信息中的信息,方法接受一个头部字段名称,并返回该字段名称的字段值。当readystate状态码小于4的时候,该值为空字符串。

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
	
	if (xhr.readstate === 4) {
		if (xhr.status === 200 || xhr.status === 304) {
			xhr.getResponseHeader('date'); //Fri, 11 Jan 2019 15:32:33 GMT
		}
	}
}

xhr.open('get','a.php',true);	
xhr.send();

5.3 getAllResponseHeaders方法
用于获取响应报文中的全部头部信息,当readyState值小于3时,该值返回null

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
	
	if (xhr.readstate === 4) {
		if (xhr.status === 200 || xhr.status === 304) {
			xhr.getAllResponseHeaders(); 
		}
	}
}

xhr.open('get','a.php',true);	
xhr.send();

结果:

date: Fri, 11 Jan 2019 15:32:33 GMT
server: Apache/2.4.23 (Win32) PHP/5.6.25
connection: Keep-Alive
x-powered-by: PHP/5.6.25
content-length: 2
keep-alive: timeout=5, max=98
content-type: text/html; charset=UTF-8

6、get请求

get请求通常用于向服务器获取数据,在请求地址的尾部,可以通过添加一个问号,后面跟上键值对的形式来表示参数,每个键值对之间需要使用&符号进行分割,如:
a.php?key1=value1&key2=value2
需要注意的是,查询参数中的所有key和value值都需要进行正确的编码,才能被正确处理,比如,value值中包含了问号或者&符号这些会产生歧义的字符,就会在处理的时候产生错误,因此,我们需要调用enCodeComponent方法来对每个键名与键值进行编码,来消除可能出现的带有歧义的符号。

var url = `a.php?${encodeURIComponent('key?1')}=${encodeURIComponent('value&1')}&${encodeURIComponent('key=2')}=${encodeURIComponent('value2')}`;

结果:a.php?key%3F1=value%261&key%3D2=value2

7、post请求
post请求通常用于需要向服务器发送大量数据与一些敏感的信息。因为post请求的主体可以包含很多的数据,并且这些数据并不会像get请求那样显示在url尾部。在send方法中,可以传入需要发送的数据,这些数据将会被添加到请求主体当中,发送给服务器。

xhr.open('post','a.php',true);
xhr.send('name=json&age=18');

post提交的常见数据格式

applaction/x-www-form-urlencoded
这是表单提交时使用的默认数据格式,服务端对这种格式的数据支持很好,其数据格式是类似查询参数那样的键值对形式,如:name=json&age=18

xhr.open('post','a.php',true);
xhr.send('name=json');

通过send方法,我们可以传入需要传递的数据,这些数据将被添加在请求主体当中,携带往服务器。
在此,你可能因为在php中使用超级全部变量获取name值如:$_POST[‘name’],因获取不到相应的值而感到疑惑,这其实是因为数据格式造成的,默认情况下,post请求的Content-Type字段值为text/plain,也就是纯文本格式,php在接受这类数据的时候并不会将它们添加到POST变量当中,将数据添加到POST变量当中的依据应该是格式为applaction/x-www-from-urlencoded格式的数据,表单提交时的默认格式就是这种类型,因此,我们需要修改Content-Type字段值:

xhr.open('post','a.php',true);
xhr.setRequestHeader('Content-Type','applaction/x-www-form-urlencoded');
xhr.send('name=json');

这样,在php中通过$_POST[‘name’]来获取name值就正常了。

multipart/form-data
这种数据格式不仅支持纯文本数据格式,而且还支持向服务器发送二进制数据,因此,通常我们用这种格式来完成上传文件的功能。

<form action="a.php" method="post" enctype="multipart/form-data">
	<input type="file"/>
	<input type="submit"/>
</form>

使用form元素进行提交文件时,需要指定enctype属性值为multipart/form-data
如果需要使用ajax进行异步文件上传的话,就需要使用FormData对象。
XMLHttpRequest2级添加了FormData对象,FormData的作用在于将数据编译成键值对,然后,我们可以通过xhr的send的方法将这个对象的实例发送出去。
FormData拥有append方法,该方法接受两个参数,第一个为键名,第二个为键值,用于往对象中添加键值对形式的数据。

<input type="file" name="file" id="file" /> 
<button id="btn">上传</button>

var btn = document.getElementById('btn');

btn.addEventListener('click',() => {
	
var xhr = new XMLHttpRequest();
xhr.open('post','a.php',true);
var formData = new FormData();
formData.append('name','json'); 
formData.append('file',file.files[0]);
xhr.send(formData);
})

php后端代码:

echo $_FILES["file"]["name"].$_POST["name"];

选择文件,然后点击上传按钮,我们就可以得到文件名和name属性的值了。

可见FormData对象为开发者提供了诸多遍历,我们不再需要将数据组成以&符号分割的键值对形式,也不需要设置请求头信息,xhr能够识别FormData对象的实例,然后自动添加对应的头信息。
需要注意的是,FormData对象需要较新浏览器版本才支持,ie10及以上版本才支持,但支持还不完善。

appliaction/json

json格式是目前最为流行并且支持度也比较好的通信数据格式。

var data = {name:'json',age:18};
var jsontext = JSON.stringify(data);
var xhr = new XMLHttpRequest();
xhr.open('post','a.php',true);
xhr.setRequestHeader('Content-Type','application/json;charset=utf-8');
xhr.send(jsontext);

在前端,我们需要对js对象序列化为json字符串,然后传入send方法中发送给服务端,并且同样需要设置请求头部字段的Content-Type值为appliaction/json。
在后台的php代码中,需要使用$GLOBALS[‘HTTP_RAW_POST_DATA’]来接受json数据,在解码后即可正常使用。

$data = $GLOBALS['HTTP_RAW_POST_DATA'];
$data = json_decode($data);
echo $data->name; //'json'

超时设定

超时设定的作用是可以让请求在规定的一段时间后,如果仍然没有响应,可以中断请求。

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
	if (xhr.readyState === 4) {
		try {
			if (xhr.status === 200 || xhr.status === 304) {
				alert(xhr.responseText);
			}
		}catch (e) {
			...
		}
	}
}

xhr.open('get','a.php',true);
xhr.timeout = 1000;
xhr.ontimeout = function () {
	alert('没有响应');
}
xhr.send(null);

上面代码中,通过xhr的timeout属性,我们可以设置超时时间,这里的时间为1000毫秒,当1000毫秒后仍然没有响应,请求就会中止,并且调用ontimeout事件。但这时候,readystate属性值可能变成4了,在请求中止后再访问有关xhr对象的属性,就会报错,因此,我们需要将这个语句放入try-catch语句中。

需要注意的是,对于超时设定,浏览器的兼容性并不好

overrideMimeType方法

该方法用于设置响应的MIME媒体类型,浏览器会根据响应的MIME类型来决定如何处理响应中的主体内容。

var xhr = new XMLHttpRequest();
xhr.open('get','a.php',true);
xhr.overrideMimeType('text/xml');
xhr.send(null);

有的时候,响应中的MIME类型与主体中的内容出现不一致,导致xhr对象不能够正确处理响应内容,通过这个方法,我们就可以修正MIME类型来确保xhr对象能够正确处理。

8、xhr对象的事件

loadstart :在接收到响应数据的第一个字节时触发
progress : 在接受到响应数据期间持续不断地触发
error : 在请求发生错误的时候触发
abort :当请求被取消时触发
load :在接收到完整的响应数据时触发
loadend : 在通信完成或者触发error,abort或load事件时触发。

8.1、onload事件

onload会在接受完响应后触发

var xhr = new XMLHttpRequest();
xhr.onload = function () {
	if (xhr.status === 200) {
		alert(xhr.responseText);
	}
}
xhr.open('post','a.php',true);
xhr.send(null);

与onreadystatechange事件不同的是,每一个阶段的变化,onreadystatechange事件都会被调用,所以需要进一步判断readyState是否等于4来确定是否接受完响应了,而onload事件不需要这么麻烦,这个事件会在接受完响应后被调用。

8.2、progress事件

这个事件会在浏览器接受数据时不停地被触发,该处理程序可以接受一个事件对象,该事件对象中包含四个属性:
target属性代表xhr对象
lengthComputeable属性的值是一个布尔值,代表进度信息是否可用。
position属性表示已经接受的字节数。
tatalSize属性表示需要传输的数据的总字节数。

但是,各浏览器的实现与标准并不一致,例如在谷歌和火狐中,tatal属性表示的是接受数据的总大小,而用loaded表示当前已经接受的字节数。
下面的例子中,将使用ajax获取一张图片,然后显示在页面中。

var xhr;
xhr = new XMLHttpRequest();
xhr.onprogress = function (e) {
     console.log(e.loaded) //11827
       console.log(e.total) //11827
       console.log((e.loaded/e.total*100)+'%') //100%
}
xhr.open("GET","1.jpg",true);
xhr.responseType = "blob";
var img = document.createElement("img");
xhr.onload = function(){
    if (xhr.status == 200) {
        var blob = xhr.response;
        img.onload = function(e) {
            window.URL.revokeObjectURL(img.src); 
        };
        img.src = window.URL.createObjectURL(blob);
        document.body.appendChild(img); 
    }
}
xhr.send();

这个例子中有许多陌生的东西,以下我们将一一解答:

1、xhr.responseType = ‘blob’;
xhr的responseType属性可以用来设置服务端返回的数据类型,这里我们需要获取的是图片,所以需要将值设置为blob,表示服务端需要返回一个包含二进制数据的blob文件对象。

2、var blob = this.response;
注意,这里不可以写成responseText,response和responsetext的区别在于,responseText返回的是文本形式的数据类型。而response则根据responseType值的不同,返回多种数据类型的其中一种,包括blob类型和文本数据类型。因此,我们需要通过response属性来获取blob文件对象。

3、img.src = window.URL.createObjectURL(blob);
createObjectURL方法可接受一个file或blob对象,返回该参数对象的资源地址。这里将这个URL赋值给了img的src,用于在页面上显示图片。
在图像加载完毕后,出于性能和内存方面的考虑,我们需要使用revokeObjectURL来释放那些不需要再使用的URL对象。

在上面是监控下载进度的示例,以下,我们将提供监控上传进度的示例
html:

<input type="file" id="file"/>
<button id="button">上传</button>

js:

var file = document.getElementById('file');
var button = document.getElementById('button');

var xhr = new XMLHttpRequest();
xhr.open('post','a.php',true);

xhr.upload.onprogress = function (e) {
	console.log(e.loaded)//2761
	console.log(e.total)//2761
	console.log(((e.loaded/e.total*100)+'%') //100% 
}

button.addEventListener('click',() => {
	var data = file.files[0];
	var formData = new FormData();
	formData.append('file',data);
	xhr.send(formData);
})

监控上传进度,需要监听upload对象中的onprogress事件,在事件处理函数中可访问相关的进度信息。

9、跨域

我们都知道,通过ajax技术,我们可以访问到服务器上的资源,但这并不意味着你能够访问到任何一台服务器上的资源,事实上,在浏览器的环境中,你的访问权限很有限,只能访问与发出请求的文件所存在同一个域中的资源。当请求的地址与请求来源地址之间的协议,域名,端口号都相同的情况下,不会发生跨域,否则只要有其中一项不同,那么就会造成跨域。

如: http://www.abc.com/index.htmlhttp://www.abc.com/index.php 发出请求,这就不造成跨域,否则,除了路径之外,其他部分只要有一项不同,都会造成跨域。
这是浏览器的一种安全策略。可以防止其他域对服务器可能产生的恶意行为。在web开发中,难免会遇到需要跨域的行为,下面,我们提供了几种对应的解决方案。

9.1、cors

cors称为跨域资源共享,是w3c的标准,为开发者们提供了更加便捷与稳固的跨域方案。这种方案非常简单,前端方面甚至都不需要修改代码。
当浏览器发现请求的地址,与发出请求的页面之间的域名,端口号,协议有不同之处,如:
http://www.a.com/ajax.htmlhttp://www.b.com/ajax.php发出请求。
这种情况下将造成跨域,基于这种情况,浏览器会自动在请求头信息中加入origin字段,字段值为发出请求的页面的来源地址,如:
Origin:http://www.a.com
在请求报文送达到服务器上之后,服务器会根据这个来源地址信息来决定你是否有访问权限。如果服务端方面允许访问,则需要在响应信息头中添加Access-Control-Allow-Origin字段,字段值为允许访问的域名,如:
http://www.a.com
字段值也可以是一个*号,这表示允许任意的域名的访问。
如果响应头中没有包含以上两个字段中的一个,或者字段值不匹配,那么就会报错,跨域失败。

在php中设置头信息:

header("Access-Control-Allow-Origin: *"); //允许所有域名的访问 
header("Access-Control-Allow-Origin: ".$_SERVER['HTTP_REFERER']); //允许请求来源的域名的访问

需要注意的是,cors请求默认是不携带cookie的,如果需要处理cookie,首先,需要服务器方面的同意,这需要服务端在响应头中添加Access-Control-Allow-Credentials字段,当字段值为true的时候,表示服务端允许请求中携带cookie。
另外在前端方面,需要将xhr对象中的withCredentials属性设置为true,告诉浏览器允许在请求中携带cookie。
在需要对cookie进行处理的时候,这两步的设置是必须的。需要注意的是,ie10以下都不支持withCredentials属性。

需要注意的是,当Access-Control-Allow-Origin字段的值为*号的时候,是不可以携带cookie的。
在有的浏览器中,即使withCredentials属性并未设置,cookie也同样会发出,如果需要禁止携带cookie,可以将withCredentials设置为false。

cors固然方便,但兼容性却不好,ie10及以下版本都不支持。如果你需要考虑兼容低版本浏览器,请求考虑以下提供的方式:

ie8中引入了XDomainRequest对象,它部分实现了cors的规范,同样能够进行跨域通信。但是,它的功能却比较匮乏。比如:
不能够携带cookie
只能设置请求头中的Content-Type字段
不能访问响应头中的信息
相对于cors能够支持所有请求类型来说,xdr只支持get与post方式显得格外寒酸

var xdr = new XDomainRequest();
xdr.onload = function () {
	console.log(xdr.responseText);
}
xdr.open('get','http://xxx.xxx.com/index.php');
xdr.send(null);

xdr的使用方法与xhr非常相似,不同的是,xdr的open方法只支持两个参数,就是开启异步与同步的第三个布尔值参数被去掉了,因为xdr只支持异步。
xdr也不提供访问响应状态码的途径,我们只需在onload事件中获取响应主体内容即可。

9.3、cors的兼容方法

通过withCredentials属性,与XDomainRequest对象,我们可以通过能力检测来编写兼容方法:

function creataCorsRequest(method,url) {
	
	var xhr = new XMLHttpRequest();

	if (xhr.withCredentials) {
		xhr.open(method,url,true);
	}else if (typeof XDomainRequest != 'undefined') {
		xhr = new XDomainRequest();
		xhr.open(method,url);
	}

	return xhr;
}

var corsRequest = createCorsRequest('get','http://www.a.com/index.php');

corsRequest.onload = function () {
	console.log(corsRequest.responseText);
}
corsRequest.onerror = function () {
	console.log('error');
}
corsRequest.send(null);

9.4 利用img标签发起跨域请求

通过img标签,我们能从其他站点上获取到图片资源并在自己的页面上展示出来,从其他站点上获取资源,这一属于跨域的行为却没有被浏览器阻止,这正是浏览器为img标签开通的特殊权限,通过这个特点,我们就能够利用img标签发起简单的get跨域请求,要知道,src属性中并不是只能够放上一张图片的链接,完全可以是其他类型的资源文件,例如php文件,需要注意的是,img标签只支持get请求,并且,img标签发起的请求,无法获取响应的内容。

<img src="http://localhost/a.php" "alert('请求完成')">

9.5、 jsonp

与img标签具有同样特殊权限的是script标签,这是为什么我们能够通过script的src属性,就能随意获取到其他域中的js脚本。虽然script标签也只支持get方式,但script标签却拥有着比img标签更强大的能力,那就是script标签能够解析执行响应中有效的js代码字符串。

<script type="text/javascript">
	function fn (obj) {
	console.log(obj.name)
	console.log(obj.age)
}
</script>	
<script type="text/javascript" src="http://localhost/a.php?callback=fn">

php代码:

$callback = $_GET['callback'];
echo $callback."({name:'json',age:18})";

运行上面的例子,你会惊讶地发现我们如愿的在控制台中输出了json和18这两个值。这样的话,我们就能够通过这种方式让后端提供给我们所需要的数据。
然而需要引起人们注意的是一个关于安全的问题:
script其本质就是从服务端处取得js代码,而这些代码能够提供给你期望的数据,同时也不妨碍它可能会进行一些恶意破坏性行为,所以在索取资源的时候,我们应该需要考虑一下为你提供资源的一方是否值得信赖。

以上我们主要介绍了cors和jsonp这两种跨域方案,jsonp的功能比较有限,兼容性却很好,cors功能强大,使用便捷,兼容性却不好。根据场景的不同,哪种方案更加适合,还需我们自己做出选择。

服务器消息推送技术

一直以来,我们讨论的都是由客户端主动向服务端发送请求获取数据的相关话题,那么,有没有一种技术能够反过来,可以让服务端给客户端发送消息,然后由客户端接受并展示给用户呢?这种需求是很常见的,如我们需要向用户推送一些广告,通知消息等等。

在早期,人们通过短轮询的方式来实现这一功能:

所谓短轮询,就是利用循环定时器和ajax频繁地向服务器发起循环,比如,通过循环定时器,我们每隔0.5秒就向服务器发起请求,服务器接受到请求,通过查询数据库查询最新的消息,然后将新消息返回,如果没有最新消息,则返回一个空的响应。这种方式的缺点很明显,大量频繁的ajax对于性能或是流量方面都有不小的问题。

第二种实现方式叫长轮询:

这种方法的原理是,首先需要客户端发起一个请求到服务端,服务端通过如循环的方式来维持住连接,然后间歇性地在数据库中查询最新的消息,当有了最新消息后,循环结束,携带最新消息的响应返回前端,展示给用户,前端随即又再次发起请求,服务端同样保持住连接,查询最新数据,如此循环往复。与短轮询相比,避免了频繁的请求,然而却让需要维护大量连接的服务器压力陡增了。

第三种实现方式叫http流:

这种方式与长轮询类似,同样需要前端率先发起请求,然后需要服务端维持住连接,不同的是,服务端在将数据发送给前端的时候,不会关闭连接,而是一直保持着,将更新的数据不断地发送给前端。可见通讯的流程不再是:
客户端发送请求->服务端接受请求并返回响应数据->客户端接受数据然后断开连接

而是:
客户端发送请求->服务端接受请求并返回最新数据->前端接受数据->服务端又返回了最新数据->前端接受数据->服务端又返回了最新数据…

一段一段的数据就这么通过连接的管道输送给前端,形成http流信息。在整个通讯过程中,只需要建立一次连接。

第四种实现方式

HTML5新增了EventSource对象,可用于实现服务器消息的推送。其原理其实是跟http流一样的。使用sse,首先,我们需要新建一个实例如:

var source= new EventSource("a.php");

EventSource构造函数可接受两个参数,第一个是请求的地址,第二个是一个对象,如:{ withCredentials: true },表示在跨域时允许浏览器携带cookies。实例在被创建后将会根据传入的地址发起请求。

EventSource实例还拥有一个属性与三个事件:

readyState属性表示连接的状态:

0表示正在连接到服务器
1表示打开了连接
2表示关闭了连接

open事件:会在连接建立时触发
message事件:会在服务器接受到数据时触发
error事件:无法建立连接时触发

我们可以通过给message事件指定回调函数,来实时获取服务端返回的数据,回调函数接受一个事件对象,返回的数据会以文本形式保存在事件对象中的data属性中:

source.onmessage = function (evnet) {
	console.log(event.data);
}

在通讯流程结束,连接断开后,sse还会自动重新进行连接,通过实例的close方法,我们可以断开连接,并且不会再重新进行连接。

数据格式:

服务端返回数据的时候,必须指明响应的MIME类型为text/event-stream类型
数据内容,需要用data前缀,如:

data:hello/n/n
data:word/n/n

注意数据行后面的/n/n,每个数据段需要用/n/n进行分割,只有数据段后面后/n/n换行符的时候,才会将这段数据发送给客户端并触发message事件。
如果数据太多,可以写成多行,每行的末尾只需一个/n,但最后一行必须是/n/n,如发送json的时候,可以写成这样:

方式一:

data: {"foo": "bar","baz":555}

方式二:

data: {\n
data: "foo": "bar",\n
data: "baz", 555\n
data: }\n\n

id字段

在每条数据项的前面或后面都一个设置一个id字段,如:

id: msg1\n
data: message\n\n

当连接断开的时候,浏览器会将发送一个http请求头,里面包含一个Last-Event-ID字段,字段的值为连接断开前最后一个被发送的数据项的id值,这样在重建连接的时候,就可以知道数据流是从哪里断开的,并从断开的地方重新发送数据回客户端。

retry 字段

服务器可以用retry字段,指定浏览器重新发起连接的时间间隔。

retry: 10000\n

自定义事件:

默认情况下,客户端接受到数据时触发的是message事件,不过,我们也可以使用自定义事件,这需要在数据项的前面通过event字段自定事件名称:

event: fn\n
data: hello\n\n

这个数据项被发送往客户端的时候,会触发fn事件,所以我们需要对fn事件进行监听:

source.addEventListener('fn', function (event) {
  console.log(event.data);
});

最后附上一个前后端代码的完整例子:

if('EventSource' in window) {
	var source = new EventSource("a.php");
	source.onmessage = function(event) {
		console.log(event.data)
	};
}

PHP后端代码:

header('Content-Type: text/event-stream');
ini_set('max_execution_time', 5);
error_reporting(0);
$i = 0;
while(true){	
	echo "data:Number is $i\n\n";
	ob_flush();	
	flush();	
	$i++;
	sleep(1);	
}

需要注意的是无论是ie或者edge都不支持sse。

websocket

websocket与轮询,http流有着很大的不同,就是,websocket能够实现正真的双向通讯。
轮询技术http流都是建立在http协议上的,而http有一大缺陷就是,只能由客户端发起通讯,而不能由服务端发起通信,这样一来,当服务器需要告知客户端自身的变化时,就会非常麻烦,由于不能自主发起通讯,因此只能通过其他变通的手段来实现。
而现在,websocket打破了这种局限,这种双向通信技术是由一种基于tcp协之上建立而成的websocket协议实现的。不同于http协议,websocket协议能够传输非常少量的数据,比http的开销要节省很多。
最后,别忘了URL的变化,
即http://www.xxx.com将变更为ws://www.xxx.com,
而https://www.xxx.com则变更为wss://www.xxx.com

websocket的使用方法在前端方面非常简单,首先,需要新建实例,并传入需要连接的服务器地址。

var socket = new webSocket('ws://www.xxx.com/test.php');

需要注意的是,传入的url必须是绝对url,websocket没有同源策略的限制,因此,我们可以在不同的域之间自由地通信。

实例创建之后,连接就会被创建。websocket实例对象有一个readyState属性,表示连接的状态:
0 :表示正在建立连接
1 :表示已经建立连接
2 :表示正在关闭连接
3 :表示已经关闭连接

websocket的事件有:

open事件 :连接成功后触发

ws.onopen = function () {
  ws.send('Hello Server!');
}

close事件 : 连接关闭后触发

ws.onclose = function(event) {
  var code = event.code; //服务器返回的状态码
  var reason = event.reason;  //服务器发回的信息
  var wasClean = event.wasClean; //一个布尔值,表示连接是否已经关闭
  // handle close event
};

message事件 :接受到服务器数据后触发

ws.onmessage = function(event) {
  var data = event.data;
  // 处理数据
};

与EventSource不同的是,EventSource只能传输文本数据,二进制数据需要编码后传送,但websocket不仅支持文本格式,还支持二进制数据,如下我们可以对Blob对象进行处理:

ws.onmessage = function(event) {

 if(event.data instanceof Blob){
    var blob = event.data;
  }

};

error事件:报错时被触发

ws.onerror = function (event) {
	
}

向服务器发送数据,需要使用send方法:

ws.send(data);

也可以发送二进制数据:

ws.send(document.getElementById('file').files[0]);

bufferedAmount属性
websocket实例的bufferedAmount属性表示在二进制数据传送的时候,剩余的未发送的字节数。

if (ws.bufferedAmount === 0) {
  console.log('发送完毕');
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值