前言:
- 浏览器与服务器之间的通信基础是HTTP协议
- 用户通过网址或表单向服务器提交请求,服务器向浏览器发送相应的响应
问题:如何通过点击能获取数据并展示到页面上?
混编模式:前端和后端一起写一个页面(前端的脚本编程方式),要用后端语言的格式。由后端完成HTML代码的构建,构建完了再返回回来一个新的页面。
如何不重新加载整个页面,却能获取到新的网页所需的数据和更新部分网页内容呢?
AJAX:Asynchronous JavaScript and XML 异步的JavaScript和XML
历史:
1999 IE5.0 允许JS脚本向服务器单独发起HTTP请求的新功能(异步,一般是浏览器通过src发起请求)
2004 Gmail 退出异步邮件更新服务
2005 Google Map 异步更新地图服务
2005 AJAX被大厂公认命名
2006 W3C发布AJAX国际标准
总结AJAX是什么?- JavaScript脚本发起HTTP通信
JavaScript异步通信:请求服务器返回JSON/XML文档,前端从XML文档中提取数据,再在不刷新整个网页的基础上,渲染到网页相应的位置。
AJAX示例
jQuery实现AJAX有下面这三种方式
$.ajax({
url: '...',
type: 'POST',
dataType: 'JSON',
data: {
states: 1
},
success: function(data){
console.log(data);
}
});
$.post('url', {status: 1}, function(){
console.log(data);
})
$.get('url?status=1', function(){
console.log(data);
})
封装AJAX
创建XMLHTTPREQUEST对象
原生XMLHttpRequest对象与ActiveX对象
作用:
-
JS脚本HTTP请求的发起必须通过XMLHttpRequest对象
-
通过AJAX进行浏览器与服务器通信的接口
-
不局限于XML,可以发送任何格式的数据(XML只是以前名字的沿用)
XMLHttpRequest本身是一个Js引擎内置的构造函数
所有XMLHttpRequest对象都需要被实例化
var xhr = new XMLHttpRequest();
兼容性:IE5/IE6使用ActiveX对象
var xhr = new ActiveXObject(’Microsoft.XMLHTTP’);
Xmlhttprequest版本
XMLHttpRequest标准又分为Level 1和Level 2
XMLHttpRequest Level 1缺点:
- 无法发送跨域请求
- 不能非纯文本的数据
- 无法获取传输进度(大文件需要)
XMLHttpRequest Level 1I改进
- 可以发送跨域请求
- 支持获取二进制数据(非纯文本数据)
- 支持上传文件
- formData对象
- 可以获取传输进度
- 可以设置超时时间
兼容性问题
- IE8/9/Opara Mini不支持xhr对象 -> ActiveXObject
- IE10/11不支持响应类型为JSON(支持xhr对象,但是不支持xhr对象下的JSON格式)
- 部分浏览器不支持超时设置
- 部分浏览器不支持blob(文件对象的二进制数据)
xhr level2 - 五个事件
xhr.onloadstart: 绑定HTTP 请求发出的监听函数
xhr.onerror:绑定请求失败的监听函数
xhr.onload: 绑定请求成功完成的监听函数(加载完成)
xhr.onabort: 绑定请求中止(调用了abort()方法)的监听函数
xhr.onloadend: 绑定请求完成(不管成功与失败,中断了也还会响应)的监听函数
loadstart -> readyState === 4 -> load/error/abort/ -> loadend
这5个事件最好都不用,兼容性不明确
发送HTTP请求
open方法(发送设置)
参数列表
-
method:请求方式
-
url:请求发送的地址
-
async:true异步 false同步
send方法(发送请求)
参数: 发送POST请求体数据用,GET不填写
发送请求时的响应任务
onreadystatechange事件: 挂载到XMLHttpRequest对象上的事件
status状态:服务器响应的状态码(200 OK、404 未找到页面)
xhr.status/xhr.statusText:服务器回应的 HTTP 状态码/服务器发送的状态提示
| 状态码 | 状态提示 |
|---|---|
| 200 | OK,访问正常 |
| 301 | Moved Permanently,永久移动 |
| 302 | Move temporarily,暂时移动 |
| 304 | Not Modified,未修改 |
| 307 | Temporary Redirect,暂时重定向 |
| 401 | Unauthorized,未授权 |
| 403 | Forbidden,禁止访问 |
| 404 | Not Found,未发现指定网址 |
| 500 | Internal Server Error,服务器发生错误 |
readyState状态:通过XMLHttpRequest对象发送HTTP请求的各阶段状态码(0-4)
当readyState变化时,将触发onreadystatechange事件执行其回调函数
| 状态码 | 状态提示 |
|---|---|
| 0 | 请求未初始化 |
| 1 | 服务器连接已建立 |
| 2 | 请求已接收 |
| 3 | 请求处理中 |
| 4 | 请求已完成,且响应已就绪 |
注意:readyState仅仅是针对请求的状态码,获取资源是否成功取决于status的状态
服务器响应
responseText: 获取字符串数据
responseXML: 获取XML数据
var xhr;
// 创建XMLHttpRequest对象
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
}else{
xhr = new ActiveXObject('Microsoft.XMLHTTP');
}
console.log(xhr.readyState); //0
// 请求发送
xhr.open('GET', 'url...', true)
xhr.send();
console.log(xhr.readyState); //1
// 监听请求和响应的状态
xhr.onreadystatechange = function(){
if (xhr.readyState === 4 && states === 200) {
console.log(xhr.readyState); //234
// 接受数据
console.log(JSON.parse(xhr.responseText));
}
}
POST请求方式的注意事项
POST请求方式下,send方法参数中的格式:a=1&b=2&c=3
xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");
POST请求方式必须设置这个请求头信息,目的是请求体中的数据转换为键值对,这样后端接收到a=1&b=2&c=3这样的数据才知道是这是一个POST方式传来的数据
直接传对象的话,消耗很大
xhr.open('POST', 'url...', true);
xhr.setRequestHeader('Content-type', 'appliaction/x-www-form-urlencoded');
xhr.send('status=1');
封装
var $ = {
ajax: function(opt){
var url = opt.url;
},
post: function(){},
get: function(){}
}
用命名空间的方法,可以但是比较老套
还是用模块化比较好
var xhr = (function(){
function _doAjax(opt){
var o = window.XMLHttpRequest ?
new XMLHttpRequest() :
new ActiveXObject('Microsoft.XMLHTTP');
if(!o){
throw new Error('您的浏览器不支持异步发起HTTP请求');
}
var opt = opt || {},
type = (opt.type || 'GET').toUpperCase(),
async = '' + opt.async === 'false' ? false : true,
dataType = opt.dataType || 'JSON',
jsonp = opt.jsonp || 'cb',
jsonpCallback = opt.jsonpCallback || 'jQuery' + randomNum() + '_' + new Date().getTime();
url = opt.url,
data = opt.data || null,
timeout = opt.timeout || 30000,
error = opt.error || function(){},
success = opt.success || function(){},
complete = opt.complete || function(){},
t = null;
if(!url){
throw new Error('您没有填写URL');
}
if(dataType.toUpperCase() === 'JSONP' && type !== 'GET'){
throw new Error('如果dataType为JSONP,type请您设置GET或不设置');
}
if(dataType.toUpperCase() === 'JSONP'){
var oScript = document.createElement('script');
oScript.src = url.indexOf('?') === -1
? url + '?' + jsonp + '=' + jsonpCallback
: url + '&' + jsonp + '=' + jsonpCallback;
document.body.appendChild(oScript);
document.body.removeChild(oScript);
window[jsonpCallback] = function(data){
success(data);
};
return;
}
o.onreadystatechange = function(){
if(o.readyState === 4){
if((o.status >= 200 && o.status < 300) || o.status === 304){
switch(dataType.toUpperCase()){
case 'JSON':
success(JSON.parse(o.responseText));
break;
case 'TEXT':
success(o.responseText);
break;
case 'XML':
success(o.responseXML);
break;
default:
success(JSON.parse(o.responseText));
}
}else{
error();
}
complete();
clearTimeout(t);
t = null;
o = null;
}
}
o.open(type, url, async);
type === 'POST' && o.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
o.send(type === 'GET' ? null : formatDatas(data));
t = setTimeout(function(){
o.abort();
clearTimeout(t);
t = null;
o = null;
throw new Error('本次请求已超时,API地址:' + url);
}, timeout);
}
function formatDatas(obj){
var str = '';
for(var key in obj){
str += key + '=' + obj[key] + '&';
}
return str.replace(/&$/, '');
}
function randomNum(){
var num = '';
for(var i = 0; i < 20; i++){
num += Math.floor(Math.random() * 10);
}
return num;
}
return {
ajax: function(opt){
_doAjax(opt);
},
post: function(url, data, dataType, successCB, errorCB, completeCB){
_doAjax({
type: 'POST',
url: url,
data: data,
dataType: dataType,
success: csuccessCB,
error: errorCB,
complete: completeCB
});
},
get: function(url, dataType, successCB, errorCB, completeCB){
_doAjax({
type: 'GET',
url: url,
dataType: dataType,
success: csuccessCB,
error: errorCB,
complete: completeCB
})
}
}
})();
请求超时
xhr.timeout: 多少毫秒后,如果请求仍然没有得到结果,就会自动终止。如果该属性等于0,就表示没有时间限制
xhr.timeout = 30000; //过了3000毫秒之后还没有响应,就会超时了,超时了就走ontimeout绑定的函数
xhr .ontimeout:绑定请求超时一个监听函数,如果发生 timeout 事件,就会执行这个监听函数
在open下面写
xhr.ontimeout = function() {
xhr.abort(); //中断
xhr = null; //销毁对象
}
level2兼容性不好
所以在外面用延时器模拟ontimeout就好了

