Ajax简介

Ajax

了解AJAX

  • AJAX: Asynchronous Javascript And Xml,Ajax 技术的核心是XMLHttpRequest对象(简称XHR),这是由微软首先引入的一个特性,其他浏览器提供商后来都提供了相同的实现

*Ajax起源:
最早出现在2005年的google搜索建议

  • ajax优点
    • 增加速度:减轻服务器的负担,按需加载数据,最大程度的减少冗余请求
    • 改善的用户体验:局部刷新页面,减少用户等待时间,带来更好的用户体验
    • 页面和数据分离:前后端分离,操作更灵活,后期维护更方便
  • 后端语言和服务器配置
    • php + Apache + mySQL
    • NodeJS + MongoDB
    • Java + tomcat + Oracle
    • .NET + IIS + SQL Server

json

json数据(json字符串)

1
{"id" : 21465461461461, "name": "张三"},[{"id" : 21465461461461, "name": "张三"}]

json字符串与对象的转换

1
2
3
4
5
6
7
8
//(一)json字符串转成对象的转换
//1. eval("("+json字符串+")");
它的作用是,将一个普通的json格式的字符串,转换成Json格式的对象
//var list = eval("("+request.responseText+")");
//2. JSON.parse(); //把JSON字符串转成JSON对象(js对象/数组)【es5】

//(二)把JSON对象转成JSON字符串
JSON.stringify();

了解json文件存在的意义

1
2
3
4
5
6
7
8
9
//模拟数据(与后端先商量)
[
{
"id":"G001",
"name":"Thermos 膳魔师 Funtainer系列水杯 12盎司(340g) 粉红色",
"imgurl":"images/g1.jpg",
"price":899,
}
]

Ajax请求步骤

创建请求对象,返回一个异步请求对象

var xhr = new XMLHttpRequest();

处理服务器返回数据

1
2
3
4
5
6
xhr.onreadystatechange = function(){
if(xhr.readyState == 4) {
//responseText:保存服务器返回的数据(从服务器返回的数据是“字符串”)。
alert(xhr.responseText);
}
}

设置请求参数,建立与服务器连接

1
xhr.open("get", "http://localhost/api/ajaxtest", true);

向服务器发送请求

xhr.send(null);

案例:演示向goodslist.json请求数据

XMLHttpRequest对象属性方法

open(type,url(同源策略),async(同步、异步))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1.open(type,url,async): 建立与服务器的连接
type:请求的类型,get、post
* 区别?
get请求数据接在url后面,post请求数据通过send方法传递
get传递的数据会比较少
get传递的数据会暴露出来
url:数据请求的地址(API地址),一般由后端开发人员提供
* 当前页面访问地址,API地址两者一定要同域
* 同域(同源策略):协议,域名,端口三者一致
* 报错: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access.
async:是否异步发送请求(true,false),默认为true
* 同步:按步骤顺序执行,前面的代码执行完后,后面的代码才会执行
做完前一件事情, 才能下一件事情(排队)
* 异步:与其他操作同时执行,也叫并发(图片加载,ajax请求,定时器)

send(data)

1
2
3
4
5
6
7
8
9
2.send(data): 向服务器发送请求
data:可选参数,post请求时才生效,表示发请求时传送的数据字符串。
xhr.send('size=20&type=music');
在某些浏览器中,如果不需要通过post请求主体发送数据,则必须传入null
//备注:get请求的数据写在url地址后
request.open("get", "http://localhost/api/getdata.php?type=get&qty=10", true);
setRequestHeader(key,val):设置请求头
//备注:利用请求头设置POST提交数据格式:
xhr.setRequestHeader(‘content-type’,”application/x-www-form-urlencoded”);//open方法后设置

**在请求收到服务器的响应后,响应的数据会自动填充xhr 对象的属性,相关的属性简介如下:

readyState

1
2
3
4
5
6
0 - (未初始化)尚未调用open()方法。
1 - (启动)已经调用open()方法,但尚未调用send()方法。
2 - (发送)send()方法执行完成,但尚未接收到响应。
3 - (接收)已经接收到部分响应数据。
4 - (完成)响应内容解析完成,可以在客户端调用了
只要readyState 属性的值由一个值变成另一个值,都会触发一次readystatechange 事件。必须在调用open()之前指定onreadystatechange事件处理程序才能确保跨浏览器兼容性。

responseText

