ajax
是什么
可以在不重新加载整个网页的情况下,对网页的某部分进行更新。(重新拍照理解为重新加载网页)
直白地说,就是没用AJAX的网页,你点一个按钮就要刷新一下页面,尽管新页面上只有一行字和当前页面不一样,但你还是要无聊地等待页面刷新。用了AJAX之后,你点击,然后页面上的一行字就变化了,页面本身不用刷。
AJAX只是一种技术,不是某种具体的东西。不同的浏览器有自己实现AJAX的组件。
手写 ajax
- 创建 ajax 对象
- 等待数据响应
- 调用 open
- 调用 send
let xhr = null
try {
xhr = new XMLHttpRequest()
} catch (error) {
xhr = new ActiveXObject('error')
}
//必须在调用open()方法之前指定onreadystatechange事件处理程序才能确保跨域浏览器兼容性问题
//只要readyState属性的值有变化,就会触发readystatechange事件
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
//判断本次下载的状态码都是多少 304表示请求的资源没有被修改
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
alert(xhr.responseText)
} else {
alert('Error' + xhr.status)
}
}
}
xhr.open('get', 'https://api.github.com/repos/vmg/redcarpet/issues?state=closed', true)
xhr.send()
这样写似乎也可以,但是总觉得少点什么?
啥,又要我封装,我能拒绝吗?可以
这答案出乎我意料啊🤔,在拒绝之前,先看一个已经封装好的 promise 版的吧。
Ajax Promise 版
function ajax(method, url) {
return new Promise((resolve, reject) => {
const request = new XMLHttpRequest();
request.open(method, url);
request.onreadystatechange = () => {
if (request.readyState === 4) {
if (request.status === 200) {
resolve(request.response);
} else {
reject(request);
}
}
};
request.send();
});
}
ajax("get", "https://api.github.com/repos/vmg/redcarpet/issues?state=closed").then(response => {
console.log(response);
});
接下来隆重登场,fetch
Fetch
Fetch API 提供了一个 JavaScript 接口,用于访问和操纵 HTTP 管道的一些具体部分,例如请求和响应。它还提供了一个全局 fetch()
方法,该方法提供了一种简单,合理的方式来跨网络异步获取资源。
这种功能以前是使用 XMLHttpRequest
实现的。Fetch 提供了一个更理想的替代方案,可以很容易地被其他技术使用,例如 Service Workers
(en-US)。Fetch 还提供了专门的逻辑空间来定义其他与 HTTP 相关的概念,例如 CORS 和 HTTP 的扩展。
fetch('https://api.github.com/repos/vmg/redcarpet/issues?state=closed')
.then(function (response) {
return response.json();
})
.then(function (myJson) {
console.log(myJson);
});
fetch 实现并行请求
通过 promise.all 来实现并行请求
let p1 = new Promise((resolve, reject) => {
fetch('https://api.github.com/users/suiboyu/starred').then(res =>
res.json()
).then((res) => {
resolve(res)
})
})
let p2 = new Promise((resolve, reject) => {
fetch('https://api.github.com/users/suiboyu/events/public').then(res =>
res.json()
).then((res) => {
resolve(res)
})
})
Promise.all([p1, p2]).then((res) => {
console.log(res)
})
到目前为止,还没有可以取消 fetch 的真正方法。 JavaScript 规范中添加了新的 AbortController,允许开发人员使用信号中止一个或多个 fetch 调用。
以下是取消 fetch 调用的工作流程:
- 创建一个 AbortController 实例
- 该实例具有 signal 属性
- 将 signal 传递给 fetch option 的 signal
- 调用 AbortController 的 abort 属性来取消所有使用该信号的 fetch
const controller = new AbortController();
const { signal } = controller;
fetch("https://api.github.com/users/suiboyu/events/public", { signal }).then(response =>
response.json()
).then(res => {
console.log(res)
}).catch(e => {
if (e.name === 'AbortError') {
console.warn(`中断 fetch 请求: ${e.message}`);
}
});
// Abort request
controller.abort();
以上的方式方式只能实现浏览器发起请求,服务器返回数据,服务器不能主动返回数据。要实现实时数据交互只能是ajax轮询(让浏览器隔个几秒就发送一次请求,然后更新客户端显示。这种方式实际上浪费了大量流量并且对服务端造成了很大压力)。
轮询
实现原理:每隔一段时间发一次请求来获取最新数据,定时器发送 ajax 请求,DOM 操作更新页面数据。
缺点
- 对服务器造成的压力比较大,耗费资源。请求太多太频繁,如果是访问量比较大的网站,就会造成压力了
- 会有延迟,数据的实时性不高。并不是数据刚更新就能拿到并更新的,需要请求正好能拿到数据。
- 数据看起来可能会有紊乱,同一时间你看到的数据和别人的不一样。页面打开开始计算的请求定时器开始时间不一样,对方拿到的可能是刚刚刷新的数据,而你还没去获取最新数据
代码实现
function getData() {
let xhr = null
try {
xhr = new XMLHttpRequest()
} catch (error) {
xhr = new ActiveXObject('error')
}
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
alert(xhr.responseText)
} else {
alert('Error' + xhr.status)
}
}
}
xhr.open('get', 'https://api.github.com/repos/vmg/redcarpet/issues?state=closed', true)
xhr.send()
}
window.onload = function () {
setInterval(() => {
getData()
}, 2000);
}
长轮询
实现原理:请求进来,有数据就返回,没有就夯住(先不把请求响应给前端),直到有数据或者超时再返回(然后立即再发起一个请求过来)
在前端的表现就是请求处于 pending 状态
如网页版微信就是利用长轮询实现的:登录微信后会有一个请求发到后端,一直等待(请求处于 pending 状态)后端返回数据,拿到后端数据之后又立马再发一个请求同样等待数据,就这样不停地等着拿数据(后端可能用的是 Queue,q.get()
取数据时没有数据就会夯在那里,等有数据了就接着执行后面的代码,响应给前端),如此往复也就能实现数据的实时获取了
这样做其实不太好,但网页版微信这么做是为了做兼容,ie 还不能很好地兼容 H5 的特性
好处
- 可以降低延迟(设置一个超时时间,在这段时间内,一有数据就返回)
减少了一定的请求次数,把单纯依靠请求来获取数据变成等待数据主动返回、超时返回相结合(返回了立即再次发起请求等着获取最新数据)
缺点
- 对服务器造成的压力依旧比较大,耗费资源
代码实现
这个实现方式一般觉察不出什么,相对延迟较低
function ajax(method, url) {
return new Promise((resolve, reject) => {
const request = new XMLHttpRequest();
request.open(method, url);
request.onreadystatechange = () => {
if (request.readyState === 4) {
if (request.status === 200) {
resolve(request.response);
} else {
reject(request);
}
}
};
request.send();
});
}
function getData() {
ajax("get", "https://api.github.com/repos/vmg/redcarpet/issues?state=closed").then(response => {
if (response !== '') {
// 拿到数据就更新页面,让用户看到最新信息
alert(response)
}
getData();
});
}
window.onload = function () {
getData();
}
Websocket
websocket是HTML5出的东西(协议),是一种全双工通信机制,两端可以及时地互发事件,互发数据,相互通信,只需要浏览器和服务器建立一次连接,服务器就可以主动推送数据到浏览器实现实时数据更新。
协议规定
- 连接的时候需要握手(是基于 HTTP 来发起握手的)
- 发送的数据需要加密(根据 websocket 协议去发送数据)
- 保持链接不断开
接下来我们使用 node + express 搭建后台服务器
mkdir node
npm init -y
npm install express --save
npm install nodejs-websocket
let express = require('express');
let app = express();
const io = require('nodejs-websocket');
io.createServer(connection => {
console.log('new connection....')
connection.on('text', function (data) {
console.log("接收到的客户端消息:" + data);
connection.sendText("服务器端返回数据:" + data)
})
connection.on('close', function (code, reason) {
console.log('connection closed')
})
connection.on('error', () => {
console.log('close')
})
}).listen(3000);
// 创建web服务,设定端口号和ip地址
app.listen(8081, function () {
console.log("应用实例,访问地址为 http://localhost:8081")
})
初始化前端页面
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>websocket测试</title>
</head>
<body>
<button type="button" id="btn">按钮</button>
<input type="text" name="" id="demo" value="" />
<div id="res">接收到的服务器端消息显示区域</div>
</body>
</html>
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
<script type="text/javascript">
// 打开一个 web socket,设定websocket服务器地址和端口
var ws = new WebSocket("ws://127.0.0.1:3000");
//开启连接open后客户端处理方法
ws.onopen = function () {
// Web Socket 已连接上,在页面中显示消息
document.getElementById('res').innerHTML = "当前客户端已经连接到websocket服务器";
};
// 点击按钮时给websocket服务器端发送消息
$('#btn').click(function () {
var value = $('#demo').val();
ws.send(value);
})
// 接收消息后客户端处理方法
ws.onmessage = function (evt) {
console.log(evt.data);
$('#res').text(evt.data);
};
// 关闭websocket
ws.onclose = function () {
// 关闭 websocket
alert("连接已关闭...");
};
</script>
</body>
</html>
当前端页面发送数据的时候,后台接受到数据,并返回前端页面数据。