异步与同步
async的值
异步(默认)(async = true):Ajax异步发送请求时,不影响页面加载、用户操作以及AJAX程序后的程序执行
同步(async = false):AJAX同步发送请求时,浏览器必须等到请求完成并响应成功后,AJAX程序后续的程序才会执行。
var datas = null;
$.ajax({
url: ...,
type: 'POST',
data: {...},
async: true,
success: function(data){
console.log(1);
datas = data;
}
});
console.log(2);
console.log(datas);
设置为异步的结果就是依次返回1,null,2(下面的2/datas和ajax的success是一起执行的,只是2更快)
设置为同步是话,结果是1,2,ajax里的data值(ajax请求会阻塞下面的代码执行)
datatype
dataType 返回的数据类型
JSON TEXT XML -> 解决如何正确返回响应格式的数据
dataType = opt.dataType || 'JSON'
xhr.responseXML 返回类型为XML
AJAX- 同时多个AJAX请求
问题:多个AJAX发送请求,只有最后请求能解决到响应?
XHR新实例的创建问题
跨域HTTP请求
为什么会有跨域的问题,就是因为同源策略导致的跨域请求资源的阻塞。
源http://test2.jsplusplus.com/向源http://test.winwin.com获取资源
1 – 服务器中转请求(常用)
同源策略是针对客户端说的,对服务器是无效的;不仅是客户端可以发起请求,服务器也可以向另一台服务器发起请求。
既然客户端发不了不同源是请求,那就发同源的请求给同源服务器(中转),然后由服务器向不同源服务器发送请求,数据返回给同源服务器,再由同源服务器返回给客户端。

