浏览器发出请求的方式有:
src
或者href
@import
var img = new Image(); // DOM的image标签对象
img.src = "1.jpg"; // 这句话运行后浏览器就会尝试去发送请求获取图片
AJAX就是浏览器提供的一套API,可以使用JavaScript调用,对服务器进行请求的发送和响应获取。
类比与使用浏览器查看页面的步骤:
使用步骤:
方法/函数/属性 | 含义 |
---|---|
new XMLHttpRequest() |
创建 xhr 请求对象 |
xhr.open('GET', 'url' ) |
创建链接 |
xhr.send() |
发送请求 |
xhr.readyState |
获取状态 |
xhr.responseText |
获取请求内容 |
xhr.getAllResponseHeaders() |
获取所有响应头 |
xhr.getResponseHeader("string") |
根据key获取特定响应头内容 |
xhr.onreadystatechange |
状态时间变化的event |
var xhr = new XMLHttpRequest()
xhr.open('GET', './time.php')
xhr.send()
// 因为客户端永远不知道服务端何时才能返回我们需要的响应
// 所以 AJAX API 采用事件的机制(通知的感觉)
xhr.onreadystatechange = function () {
// 这个事件并不是只在响应时触发,XHR 状态改变就触发
if (this.readyState !== 4) return
// 获取响应的内容(响应体)
console.log(this.responseText)
}
JS是单线程的
发送请求的地址不能使用文件协议访问(file://)
readyState
状态码onreadystatechange
事件会在xhr
对象状态变化时触发调用,所以说这个事件会多次触发。
状态码 | 时机 | 含义 |
---|---|---|
0 | UNSENT | 已经完成对象初始化 创建了xhr 对象之后,但还未调用open , |
1 | OEPNED | xhr 对象调用了open 方法之后,已建立了一个与服务端特定端口的连接 |
2 | HEADERS_RECIEVED | send 已经被调用,且已获取了响应报文的响应头和响应行 |
3 | LOADING | xhr 正在下载响应报文的响应体,responseText 已经可以获取部分响应体 |
4 | DONE | xhr 处理结束,整个响应报文已经完整下载下来了,可以直接使用responseText |
习惯使用
addEventListener
注册事件
var xhr = new XMLHttpRequest();
console.log(xhr.readyState);
// => 0
// 初始化 请求代理对象
xhr.open('GET', 'time.php');
console.log(xhr.readyState);
// => 1
// open 方法已经调用,建立一个与服务端特定端口的连接
xhr.send();
xhr.addEventListener('readystatechange', function () {
switch (this.readyState) {
case 2:
// => 2
// 已经接受到了响应报文的响应头
// 可以拿到头
// console.log(this.getAllResponseHeaders())
console.log(this.getResponseHeader('server'));
// 但是还没有拿到体
console.log(this.responseText);
break;
case 3:
// => 3
// 正在下载响应报文的响应体,有可能响应体为空,也有可能不完整
// 在这里处理响应体不保险(不可靠)
console.log(this.responseText);
break;
case 4:
// => 4
// 一切 OK (整个响应报文已经完整下载下来了)
console.log(this.responseText);
break;
}
})
方法/属性 | 含义 |
---|---|
xhr.setRequestHeader(key,value) |
设置请求头,一次设置一个 |
xhr.send(data) |
发送的时候设置请求体 |
xhr.status |
响应状态码 |
xhr.statusText |
响应状态描述文本 |
xhr.responseText |
获取请求内容 |
xhr.getAllResponseHeaders() |
获取所有响应头 |
xhr.getResponseHeader("string") |
根据key获取特定响应头内容 |
根据不同携带的内容,要设置不同的
Content-Type
。
Content-Type ‘application/x-www-form-urlencoded’, 携带的是Form Data
Content-Type 'text/plain;charset=UTF-8, 携带的是Request Payload
var xhr = new XMLHttpRequest()
xhr.open('POST', '/add.php') // 设置请求行
xhr.setRequestHeader('Foo', 'Bar') // 设置一个请求头
// 一旦你的请求体是 urlencoded 格式的内容,一定要设置请求头中 Content-Type 'application/x-www-form-urlencoded'
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
xhr.send('key1=value1&key2=value2') // 以 urlencoded 格式设置请求体
send
传递,并且要记住设置 Content-Tye
为application/x-www-form-urlencoded
xhr1.open('GET', 'users.php?id=' + this.id)
// 设置Content-Type
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
// xhr.send('username=' + username + '&password=' + password)
xhr.send(`username=${username}&password=${password}`) // 发送POST请求内容
浏览器可以将id唯一的对象创建出来直接使用:
<input type="button" id="btn" />
<script>
btn.onclick = ...
script>
open
方法还有第三个参数async
, 取值为true/false
,默认是true
:send
方法,在同步中,send结束就可以直接获取响应体;而异步中,需要使用事件处理 var xhrAsync = new XMLHttpRequest()
// open 方法的第三个参数是 async 可以传入一个布尔值,默认为 true
xhrAsync.open('GET', 'time.php', true)
console.time('async')
xhrAsync.send()
console.log(xhrAsync.responseText)
// console.log('end request')
console.timeEnd('async')
// 同步模式 ajax 操作会有楞等的情况
// 区别在于 send 方法会不会出现等待情况
// console.log('begin request')
var xhrSync = new XMLHttpRequest()
// open 方法的第三个参数是 async 可以传入一个布尔值,默认为 true
xhrSync.open('GET', 'time.php', false)
console.time('sync')
xhrSync.send()
console.log(xhrSync.responseText)
// console.log('end request')
console.timeEnd('sync')
同步阶段,注册onreadystatechange
的时机需要在send
之前:
var xhr = new XMLHttpRequest()
xhr.open('GET', 'time.php', true)
xhr.send()
// 一定要注意事件注册时间问题
xhr.onreadystatechange = function () {
console.log(this.readyState)
}
console.time(
字符串标识符
);
console.timeEnd(字符串标识符
); // 结束秒表,打印计时
方法 |
---|
JSON.parse() |
var jsonData = JSON.parse(data);
方法 |
---|
xhr.responseXML 返回DOM |
但是需要服务端响应头中的 Content-Type 必须是 application/xml
// this.responseXML 专门用于获取服务端返回的 XML 数据,操作方式就是通过 DOM 的方式操作
console.log(this.responseXML.documentElement.children[0].innerHTML)
console.log(this.responseXML.documentElement.getElementsByTagName('name')[0])
XMLHttpRequest
在老版本IE(5/6)有兼容问题:
var xhr = window.XMLHttpRequest ?new XMLHttpRequest(): new ActiveXObject('Microsoft.XMLHTTP');
xhr.responseType
属性是客户端设置的,这样之后就可以通过xhr.response
来获取对应的对象:
// 我们通过代码告诉请求代理对象,服务端响应给我们的是 JSON
xhr.responseType = 'json';
xhr.onreadystatechange = function () {
if (this.readyState !== 4) return;
console.log(this.response);
// this.response 获取到的结果会根据 this.responseType 的变化而变化
// this.responseText 永远获取的是字符串形式的响应体
}
这种异步调用结束时的结果,没法像同步调用一样返回给调用者,只能通过回调函数
function ajax (method, url, params, done) {
method = method.toUpperCase()
var xhr = new XMLHttpRequest()
if (typeof params === 'object') {
var tempArr = []
for (var key in params) {
var value = params[key]
tempArr.push(key + '=' + value)
}
params = tempArr.join('&')
}
if (method === 'GET') {
url += '?' + params
}
xhr.open(method, url, false)
var data = null
if (method === 'POST') {
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
data = params
}
xhr.onreadystatechange = function () {
if (this.readyState !== 4) return
// 不应该在封装的函数中主观的处理响应结果
// console.log(this.responseText)
// 你说我太主观,那么你告诉我应该做什么
done(this.responseText)
}
xhr.send(data)
}
http://www.ruanyifeng.com/blog/2012/09/xmlhttprequest_level_2.html
onload
事件HTML提供的 XMLHttpRequest Version 2.0
onload
事件,就是专门用来处理当请求已经完成时候的处理:
var xhr = new XMLHttpRequest();
xhr.open("GET", "url");
xhr.send();
xhr.onload = function(){
console.log(this.readyState); // 永远是 4
console.log(this.responseText);
}
有很多前端的js渲染模板,art-template就是一个国产的。
步骤:
https://github.com/tj/consolidate.js#supported-template-engines
重点::
text/javascript
,这样不会被解释执行:script
标签在被使用做为模板的时候,type
属性建议写跟模板相关的内容,比如x-art-template
, 其中x
代表自定义
<html lang="en">
<head>
<meta charset="UTF-8">
<title>AJAX 动态表格渲染title>
head>
<body>
<table id="demo">table>
<script id="tmpl" type="text/x-art-template">
{{each comments}}
<!-- each 内部 $value 拿到的是当前被遍历的那个元素 -->
<tr>
<td>{{$value.author}}</td>
<td>{{$value.content}}</td>
<td>{{$value.created}}</td>
</tr>
{{/each}}
script>
<script src="template-web.js">script>
<script>
var xhr = new XMLHttpRequest()
xhr.open('GET', 'test.php')
xhr.send()
xhr.onreadystatechange = function () {
if (this.readyState !== 4) return
var res = JSON.parse(this.responseText)
// 模板所需数据
var context = { comments: res.data }
// 借助模板引擎的API 渲染数据
var html = template('tmpl', context)
console.log(html)
document.getElementById('demo').innerHTML = html
// 1. 选择一个模板引擎
// https://github.com/tj/consolidate.js#supported-template-engines
// 2. 下载模板引擎JS文件
// 3. 引入到页面中
// 4. 准备一个模板
// 5. 准备一个数据
// 6. 通过模板引擎的JS提供的一个函数将模板和数据整合得到渲染结果HTML
// 7. 将渲染结果的HTML 设置到 默认元素的 innerHTML 中
//
// 为什么不在JS变量中写模板?
// 1. 如果将模板写到JS中,维护不方便,不能换行,没有着色
// 为什么使用script标记
// 1. script不会显示在界面
}
script>
body>
html>
$.ajax
方法是jQuery封装的ajax
, 底层也是 XMLHttpRequest
。 jQuery也提供了其他更加方便的ajax方法,它们的底层都是$.ajax
方法 |
---|
$.ajax |
$.get |
$.post |
$.getJSON |
https://api.jquery.com/jQuery.ajax/
// 关于参数
$.ajax({
url: 'json.php',
type: 'get',
// 设置的是请求参数
data: { id: 1, name: '张三' },
// 用于设置响应体的类型 注意 跟 data 参数没关系!!!
dataType: 'json',
success: function (res) {
// 一旦设置的 dataType 选项,就不再关心 服务端 响应的 Content-Type 了
// 客户端会主观认为服务端返回的就是 JSON 格式的字符串
console.log(res)
}
})
// 关于回调
$.ajax({
url: 'time.php',
type: 'get',
beforeSend: function (xhr) {
// 在所有发送请求的操作(open, send)之前执行
console.log('beforeSend', xhr)
},
success: function (res) {
// 隐藏 loading
// 只有请求成功(状态码为200)才会执行这个函数
console.log(res)
},
error: function (xhr) {
// 隐藏 loading
// 只有请求不正常(状态码不为200)才会执行
console.log('error', xhr)
},
complete: function (xhr) {
// 不管是成功还是失败都是完成,都会执行这个 complete 函数
console.log('complete', xhr)
}
})
$.get 、 $.post
、$.getJSON
$.get('json.php', { id: 1 }, function (res) {
console.log(res)
})
$.post('json.php', { id: 1 }, function (res) {
console.log(res)
})
$.getJSON('json.php', { id: 1 }, function (res) {
console.log(res)
})
$对象.load
ajax发送数据,然后将获取到的结果,直接替换元素的内容
$( "#result" ).load( "ajax/test.html #container" );
上面代码,会像 ajax/test.html
发送请求,然后将获取到的元素解析,拿到其中id="container"
的元素,将其替换成 原页面中#result
选择器选中元素的内容。
$(function($){
// jquery 的入口可以传入一个 $, 代表就是jquery对象
// 这样以后就可以调用使用这个局部 jq对象,而不是全局的 jq对象了
});
为所有ajax请求来注册事件,会让每次发送请求的特定时机都触发相同的handler。
Global events are never fired for cross-domain script or JSONP requests, regardless of the value of global
事件处理函数 |
---|
.ajaxComplete() |
.ajaxError() |
.ajaxSend() |
.ajaxStart() |
.ajaxStop() |
.ajaxSuccess() |
$( document ).ajaxComplete(function() {
$( ".log" ).text( "Triggered ajaxComplete handler." );
});
所有这些全局注册事件,都必须绑定在document
上面。
就是一个库,用于在浏览器最顶端显示进度条
<link rel="stylesheet" href="../nprogress.css">
<script src="../nprogress.js">script>
<script>
$(document)
.ajaxStart(function () {
NProgress.start()
})
.ajaxStop(function () {
NProgress.done()
})
script>
除了ajax,还有其他可以发送请求的方式:
var img = new Image()
img.src = 'http://locally.uieee.com/categories'
var link = document.createElement('link')
link.rel = 'stylesheet'
link.href = 'http://locally.uieee.com/categories'
document.body.appendChild(link) // 要加入到文档中,才能发送出去
var script = document.createElement('script')
script.src = 'http://localhost/time2.php'
document.body.appendChild(script) // 开始发起请求
用js创建script标签发送请求,这是一个异步操作,无法获取到结果,更别说json的结果了。
解决办法:
让服务器端返回一段JavaScript代码,将数据传入一个事先约定好的函数中当参数,然后客户端定义这个函数,就可以处理了。
客户端:
var script = document.createElement('script')
script.src = 'http://localhost/time2.php'
document.body.appendChild(script) // 开始发起请求
// 相当于请求的回调
function foo (res) {
console.log(res)
}
服务器端:
application/javascript
foo(data)
这样当客户端接收到服务器端响应后,就会解析其中的js代码,然后调用 foo
函数,由于foo
已经在客户端定义了,所以就能够接收到服务器给的参数
JSONP就是 JSON with Padding, 是一种借助script
标签发送跨域请求的技巧
其原理就是在客户端借助script
标签请求服务端的一个动态网页,服务器端返回一段带有函数调用的JavaScript全局函数调用的脚本,将原本需要返回给客户端的数据传递进去。
以后绝大多数都是采用JSONP的手段完成不同源地址之间的跨域请求。
例子:
客户端:
<script src="http://api.xxxx.xxx/a.php?callback=foo">script>
服务器端发送
foo(data)
总结:
XMLHttpRequest
无法发送不同源地址之间的请求,所以才导致使用script
标签来解决跨越办法,这种方式成为JSONP
GET
请求script
标签,和AJAX没有任何关系$.ajax
,然后设置dataType
为jsonp
$.ajax({
url: 'http://localhost/jsonp/server.php',
dataType: 'jsonp',
success: function (res) {
console.log(res)
}
})
Cross Origin Resource Share
跨域问题以前无法解决,而现在是可以解决的了,只要在服务器返回响应的时候设置一个头:
Access-Control-Allow-Origin: * # *代表允许所有IP地址的请求
这样就可以用AJAX跨域了