保存服务器返回的数据(从服务器返回的数据是“字符串”)。

status

1
2
3
4
5
6
7
8
* 响应的HTTP 状态。
200(OK):服务器成功返回了页面
304(Not Modified):数据与服务器相同,不需要重服务器请求(直接使用缓存的数据)
400(Bad Request):语法错误导致服务器不识别
401(Unauthorized):请求需要用户认证
404(Not found):请求地址不存在
500(Internal Server Error):服务器出错或无响应
503(ServiceUnavailable):由于服务器过载或维护导致无法完

php本地数据操作

获取前端数据

1
2
3
$_GET 获取前端用get方式传递过来的数据(url地址后面数据也能被获取)
$_POST 获取前端用get方式传递过来的数据
isset() 判断某个值是否被设置,若不存在返回boolean

文件的读取与写入

fopen(path,mode):打开文件

使用fopen函数打开文件时,你首先需要明确打开文件的模式:
1.将数据写入文件,亦或者读写文件
2.考虑如果文件中已经存在相关数据,你是覆盖原有文件中的数据呢,还是仅仅将新数据添加至文件末尾

文件模式:

  • r 以只读方式打开文件,从文件头开始读
  • r+ 以读写方式打开文件,写入时以追加的方式写入文件
  • w 以写入方式打开文件,从文件头开始写。文件不存在则尝试创建,若存在,则先删除文件中的内容
  • w+ 以读写方式打开文件,从文件头开始读写。文件不存在则尝试创建,若存在,则先删除文件中的内容
  • a 以写入方式打开,从文件末尾开始追加写。如果文件不存在则尝试创建
  • a+ 以读写方式打开,从文件末尾开始追加写入或者读。如果文件不存在则尝试创建。
fread(file,length):读取内容
fwrite(file,json字符串):写入内容
fclose(file):关闭文件,避免资源占用
filesize(path):读取文件字符长度
//1.以读取模式打开文件
$path = './data/weibo.json'
$myfile = fopen($path, 'r');

//2.读取文件内容
$content = fread($myfile, filesize($path));