2 – 设置基础域名+iframe(用的最多,不用后端写程序,前端全能搞定)
客户端增加一个iframe引入,引入一个不同源的页面,在两个页面上都设置基础域名。获取引入页面的window对象,得到引入页面的ajax函数,通过iframe引入的页面请求和它同源的数据

两个页面都要写基础域名
http://test/winwin.com/index.html
document.domain = 'winwin.com';//返回当前文档的服务器域名
http://test2/winwin.com/index.html
document.domain = 'winwin.com';
var iframe = document.createElement('iframe');
iframe.src = 'http://test/winwin.com/index.html';
iframe.id = 'myIframe';
iframe.style.display = 'none'; //用来跨域的,不是为了显示
iframe.onload = function(){ //iframe引用的页面需要下载时间
var ? = document.getElementById('myIframe').contentWindow.$; //拿到这个不同源页面window对象下面的ajax请求资源
?.post('http://test/winwin.com/get_courses1.php');
}
document.body.appendChild(iframe);
封装:
var ajaxDomain = (function(){
function createIframe(frameId, frameUrl) {
var frame = document.createElement('iframe');
frame.src = frameUrl;
frame.id = frameId;
frame.style.display = 'none';
return frame;
}
return function(opt) {
document.domain = opt.basicDomain;
var frame = createIframe(opt.frameId, opt.frameUrl);
frame.onload = function(){
var ? = document.getElementById(opt.frameId).contentWindow.$;
?.ajax({
url: opt.url,
type: opt.type,
data: opt.data,
success: opt.success,
error: opt.error
});
}
document.body.appendChild(frame);
}
})();
http://test/winwin.com/index.html页面调用:
ajaxDomain({
basicDomain: 'winwin.com',
frameUrl: 'http://test/winwin.com/index.html',
url: 'http://test/winwin.com/get_courses1.php',
type: 'POST',
data: {
status: 1
},
success: function(data){
console.log(data);
},
error: function(){
console.log(0);
}
})
3 – window.name+iframe
通过window.name可以实现互相之间传值,有共享属性
window.name的特点:
-
每个浏览器窗口都有一个全局变量window(包含iframe框架contentWindow)
-
每个window对象都有一个name属性(注意:一个窗口只有一个name属性)
-
该窗口被关闭前(生命周期内),所有页面共享一个name属性并有读写的权限
-
无论该窗口在被关闭前,载入什么页面,都不会改变name值
-
存储约为2M的字符串
-
如果父级窗口地址源和iframe的地址源不同,父级无法通过iframe.contentWindow.name获取值(同源策略干扰),但iframe内部(iframe和iframe之间)不受该规则限制
解决方案:先让iframe中的页面程序保存window.name, 然后跳转与父级窗口同源的另一个页面,父级页面可以从当前的iframe拿到该页面的window.name (不同页面可以获取同一个窗口的window.name)

