Ajax
Ajax
Asynchronous JavaScript and XML,通过JavaScript异步通信,从服务器获取XML文档(JSON 格式数据)并从中提取数据,再更新当前网页的对应部分,而不用刷新整个网页。
- 创建XMLHttpRequest实例
- 发出HTTP请求
- 接收服务器传回的数据
- 更新网页数据
注意,AJAX只能向同源网址(协议、域名、端口都相同)发出HTTP请求,如果发出跨域请求,就会报错。
缺点:同源限制。
readyState
表示实例对象的当前状态,只读。
- 0,请求未初始化,表示 XMLHttpRequest 实例已经生成,但是实例的
open()
方法还没有被调用。 - 1,服务器连接已建立,表示
open()
方法已经调用,但是实例的send()
方法还没有调用,仍然可以使用实例的setRequestHeader()
方法,设定 HTTP 请求的头信息。 - 2,请求已接受,表示实例的
send()
方法已经调用,并且服务器返回的头信息和状态码已经收到。 - 3,请求处理中,表示正在接收服务器传来的数据体(body部分)。这时,如果实例的
responseType
属性等于text
或者空字符串,responseText
属性就会包含已经收到的部分信息。 - 4,请求已完成且响应已就绪,表示服务器返回的数据已经完全接收,或者本次接收已经失败。
xhr.onreadystatechange = function(){ /// 通信成功 if (xhr.readyState === 4){ /// 获取数据成功 if (xhr.status === 200){ console.log(xhr.responseText); } else { console.error(xhr.statusText); } } };
status
表示服务器回应的 HTTP 状态码。
- 200, OK,访问正常
- 403, Forbidden,禁止访问
- 404, Not Found,未发现指定网址
- 500, Internal Server Error,服务器发生错误
通常
4xx:客户端错误,客户请求包含语法错误或者是不能正确执行 5xx:服务端错误,服务器不能正确执行一个正确的请求
另外,statusText 属性返回一个字符串,表示服务器发送的状态提示。
upload
主要用于追踪文件上传的进度。
该属性得到一个对象,通过该对象的onprogress上的监听函数可以得知上传的进展。
function updateProgress (evt) { if (evt.lengthComputable) { var percentComplete = evt.loaded / evt.total; } }
getAllResponseHeaders()
收到服务器响应后,获取服务器发来的所有 HTTP 头信息。
返回值为字符串,每个头信息之间使用CRLF
分隔(回车+换行)[\r\n]
/// 获取 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; });
abort()
终止已经发出的 HTTP 请求。调用方法后,readyState属性变为4,status属性变为0。
if (xhr) { xhr.abort(); xhr = null; }
onreadyStateChange VS onload
- load事件表示请求成功完成,服务器传来的数据接收完毕,即只有状态码为4时才回调一次函数。
- onreadystatechange表示只要返回的状态码发生变化就回调一次函数。
Ajax请求
Ajax异步刷新方法
- XMLHttpRequest
- Fetch API
- jQuery
目标可以是:
- 本地数据(.json, .xml, .js)
- 数据库
与POST相比,GET更简单也更快,在大部分情况下都适用。然而以下情况:
- 无法使用缓存文件(更新服务器上的文件或数据库)
- 向服务器发送大量数据(POST没有数据量限制)
- 发送包含未知字符的用户输入时,POST比GET更稳定也更可靠
请使用POST请求。
- 常规ajax请求
class MyComp extends React.Component { componentDidMount() { $.get(this.props.source, (result) => { var infoMsg = result[0]; if (this.isMounted()) { this.setState({ // ...设置组件状态 }); } }.bind(this)); } }); ReactDOM.render( <MyComp source="url" />, document.getElementById('root') );
- Promise对象
class MyComp extends React.Component { componentDidMount() { this.props.promise.then( value => this.setState({...设置组件成功状态}), error => this.setState({...设置组件失败状态})); } } ReactDOM.render( <MyComp promise={$.getJSON('url')} />, document.getElementById('root') );
下面分别简单介绍 ajax 的三种实现方式:
[1]. XMLHttpRequest(XHR)
- API粗糙,配置和调用方式比较混乱
- 浏览器的兼容性问题
- 基于事件机制来跟踪状态变化,异步处理繁琐
- 不符合关注分离(Separation of Concerns)的原则
- 回调地狱
通过 onreadystatechange 属性来检测服务器的响应状态:
// 方法一 指定可调用的函数 xhr.onreadystatechange = onReadyStateChange; function onReadyStateChange() { // do something } // 方法二 使用匿名函数 xhr.onreadystatechange = function(){ // do the thing };
[2]. jQuery实现的AJAX
- 封装原生ajax代码,简单化
- 封装jsonp,支持跨域访问
- 回调地狱问题仍然存在
下面是常用的简单调用示例:
// GET $.get('/api', function(res) { // do something }); // POST var data = { username: 'admin', password: 'root' }; $.post('/api', data, function(res) { // do something });
[3]. Fetch
- API是基于标准Promise设计,支持链式调用
- 回调较清晰,catch捕获异常
fetch(url, options).then(function(response) { // handle HTTP response }, function(error) { // handle network error }).catch(function(e) { // exception error });
Fetch坑
-
Fetch 请求默认不带 cookie,需要设置 options = {credentials: 'include'}
-
服务器返回 400,500 错误码时并不会 reject,只有网络错误这些导致请求不能完成时,fetch 才会被 reject
下面给出一个fetch的封装,摘自网上
function _fetch(url, data, method = 'GET',options={}) { const body = o2s(data); let params = { method: method, }; if (method === 'GET') { // 如果是GET请求,拼接url url += '?' + body; } else { params.body=body } if(options.cookie!=undefined){ params.credentials='include' } if(options.headers!=undefined && typeof options.headers=="object"){ params.headers=new Headers(options.headers); } else{ params.headers=new Headers({ 'Accept': 'application/json', 'Content-Type': 'application/x-www-form-urlencoded' }); } fetch(url, params) .then(r => options.dataType=="text"?r.text():r.json()) .then(r => r); } export function o2s(obj, arr = [], idx = 0) { for (let item in obj) { arr[idx++] = [item, obj[item]]; } return new URLSearchParams(arr).toString(); } export function get(url, data,,options={}) { return _fetch(url, data, 'GET',options); } export function post(url, data,options={}) { return _fetch(url, data, 'POST',options); } // 方法调用示例 post("/api/test",{title:"qwer"},{ dataType:"json", cookie:true, headers:{ 'Accept': 'application/json', 'Content-Type': 'application/x-www-form-urlencoded' } });
fetch加强版
- generator/yield:待学习
- async/await
async function() { try { let response = await fetch(url); let data = response.json(); console.log(data); } catch (error) { console.log('Oops, error: ', error); } }
await后面跟Promise对象,表示等待Promise resolve()才会继续向下执行。如果Promise被reject()或抛出异常,则会被外面的try...catch捕获。
文件上传
HTML网页的<form>元素支持四种格式,向服务器发送数据:
- POST方法,enctype=application/x-www-form-urlencoded,默认方法
- POST方法,enctype=text/plain
- POST方法,enctype=multipart/form-data
- GET方法,enctype属性忽略
通常使用file控件上传文件,控件的multiple属性,标识可以一次选择多个文件。
<input type="file" id='input-file-select' name='input-file-name' multiple/> /// 获取选中待传文件 var fileSelect = document.getElementById('input-file-select'); var files = fileSelect.files; /// 组装FormData对象 var formData = new FormData(); files.forEach(function (file) { formData.append('input-file-name', file, file.name); });
除了使用FormData上传,也可以直接使用File API上传
var xhr = new XMLHttpRequest(); xhr.open('POST', myURL); xhr.setRequestHeader('Content-Type', file.type); xhr.send(file);
Ajax同源问题: CORS
除了服务器代理(浏览器请求同源服务器,再由后者请求外部服务),支持三种方法规避限制
[1]. JSONP
服务器与客户端跨源通信的常用方法,简单适用,服务端改造非常小。
缺点:只能发Get请求。但是,支持老式浏览器,可以向不支持CORS的网站请求数据。
基本思想:网页通过添加一个<script>元素,向服务器请求JSON数据,不受同源政策限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。
[2]. WebSocket
一种使用 ws://(非加密)和 wss://(加密)作为协议前缀的通信协议,不实行同源政策。
优点:允许服务器端与客户端进行全双工(full-duplex)的通信。(HTTP仅支持客户端请求服务器)
WebSocket握手,建立在 TCP 协议之上。
[3]. CORS
跨源资源分享(Cross-Origin Resource Sharing),W3C标准, 跨源Ajax请求的 根本解决 方法。
- 与JSONP相比,支持任何类型的请求
- 整个CORS通信过程,浏览器自动完成
CORS请求分成两类
- 简单请求:同表单请求
- 非简单请求:先预检请求,不通过则提前拒绝,通过则正常请求