//3.关闭文件,减少资源占用
fclose($myfile);
1
2
3
4
5
6
echo返回数据
json_encode(); 把数组转成字符串
php5.4+ 使用JSON_UNESCAPED_UNICODE防止中文转义
json_decode(json,assoc); 把字符串转成数组/对象
json:待解码的 json string 格式的字符串
assoc:默认false,返回嵌套对象的多维数组,通过arr->key获取对象键对应的值。当该参数为 true 时,将返回嵌套关联数组的多维数组,通过arr[key]获取关联数组键对应的值。
案例:微博点赞
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
//(html页面)1.发起ajax请求,将weibo.json的内容返回到页面
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if(xhr.readyState === 4 && xhr.status === 200){
var res = JSON.parse(xhr.responseText);
ul.innerHTML = res.map(item=>{
return `<li data-id="${item.id}">
<h4>${item.username}</h4>
<p>${item.content}</p>
<span>${item.comtcnt}</span>
<span class="like">${item.likecnt}</span>
</li>`;
}).join('');
datalist.appendChild(ul);
}
}
xhr.open('get','../data/weibo.json',true);
xhr.send();
//(html页面)2.点赞,获取到当前按钮对用的id,发送ajax请求,get方法通过url拼接参数传送id到后台。
//随后php页面对json数据进行修改,再将当前被改变的对象返回。xhr对象通过responseText进行接收为字符串,转成对象,获取到linkcnt键对应的值,给当前事件源修改innerHTML。
//考虑多次请求,可能存在缓存状态,所以补充若是status为304也为请求成功。
datalist.onclick = function(e){
if(e.target.className === 'like'){
// e.target.innerText = e.target.innerText*1+1;
// 获取当前微博id
let currentLi = e.target.parentNode;
let id = currentLi.dataset.id;
let xhr = new XMLHttpRequest();
let status = [200,304];
xhr.onreadystatechange = function(){
if(xhr.readyState === 4 && status.indexOf(xhr.status)>=0){
let res = JSON.parse(xhr.responseText);
e.target.innerHTML = res.likecnt;
}
}
xhr.open('get','../api/weibo.php?id='+id,true);
xhr.send();
}
}
//(php)
//1.接收前端传过来的id,返回值为字符串
$id = isset($_GET['id']) ? $_GET['id'] : Null;
//2.打开json文件fopen(),读取json文件数据fread()。
$path = '';//相对路径
$file = fopen($path,"r");
$content = fread($file,filesize($path));
fclose($file);
//2.对得到的content内容转成json数组后,遍历里面每个item对象,若id键对应的值等于传入的$id,将该对象的linkcnt++。
$contentArr = json.decode($content);//此处contentArr为对象数组
$res;
foreach($contentArr as $item){
//此处item为一个对象
if($item->id == $id){
$item->linkcnt++;
$res = $item;
}
}
//4.将contentArr转成字符串,重新写入json文件
$file = fopen($path, 'w');
fwrite($file, json_encode($arr,JSON_UNESCAPED_UNICODE));
fclose($file);
//5.将步骤2声明变量res,将改变的item对象用res接收,转成字符串,echo给前端
echo json_encode($res,JSON_UNESCAPED_UNICODE);
补充:eval的使用
1
2
3
4
5
var json = '{"name":"lemon","age":18}'; //标准json字符串
var json = '{"name":"lemon",\'age\':18}';//不标准
eval('('+json+')');//也可以转成对象
eval('1+2');//3
eval('定义函数;执行函数') //函数可以在字符串中执行
讲解:ajax的来历及同源策略、同步异步
案例:用户名验证
username.onblur = function(){
    let _username = username.value;
    let status = [200,304];
    let xhr = new XMLHttpRequest();
    //xhr.onload意思为数据请求完成后,等同于xhr.readystate==4的状态
    xhr.onload = function(){
        if(status.includes(xhr.status)){
            let res = xhr.responseText;//fail,success
            if(res === 'fail'){
                username.nextElementSibling.innerHTML = `${_username}太受欢迎,你走吧`;
            }else{
                username.nextElementSibling.innerHTML = `恭喜你绿了`;
            }
        }
    }
    xhr.open('get','../api/checkname.php?username='+_username,true);
    xhr.send();
}
//php
//1.数组存放已经存在的用户名
$userlist = array('张三','李四','王尼玛','奥巴马','laoxie','lemon');
//2. 获取前端传来的用户名
$username = isset($_GET['username']) ? $_GET['username'] : null;
//3. 判断前端传来的用户名是否已存在数组内
$res = in_array($username, $userlist);
if($res){
    // 用户名已存在,注册失败
    echo "fail";
}else{
    echo "success";
}
案例:分页数据加载
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
//html页面:
var qty = 5;
// 1.发起ajax请求数据,拿到返回的数据,转成对象。
let status = [200,304]
let xhr = new XMLHttpRequest();
xhr.onload = function(){
if(status.includes(xhr.status)){
let res = JSON.parse(xhr.responseText);
// 2.拿到对象的data键对应的值(为json字符串),渲染页面
datalist.innerHTML= res.data.map(function(item){
return `<li>
<h4>${item.name}</h4>
<div>${item.content}</div>
</li>`;
}).join("");
//3.得到总页码数pageNum,parseInt(数据总数res.total/每页数据qty).遍历生成页码
var pageNum = parseInt(res.total/res.qty);
page.innerHTML = "";
for(var i=0;i<pageNum;i++){
var span = document.createElement("span");
span.innerHTML = i+1;
//5.拿到返回的当前页码-1,把当前索引设置高亮
var curidx = res.curpage -1;
if(i == curidx){span.className = "active";}
page.appendChild(span);
}

}
}
xhr.open('get','../api/lemon.php?qty='+qty+'&curpage=1',true);
xhr.send();
// 4.点击分页切换
page.onclick = function(e){
if(e.target.tagName.toLowerCase() === 'span'){
let curpage = e.target.innerText;
xhr.open('get','../api/lemon.php?qty='+qty+'&curpage='+curpage,true);
xhr.send();
}
}
//php:
<?php
$qty = isset($_GET["qty"])? $_GET["qty"] : 5;
$curpage = isset($_GET["curpage"])? $_GET["curpage"] : 1;
//1.拿到json数据,根据每页数量与当前页,裁切对应的数据
$path = '../data/football.json';
$file = fopen($path,'r');
$content = fread($file,filesize($path));
$data = json_decode($content);
$curdate = array_slice($data,$qty*($curpage-1),$qty);
//2.格式化数据,再返回给前端
$res = array(
"total" => count($data),
"data" => $curdate,
"qty" => $qty*1,
"curpage" => $curpage*1,
);
echo json_encode($res,JSON_UNESCAPED_UNICODE);
?>