将请求过来的数据放在window.name里去,iframe无论怎么跳转,都共享一份window.name,而且大家都获取的到,只有父级获取不到。
$.post('http://test/winwin.com/get_courses1.php', {
status: 1
}, function(data){
window.name = JSON.stringify(data);
})
iframe跳转到和test2同源的页面,那么主页面就可以获取到同源的window.name,就可以获取到上面请求到的数据。
var flag = false;
var iframe = document.createElement('iframe');
var getDatas = function(){
if(flag){
var data = iframe.contentWindow.name;
console.log(ISON.parse(data));
}else{
flag = true;
setTimeout(function(){ //AJAX请求需要时间
iframe.contentWindow.location = 'index2.html';
}, 500);
}
}
iframe.src = 'http://test/winwin.com/index.html';
if (iframe.attachEvent) {
iframe.attachEvent('onload', getDatas);
}else{
iframe.onload = getDatas;
}
document.body.appendChild(iframe);
这个方法一般都是传比较简单的值,传JSON数据比较少。
4 – postmessage+iframe (了解)
不常用原因:
-
伪造数据端漏洞
-
xss攻击
-
兼容性问题
变量参数:otherWindow.postMessage(message, targetOrigin)
otherWindow: 接收方的引用
message: 要发送到接受方的数据
targetOrigin: 接收方的源,还有必须要有监听message事件