ajax跨域解决方案

JSONP

  • JSONP 是JSON with padding(填充式JSON 或参数式JSON)的简写。
  • JSONP是一种可以绕过浏览器的安全限制,从不同的域请求数据的方法。
  • JSONP请求不是ajax请求,是利用script标签能加载其他域名的js文件的原理,来实现跨域数据的请求
  • 局限性:
    • 只能为get请求
    • 接口必须有回调函数的执行
演示:使用script标签其他js文件调用本地js的某个函数
1
2
3
4
5
6
7
8
9
10
11
12
//html页面:
<script type="text/javascript">
function sum(n){
console.log(n);
}
</script>
<script type="text/javascript" src="../js/common.js"></script>

//common.js代码
sum(6666);

//此时html页面的控制台打印 6666。
演示:使用script标签其他php文件调用本地js的未知名方法,返回数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//html页面:
<script type="text/javascript">
function sum(data){
console.log(data);
}
function sum2(data){
alert(data);
}
</script>
<script src="../api/lemon.php?callback=函数名"></script>

//php文件:
<?php
$fn = $_GET["callback"];
echo "$fn(数据)";
?>
案例:利用JSONP原理调用百度建议
msg.oninput = function(){
    let _msg = msg.value;
    clearTimeout(timer);
    timer = setTimeout(()=>{
    let script = document.createElement('script');
    script.src='https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/sujson=1&cb=getData&wd='+_msg;
    document.body.appendChild(script);
    },500);
}
window.getData = function(data){
    suggest.innerHTML = data.s.map(item=>{
        return `<li>${item}</li>`
        }).join("");
    }
})
//原理性代码:
//1.script的src中回调函数的传递
script.src='https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/sujson=1&cb=getData&wd='+_msg;
//2.声明全局函数
window.getData = function(data){处理数据}

CORS

CORS是一个W3C标准,全称是”跨域资源共享”(Cross-origin resource sharing),它允许浏览器向跨源服务器发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。

1.Access-Control-Allow-Origin
    header('Access-Control-Allow-Origin: * ');  
该字段是必须的。需要在后端响应头添加词字段,值要么是一个*,表示接受任意域名的请求,要么指定一个域名http://localhost。
2.Access-Control-Allow-Methods
3.Access-Control-Allow-Headers
    header('Access-Control-Allow-Methods:POST');  
    header('Access-Control-Allow-Headers:x-requested-with,content-type'); 
案例:天气预报
//1.直接利用ajax请求得到数据
let xhr = new XMLHttpRequest();
let status = [200,304]
xhr.onload = function(){
    if(status.includes(xhr.status)){
        let res = JSON.parse(xhr.responseText)
        console.log(res.data);
        //拿到数据,可以渲染部分到页面上
        let title = document.createElement('h2');
        title.innerHTML = res.data.city + '天气预报';
        let tips = document.createElement('p');
        tips.innerHTML = res.data.ganmao;
        let ul = document.createElement('ul');
        ul.innerHTML = res.data.forecast.map(item=>{
            return `<li>
                <h4>${item.date}</h4>
                <p>${item.low} / ${item.high}</p>
                <p class="type">${item.type}</p>
            </li>`
        }).join('\n');
        // 清空内容
        weather.innerHTML = '';
        weather.appendChild(title);
        weather.appendChild(tips);
        weather.appendChild(ul);
    }
}
xhr.open('get','http://wthrcdn.etouch.cn/weather_mini?city=广州',true);
xhr.send();
//2.配合文本框实现,得到不同城市的天气预报
city.onblur = function(){
    let _city = city.value.trim();
    if(_city.length === 0){
        return;
    }
    xhr.open('get','http://wthrcdn.etouch.cn/weather_mini?city='+_city,true);
    xhr.send();
}
演示:百度地图

服务器代理

后端不存在跨域问题,所以可以利用后端间接获取其他网站的数据

Promise

Promise是一个构造函数,所谓的Promise对象,就是通过new Promise()实例化得到的对象,用来传递异步操作的消息。它代表了某个未来才会知道结果的事件(通常是一个异步操作),并且这个事件提供统一的 API,可供进一步处理。

Promise 的三种状态

Pending(未完成)可以理解为Promise对象实例创建时候的初始状态
Resolved(成功) 可以理解为成功的状态
Rejected(失败) 可以理解为失败的状态