5 – hash+iframe (了解)
基本原理:利用url的hash值 #xxx来传递数据
基础工具:location.hash
6 – cors跨域
"跨域资源共享"(Cross-origin resource sharing)
任意域名:(php)
header("Access-Control-Allow-Origin: *");
单域名:
header("Access-Control-Allow-Origin: http://test2.jsplusplus.com");
多域名:
$allowed_origins = array('http://test2.jsplusplus.com', 'http://test3.jsplusplus.com');
header("Access-Control-Allow-Origin: ".$allowed_origins);
通知服务器在真正的请求中会采用哪种 HTTP 方法
header("Access-Control-Request-Methods:GET,POST");
可能会有安全问题
7 – JSONP跨域
JSONP - JSON with Padding: 跨域获取JSON数据的一种非官方的使用模式,一定是GET方式
JSONP的目的就是为了跨域拿资源,它利用script标签不受同源策略影响的机制,拿到后端脚本,让后端拼接出来前端需要的脚本(也就是函数执行),拼接好了之后,返回回来。响应好了之后就会执行本身定义好的函数。
-
JSON和JSONP不是一个类型
-
JSON是数据交换格式
-
SONP是一种跨域获取JSON数据的交互技术(非正式的协议)
-
JSONP抓取的资源并不直接是JSON数据,而是带有JSON数据参数- 的函数执行
-
客户端期望返回的:{”name”:”Jacky”, “age”: ”18”}
JSONP实际返回的:callback({”name”:”Jacky”, “age”: ”18”})
同源策略到底给谁走了后门?(案例)
-
img的src引入不同源的图片资源
-
link的href引入不同源的样式文件资源
-
iframe的src引入不同源的网页资源
-
script的src引入不同源的脚本文件资源
script src引入php和引入js脚本的区别
script src引用的脚本的后缀名问题:后缀名对于script标签来说并不重要,它本身就是用来引入脚本的,只解析这个文本里的内容是什么,就算是txt格式的也可以解析执行(写的是什么,就按脚本解析什么)
get方式传值
<script type="text/javascript">
function test1(str){
console.log(str);
}
function test2(str){
console.log(str);
}
function test3(str){
console.log(str);
}
</script>
<script src="http://test.winwin.com/jsonp.js?cb=test3"></script>
jsonp.js
function getParams(){
var path = document.getElementById('javascript').src,
callback = path.match(/cb=(.*)/)[1]; //替代地址上的cb值
switch(callback){
case 'test1':
test1('test1');
break;
case 'test2':
test2('test2');
break;
case 'test3':
test3('test3');
break;
defqult:
test1('test1');
}
}
getParams()
实现跨域
引入脚本,就相当于把脚本里的代码全都拿进来
<script type="text/javascript">
function test(data){
console.log(data);
}
</script>
<script src="http://test.winwin.com/jsonp/jsonp.js"></script>
jsonp.js
$.ajax({
url: 'http://test.winwin.com/get_course.php',
type: 'POST',
data: {
status: 1
},
success: function(data){
test(data);
}
})
不管是js脚本,还是php脚本,反正php脚本会在后端执行完之后再返回结果,响应到前端。只要响应回来的东西是script标签认识的,就可以正常解析,有错误就按脚本的报错来。
<script type="text/javascript">
function test(data){
console.log(data);
}
</script>
<script src="http://test.winwin.com/jsonp/jsonp.php?cb=test"></script>
<?php
$cb = $_GET;
echo $cb.'("winwin")'
cb是处理跨域返回数据的函数
php脚本返回test("winwin"),可以被js执行
但是如果有10个需要跨域的数据,就需要10个script标签?这样其实是不太合理的,所以可以动态创建脚本,获取到数据之后就删除。
var oBtn = document.createElement('btn');
oBtn.onclick = function() {
oScript = document.createElement('script');
document.src = "http://test.winwin.com/jsonp/jsonp.php?cb=test";//已经开始响应了
document.body.appendChild(oScript);//执行
document.body.removeChild(oScript);//获取到数据之后就删除
}
function test(data){
console.log(data);
}
用jQuery JSON如何获取数据?
jQuery不用另外写处理数据的函数test,返回的数据的处理是在success里处理的。
oBtn.onclick = function() {
$.ajax({
url: 'http://test.winwin.com/jsonp/jsonp.php',
type: 'get',
dataType: 'JSONP',
json: 'cb',
jsonpCallback: 'test', //响应的函数名称,没有设置的话就会给一个随机的名称,其实实际上没有什么实际意义
success: function(data) {
console.log(data);
}
});
}
封装自己的AJAX JSONP
oScript = document.createElement('script');
document.src = "http://test.winwin.com/jsonp/jsonp.php?" + opt.jsonp + '=' + opt.jsonpCallback;
document.body.appendChild(oScript);
document.body.removeChild(oScript);
=>
配置项
jsonp = opt.jsonp || 'cb',
jsonpCallback = opt.jsonpCallback || 'jQuery' + randomNum() + '_' + new Date().getTime();
if(dataType.toUpperCase() === 'JSONP'){
var oScript = document.createElement('script');
oScript.src = url.indexOf('?') === -1
? url + '?' + jsonp + '=' + jsonpCallback
: url + '&' + jsonp + '=' + jsonpCallback; //存在问号就用&合并其他参数
document.body.appendChild(oScript);
document.body.removeChild(oScript);
window[jsonpCallback] = function(data){
success(data);
};
return;
}
未配置jsonpCallback的话,自动生成随机数
function randomNum(){
var num = '';
for(var i = 0; i < 20; i++){
num += Math.floor(Math.random() * 10);
}
return num;
}
完整的部分看上面的AJAX封装
百度搜索联想词 sp0.baidu.com/5a1Fazu8AA5…
淘宝商品联想词JSONP API suggest.taobao.com/sug?code=ut…
手机号码归属地、运营商查询JSONP API www.baifubao.com/callback?cm…
当前所在城市天气JSONP API api.asilu.com/weather_v2/…
历史上的今天JSONP API api.asilu.com/today/?call…
个人身份基本信息JSONP API api.asilu.com/idcard/?cal…
有道翻译JSONP API fanyi.youdao.com/openapi.do?…
百度音乐搜索JSONP API tingapi.ting.baidu.com/v1/restserv…