仰望星空的天台

0%

Ajax 和 JSONP 学习笔记

准备工作

因为我们接下来学习的是如何异步的与后台交换数据 所以我们需要先下载一个服务器软件(帮我们配置好默认的配置信息) 然后将我们的主机模拟成一台服务器,进行接下来的学习

我学习AJAX的时候使用的wampserver 一个法国人开发的一个服务器软件 里面包含了PHP运行环境、Aapache运行环境和MySQL

大家根据自己电脑的位数 自行下载wampserver

使用wampserver 可能会遇到的问题

问题一 : 点击Your Projects的下的文件夹链接, url变成项目名,路径出错,没有了localhost

解决方法:在www目录下找到index.php,然后修改里面查找$projectContents,或直接查看338行代码,修改’http://’为’http://localhost/’即可。

问题二 : firebug报错 Warning: failed to open stream: Invalid argument in Unknown on line

因为不识别中文路径导致的

解决方法:将路径中全部由中文有空格的文件名改成英文和‘ - ’。

小白级使用wampserver

双击打开wampserver 在右下角有运行图标 shootpic

localhost就是你的本机地址 www directory 是本机存放数据的文件夹 你练习的文件都需要存在www这个文件夹里面

更深入的了解wampserver 可以自己百度 这里不再赘余

什么是AJAX?

Ajax (Asynchronous Javascript And XML) 也就是异步的 javascript 和 XML

首先解释一下所谓的异步和同步是什么

在生活中,异步就是行为上的不同步,不协调,比如一个工程分工 A 和 B的工作速度不同,结果工作进度异步 同步就是行为上的相同,比如中央进行利率调整,同步的,股市也会因此涨跌

但是,在计算机中,异步和同步理解起来就和生活中的完全相反

异步 :进程非堵塞的 e.g:

settimeout(consonle.log(1),2000);
console.log(2);

这里settimeout也就是2秒后打印出1 但是consonle.log(2)不会等settimtout2秒打印出1后才执行,它会立即执行,也就是说settimeout这个代码块不会堵塞总体代码的执行进程,之后的代码不会因为它的延时执行而不立即执行

同步 :进程堵塞的 e.g

<script src="jquery.js"></script>
<script>
window.onload = function()
{
	$(function(){});
}
</script>

如果要让 $(function(){}); 这句代码执行。 必须等到这个库<script src="jquery.js"></script> 加载完,不然这个封装的方法就无法使用,这就是所谓的进程堵塞,也就是同步

说白了,生活中的同步异步针对的各个对象之间的联系,而计算机中是针对进程上的联系

最基本的AJAX

直接上代码:


//可以想象成虚拟的打开了一个输入Url的地址框
try
{
	//IE7以上创建AJAX对象
	xhr = new XMLHttpRequest();
}
catch(e)
{
	//兼容IE6 以ActiveXObject插件创建AJAX对象
	xhr = new ActiveXObject('Microsoft.XMLHTTP');
}

//可以想象成向这个虚拟的地址框里输入要访问文件的地址
xhr.open('get(传输的方式 有get & post 两种)','需要加载的文件的文件名','是否异步(true & false)');

//可以想象成回车发送请求
xhr.send();

//服务器接收处理请求,然后返回文件数据
xhr.onreadystatechange = function()
{
	if(xhr.readyState == 4)
	{
		//如果HTTP状态码 等于 200(请求成功) 那么就打印出返回的文件内容
		if( xhr.status == 200)
		{
			alert( xhr.responseText );
		}
		else
		{
			alert( '出错咯 ,Err : ' + xhr.status)
		}
	}
}

========华丽丽的分割线========

如果需要象HTML表单那样,也就是说用POST上传数据,
则需要添加HTTP头声明
然后,在send()中规定你所需要发送的数据

也就是说上面简单的
xhr.open('get','需要加载的文件的文件名','是否异步(true & false)');

xhr.send();

要改成

xhr.open("POST","ajax.php",true);
xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");
xhr.send("name=Owen&password=123456");

对上面的代码做一些解释: xhr.readyState : 是AJAX的状态码,用来表示AJAX运行的各种状态

0 >>(初始化)还没有调用open()方法
1 >>(载入)已调用send()方法,正在发送请求
2 >>(载入完成)send()方法完成,已收到全部响应内容
3 >>(解析)正在解析响应内容
4 >>(完成)响应内容解析完成,可以在客户端调用了

on readystate change事件 : 当AJAX状态改变执行事件, 注意,只要open声明的是异步的 这个事件的代码甚至可以放在新建AJAX对象代码, 之后的任何地方,原因很简单, 因为整个进程是非堵塞的嘛(再次复习同异步的概念)

xhr.status : 服务器(请求资源)的状态 HTTP状态编码

xhr.responseText:返回以文本形式存放的内容(注意: 不论什么格式的内容都会以文本形式返回, 如果编码存在问题 , 可能会导致一些其他问题)

xhr.responseXML :返回XML形式的内容

xhr.setRequestHeader(header,value) 添加HTTP头请求函数 header部分 规定了请求头的名称 value 部分 规定了请求头的值

幂等(idempotent、idempotence)

首先解释一下幕等的概念,幂等是是一个数学或计算机学概念,常见于抽象代数中。但是不要给这么高逼格的词吓到了。其实说白了很简单

比如说 1+3=4 和 1+2+1=4 这里 两个计算结果是相同的 就是所谓的幂等 ,再举个难点的例子 max(x,x) == max(min(x,x),max(x,x)) 这个看起来是不是很复杂?但是细心一看 这两边运算结果不是都一样的么?多步运算和一步结果相同,哈哈 这也叫幂等

copy一下权威内容(更深奥的理解详见百度谷歌) :

幂等有以下几种定义: 对于单目运算,如果一个运算对于在范围内的所有的一个数多次进行该运算所得的结果和进行一次该运算所得的结果是一样的,那么我们就称该运算是幂等的。比如绝对值运算就是一个例子,在实数集中,有abs(a) == abs(abs(a)) 。   

对于双目运算,则要求当参与运算的两个值是等值的情况下,如果满足运算结果与参与运算的两个值相等,则称该运算幂等,如求两个数的最大值的函数,有在在实数集中幂等,即max(x,x) == x。

关于幂等,我们还可以看一下下面这个例子

假设我们网站上需要在线支付的功能,那么我们肯定要写个函数对吧 好,坐了几个小时的冷板凳写出来了,然后调用

bool withdraw(account_id, amount)

bool函数withdraw,返回true或者flase 传递的参数是 用户的账号(account_id) 和 要支付的金额(amount)

然后上线给用户使用啦,结果不到一天,就给老板请到办公室喝茶,说有用户投诉,明明只需要付一次钱,却扣了很多次,有bug,又要苦逼的加班修改了 T_T

结果又花了半天的时间分析,分析来分析去,终于发现了问题所在

idempotence

用户端已经成功输完密码,支付了相应的款项,但是,因为网络原因,服务器已经成功扣款的消息没有及时出现在用户端,结果,用户以为出了什么错误,手残又按无数次刷新,结果导致,多次付费…

好吧既然,问题清楚了,那就想办法解决 于是,想来想去,好了,凌晨1点,终于,想出来了

int create_ticket() 
bool idempotent_withdraw(ticket_id, account_id, amount)

多加了一个int函数create_ticket 返回一个整型的支付效验码 ,idempotent_withdraw多加了一个形参ticket_id。

好,具体步骤是什么呢?哈哈,其实很简单嘛 (满脸狞狰的说)

第一步:
傻乎乎的用户如果需要支付款项,我就让用户端,先执行一下create_ticket产生一个效验码,
然后传到服务器端
如果,又因为网络原因没有成功传输,
没关系,反正效验码又不值钱,
傻乎乎的用户刷新的时候再调用一次create_ticket产生一个新的传过来就是了。
			
好了,如果上一步的过程成功了,
用户端和服务器端就会拥有了一个共同的,也就是我们所谓的幂等的效验码

(因为网路原因导致重复产生效验码所得到的影响和一次步骤就成功产生效验码所得到的影响是相同的,
都是让用户端和服务端得到一个共同的ID)

第二步:
用户端根据得到的效验码,支付款项。
如果又网络错误,没事,再次请求支付款项就行了,
反正没传递到服务器端是不会扣钱的

第三步:
服务器端收到请求,执行扣款,然后,状态标记为已经扣款成功
(注意:时候用户端状态是扣款未成功)

第四步:
服务器端返回扣款成功的消息。
这时候,假设又因为网络原因,消息传输失败了,怎么办?
没关系,反正幂等的效验码在那里。
如果失败了,用户刷新重新支付,
服务器查询效验码,发现状态是已经支付成功,就不会再扣钱,
然后再次返回成功支付的消息,
直到用户端接收成功这个消息并更改状态为支付成功,
这样一个优雅的支付功能就写好了。(终于可以回家睡觉觉了=_=)

idempotence

总结来说就是,幂等即任意多次执行所产生的影响均与一次执行的影响相同。

关于get和post两种传输方式分析、关于幂等性分析

get的传输方式分析

1.以?分割URL和传输数据,参数之间以&相连,


如:www.baidu.com
?name=Owen+Brown&password=xxxxx&verify=%e6%88%91%e7%9a%84%e5%90%8d%e5%ad
%97%e5%8f%ab%e6%ac%a7%e9%98%b3%e6%b9%98%e7%b2%a4
	我们分解一下这串url

www.baidu.com是网站域名
? 之后是GET方式传递的数据
也就是假设我们在www.baidu.com这个网站上填写了三个表单
对应的分别是
	姓名:<input type="text" name=name> => 输入了Owen Brown => url中显示 name=Owen+Brown
	密码:<input type="password" name=password> => 输入了xxxxx => url中显示 password=12345
	验证:<input type="text" name=verify> => 输入了我的名字叫欧阳湘粤 => url中显示 verify=%e6%88%91%e7%9a%84%e5%90%8d%e5
	%ad%97%e5%8f%ab%e6%ac%a7%e9%98%b3%e6%b9%98%e7%b2%a4(Url编码加密)
解释:
如果数据是英文字母/数字,原样发送,
如果是空格,转换为+
如果是中文/其他字符,则直接把字符串用Url编码加密,
其中%XX中的XX为该符号以16进制表示的ASCII

2.因为url栏目有字符上限,所以有关大量数据的传递,一来因为会把数据内容显示在url栏,会有安全性问题,再来有url有字符上限,如果超过上限数据就无法进行传输,所以建议,get应用在小规模数据传递且数据所需安全性不用太高的表单中

以下是网友对一些浏览器地址栏最大字符上限的测试:

Microsoft Internet Explorer (Browser)
IE浏览器对URL的最大限制为2083个字符,如果超过这个数字,提交按钮没有任何反应。在我的测试中,这个数字得到验证。

Firefox (Browser)
写道Bad Request
Your browser sent a request that this server could not understand.
Size of a request header field exceeds server limit.
??

Safari (Browser)
URL最大长度限制为 80,000个字符。
		
Opera (Browser)
URL最大长度限制为190,000个字符。
		
Google (chrome)
url长度一旦超过8182个字符时,出现如下服务器错误:写道Request-URI Too Large
The requested URL's length exceeds the capacity limit for this server.
Apache/2.2.12 (Ubuntu) Server at 127.0.1.1 Port 80??Apache (Server)
能接受最大url长度为8,192个字符,但我的测试数据是8,182,10个字符,差别不在,数据具体符合。
			
Microsoft Internet Information Server(IIS)
能接受最大url的长度为16,384个字符。
			
实际上,URL不存在参数上限的问题,HTTP协议规范没有对URL长度进行限制。
这个限制是特定的浏览器及服务器对它的限制,之所以对地址栏长度进行限制,
一来url过长,会对服务器处理产生负担,如果有人恶意地构造了几个几M大小的URL,并不停地访问你的服务器。
服务器的最大并发数显然会下降,介于安全和稳定的考虑,故而给了url加了限制。

3.get存在Web缓存问题,

什么是Web缓存呢? 这是浏览器的一种提升访问效率的机制 当我们多次浏览同一个页面, 或者在同一个页面多次请求的时候 浏览器就会将你的url保存在本地的缓存文件夹中 这样当你再次访问这个页面的时候就不用访问DNS服务器来解析域名了 直接能访问到你想访问的网站

但是我们知道AJAX的操作对象是在同一张页面内 使用GET方式传输的URL地址肯定是相同的, 这样Web缓存就会出麻烦

现在我们来举个例子:


比如我们现在要登录账户
GET发送请求
numerhero.github.io/?username=Owen&pw=123
当我们访问一次之后和后端交互了一次之后,成功了,得到了我们想要的数据
然后缓存文件夹就会记录下numerhero.github.io/?username=Owen&pw=123交互成功之后的内容
以便以后再次访问

但是,如果下次我们使用GET方法登录账户
numerhero.github.io/?username=Owen&pw=123
浏览器就会从本地的缓存文件夹中调取老数据
这样,就无法得到我们想要的新数据

=== 解决方案 ===

我们可以在请求的后面添加一个 随机数  时间戳 来保证每次请求的地址不相同
这样浏览器看到每次请求的地址和老地址不相同,
就不会访问本地缓存文件而是访问远端服务器了

"numerhero.github.io/?username=Owen&pw=123&" + new Date().getTime() 
(注意:请求内容和时间戳或随机数之间需要有一个&来连接) 

每请求一次 都是毫秒级的变化,这样就不可能出现缓存问题了
(当然也可以用随机数目,但是随机数有重复的可能,而时间戳基本不可能重复)

4.中文 以及其他文字的编码问题 我们页面使用的一般是unicode编码 和 utf-8编码 但是我们用于传输的地址自己有一套特殊的编码机制 也就是url编码

所以,如果我们要用GET方式传递像 numerhero.github.io/?username=欧阳湘粤&pw=123 的数据,就需要对齐进行编码 而非直接用 utf-8 中文编码 encodeURL()方法把中文编码转化成URL编码 “numerhero.github.io/?username=” + encodeURL(欧阳湘粤) + “&pw=123”

这样就能解决乱码问题

get的安全安全性

get相对与post方法对于服务器而言是较安全的 这里所谓的安全不是上文所指的安全,这里的安全是对于服务器而言的,而上文的安全是对用户而言的。

这里的安全意思是操作用于获取信息而非修改信息。换句话说,GET请求一般不应产生副作用。就是说,它仅仅是获取资源信息,就像数据库查询一样,不会修改,增加数据,不会影响资源的状态。

get的幂等性

GET方法用于获取资源,不应有副作用,所以是幂等的。 比如:GET http://www.bank.com/account/123456, 不会改变资源的状态,不论调用一次还是N次都没有副作用(影响)。 请注意,这里强调的是一次和N次具有相同的副作用(影响), 而不是每次GET的结果相同。

比如:GET http://www.news.com/latest-news 这个HTTP请求可能会每次得到不同的结果, 但它本身并没有产生任何副作用, 因而是满足幂等性的。

post的传输方式分析

1.在url地址栏上不显示传输的内容

2.理论上来说对数据的大小没有传输的限制, 但HTTP规范也没有对其进行限制,起限制作用的是服务器的处理能力。

3.post无法从地址栏直接传递数据 “numerhero.github.io/?username=Owen&pw=123&” + new Date().getTime() 像GET方式的传递方式,在POST方式上不适用,后端收不到这些数据

如果需要从前端向后端提交数据,可以在send()里面写

xhr.send(username=Owen&pw=123)

4.请求头声明 使用post方法需要声明请求头 也就是要添加一句 xhr.setRequestHeader(“Content-type”,”application/x-www-form-urlencoded”); 大家可能不知道是什么意思

我翻译一下 xhr对象底下头部请求信息(“请求文件的格式” , “文件的编码”) 因为服务端 对 客户端 可以说是一无所知(毕竟是电脑嘛) 所以我们需要给传输的数据加个备注信息 也就是这个所谓的请求头声明

关于请求文件的格式 和 文件的编码 具体可以百度 这里不过多讨论

5.post方式没有中文乱码问题 这是当然嘛,因为请求头信息中有文件的编码 application/x-www-form-urlencoded 这里不就已经告诉服务器端 要用urlencoded 这个URL编码 来读取数据吗?自然就没有乱码的问题了 (也就是说你可以直接使用中文发送, 而不用像GET方式那样将中文用encodeURL()括起来)

6.post不存在缓存问题 使用post发送的数据不会被缓存文件夹保存

post方法的安全性

安全性能比get要高(对于用户来说)
但是对于服务器来说比get
(1)登录页面有可能被浏览器缓存
(2)其他人查看浏览器的历史纪录,那么别人就可以拿到你的账号和密码了,
除此之外,使用GET提交数据还可能会造成Cross-site request forgery攻击

post的幂等性

post请求不幂等 POST所对应的URI并非创建的资源本身,而是资源的接收者。

比如:POST http://www.forum.com/articles的语义 是在http://www.forum.com/articles下创建一篇帖子,

HTTP响应中应包含帖子的创建状态以及帖子的URI。 两次相同的POST请求会在服务器端创建两份资源,它们具有不同的URI;

总结一下

  幂等性 对于用户安全性 对于服务器安全性 适用于
post 修改数据
get 提取数据

所以get多用于从服务器上获取数据,post多用于从服务器上发送数据 但是,GET和POST只是发送机制不同,并不是一个取一个发!(GET和POST都能取和发)

JSON对象 和 AJAX 结合

浏览器(IE7以上和标准浏览器支持JSON对象 和 他们底下的一些方法) 如果要兼容IE6 可以从外部导入 JSON.js

关于JSON具体的研究分析,请看本站JSON对象学习笔记

封装AJAX函数

这里把这个AJAX函数文件名命名为 Ajax.js

AJax实例:关于简单的瀑布流实现

下面介绍一种简单的瀑布流实现

具体思路很简单, 就是设置n个li标签(n为瀑布流的列数) 这n个li的width固定,但高随着图片的加载auto 每次加载的时候,遍历这n个li 选择offsetTop最短的那个li加载图片


=== html ===
<ul id="ul1">
		<li></li>
		<li></li>
		<li></li>
		<li></li>
</ul>

=== css ===
body{margin: 0;padding: 0;}
#ul1{width:1080px; margin:100px auto 0;}
#ul1 li {width:242px; list-style: none; float: left; margin-right:10px;}
#ul1 div { border: 1px solid #000; padding: 10px; float: left;}
#ul1 div p{ width: 220px;}
#ul1 div img{ width:220px; display: block; }

=== Javascript ===

window.onload = function()
{
	var
	oUl = $('ul1'),
	aLi = $TagName(oUl , 'li'),
	iLen = aLi.length;
	iPage = 1;
	switched = true;

	getList();

	function getList()
	{
		ajax( 'get' , 'cpage=' + iPage , 'getPics.php' , function( data )
		{		
			var data = JSON.parse(data);
			if( !data.length )
			{
				// 如果已经没有数据了
				switched = true;
				return;
			}
			for( var i = 0 ; i < data.length; i++ )
			{
				//_index 为获取的最小高度的li
				var
				_index =  getShortHeight( aLi , iLen ),
				oDiv = document.createElement('div'),
				oImg = document.createElement('img'),
				oP = document.createElement('p');
				
				//数据JSON底下preview对象和title对象
				oImg.src = data[i].preview;
				oImg.style.width = '220px'; 
				oImg.style.height = data[i].height * (220 / data[i].width) + 'px';
				oP.innerHTML = data[i].title;
		
				oDiv.appendChild( oImg );
				oDiv.appendChild( oP );
				aLi[_index].appendChild( oDiv );
			}
			//当加载完成后重新启用开关,保证下一次加载
			switched = true;
		}
		,function()
		{
			alert('错误!');
		});
	}

	window.onscroll = function()
	{
		var _index = getShortHeight( aLi , iLen );
		var oLi = aLi[_index];
		var scrollTop = document.documentElement.scrollTop || document.body.scrollTop
		
		if( getTop( oLi ) + oLi.offsetHeight < document.documentElement.clientHeight + scrollTop)
		{
			if(switched)
			{
				switched = false;
				iPage++;
				getList();
			}
			
		}
	}

	function getTop(obj)
	{
		var iTop = 0;
		while(obj)
		{
			iTop += obj.offsetTop;
			obj = obj.offsetParent;
		}
		return iTop;
	}

}

function getShortHeight( aLi , iLen )
{
	var
	index = 0,
	ih = aLi[index].offsetHeight;

	for(var i = 0 ; i<iLen ;i++ )
	{
		if( aLi[i].offsetHeight < ih)
		{
			index = i;
			ih = aLi[i].offsetHeight;
		}
	}
	return index;
}


function $(d){return document.getElementById(d);}
function $TagName(obj , tag){return obj.getElementsByTagName(tag);}

配套的PHP文件

=== 华丽丽的分割线 ===

这里使用了www.wookmark.com网站的数据源 当然你可以使用自己提供的数据源 如果有自己的数据源可以无视这部分内容 不过JS中,对数据的引用可能就不一样 需要视自己的情况更改

=== 华丽丽的分割线 ===

这里主要详细解javascript原码

一共4个函数 getList() //主函数,使用ajax来获取内容 window.scroll //实现滑动到最顶端后,加载内容 getTop() //遍历对象和其父级的offsetHeight 返回最终和 getShortHeight() //遍历数组每个元素 , 返回最小元素值的索引

还有一些变量

iPage //表示调用的数据源的页数(如果数据源不同,视自己情况改) switched //开关变量 用来控制加载

主函数调用了上文封装的ajax.js 并且传入两个匿名函数success 和 faild函数 success函数使用DOM操作将加载的数据内容 选择当前高度最小的li标签 并 将内容加入进去

window.scroll函数判断总页面(scrollTop + clientHeight)高度 是否已经超过当前最小的li标签高度 如果超过了就继续加载新的内容

getTop 工具函数 用来求取精确的offsetTop值

getShortHeight 工具函数 用来求得最小高度的li的索引

AJAX的跨域请求问题

题目看起来好像可能很高大上 其实理解起来一点都不复杂

首先理解一下什么是跨域请求 说白了就是使用别人家的数据库API 更直接点就是跑到别人家里借东西然后拿到自己家用

比如我这里 想要借用豆瓣网 他家的数据

api.douban.com/book/subjects?q=HTML5&alt=json

用GET方式传递 q参数传递的是查找关键字 alt是声明传递得到的数据格式(默认是JSON)

然后就调用AJAX代码


ajax( 'get' , 'q=HTML5&alt=json' , 'api.douban.com/book/subjects' , function( data )
{
	...
});

运行之 发现浏览器报了错

XMLHttpRequest cannot load http://api.douban.com/book/subjects ‘Access-Control-Allow-Origin’ header is present on the requested resource …..

Access-Control-Allow-Origin 输入源控制许可

说白了就是,你自己的门啦

你从别人家里借东西过来, 别人同意借你了 但是你家的门却不让你把借的东西拿进家里来

现实中听起来确实很奇葩, 但是这确实在网络世界中是实实在在存在的一道门

当然这有什么方法可以解决呢?

这里引用一下最先提出者的博客

Jsonp提出者的博客

由上得到以下三种解决方案

第一种 使用Flash在XML设置 输入源控制许可 可以解决 但是我们这里不准备详细讨论这种方法

第二种 在服务器端 执行请求 也就是在本域,也就是在本目录下的后端脚本 中请求方法跨域资源

俗话说 前面不行咱可以走后门嘛, 但是今天我们也不准备详细讨论这种方法

第三种 JSON with Padding

俗称JSONP 这才是我们今天的主角

JSONP


JSONP的主要原理是 <script> 脚本标签
因为使用<script> src 属性
我们就不会遇到跨域问题

这里所谓Padding相信大家都会有印象,
这不就是内边距吗?

没错,但是在这里我们换一个角度去理解

也就是"内填充"(其实内边距的实质也就是内填充了空间)

也就是说我们需要在js里面使用DOM操作
去动态的内填充跨域请求脚本

var oScript = document.createElement('script');
oScript.src = '你需要调用的脚本的url' + date.getTime();
document.body.appendChild(oScript);

而这里得到的脚本需要填充在一个回调函数里面
便于我们使用
所以,这里我们就需要和后端同学配合好

这里我们列举一个百度的例子

var oScript = document.createElement('script');
oScript.src = 'http://suggestion.baidu.com/su?wd='+ 要搜索的关键字字符串 + '&cb=回调函数名称' + '&t=' + date.getTime();
document.body.appendChild(oScript);

这里我们调用了百度的一个检索的数据库
'suggestion.baidu.com/su?wd='+ 要搜索的关键字字符串 + '&cb=回调函数名称' + '&t=' + date.getTime();

参数包括一个wd  cb 还有一个时间戳
wd就是你需要搜索的内容
(这里的内容不用转为URI 因为百度已经做好处理了)

重点在于回调函数上
比如我们这么写请求

'http://suggestion.baidu.com/su?wd=javascript&cb=CallBackfunc'
(注意不包含单引号)

百度的数据库就会返回这么一串JSON
CallBackfunc({q:"javascript",p:false,s:["javascript教程","javascript视频教程","javascript权威指南","javascript:void(0);","javascript 数组","javascript date","javascript array","javascript 正则表达式","javascript replace","javascript split"]});

这里注意看,我申请了一个回调函数 CallBackfunc
于是,百度后端就把数据内填充CallBackfunc()参数里啦

所以为什么叫JSONP 就是如此

如果我们没有申请回调函数
那么百度就会返回一串JSON

{q:"javascript",p:false,s:["javascript教程","javascript视频教程","javascript权威指南","javascript:void(0);","javascript 数组","javascript date","javascript array","javascript 正则表达式","javascript replace","javascript split"]}

这样我们就会处在一个蛋疼的境界:
JSON数据
但是没有办法调用...

所以需要动态的将数据以传参形式
内填充入一个函数
这样才能方便我们调用

当然有了数据之后

我们只要定义一个函数
function CallBackfunc( data )
{
	...
}
然后再这个函数里面就能够进行各种DOM操作
来使用这些数据了

AJAX 和 JSONP的对比

    数据传递方式   是否异步   数据使用方式   是否跨域
AJAX   GET和post都行     后端以文本的形式传入,需要使用JSON对象解析   不能直接跨域,可以通过其他方式跨域
JSONP   只能是GET方式     后端直接以内填充的方式传递JSON数据,可直接使用   可以直接跨域

Summery

简单来说 AJAX 可以理解为一个函数一个API 调用这个函数从后端获得数据 再通过前端技术利用并更好的展现数据

JSONP 是一个非官方的为解决 AJAX无法实现跨域传输的解决方案

但是JSONP不能完全替代AJAX 因为只能使用GET方式的缺陷 会有安全隐患

XMLHttpResquest 的缺陷 1.只支持文本数据的传送,无法用来读取和上传二进制文件。 2.传送和接收数据时,没有进度信息,只能提示有没有完成。 3.受到”同域限制”(Same Origin Policy),只能向同一域名的服务器请求数据。(使用jsonp 兼容)

补充

现在的标准浏览器已经可以支持Ajax的跨域请求了 也就是说 当你 调用 Ajax( ‘get’ , “” , “http://www.b.com/getPics.php” , function(){ … });的时候

只需要在php页面中添加一句 header('Access-Control-Allow-Origin-:http://www.a.com');

那么a域下就可以使用Ajax调用b域下的getPics文件 或者如果没有什么重要文件的话还可以

header('Access-Control-Allow-Origin-:*');

那么所有的网站都能调用b域下的文件了

还要注意一点的是关于 XMLHttpResquest() level 2 已经不推荐我们使用 onreadystatechange 来监听了 (但是还能用)

w3c档案

新增添了如下这些事件

onloadstart 当请求开始的时候触发 onload 当请求完成并且成功获取到数据 和 readystate == 4 和 xhr.state == 200 的情况是一样的 onloadend 发送请并且回应了,但不代表回应的就是正确的东西 ontimeout 请求超时的时候触发 onerror 请求错误的时候触发 onabort 当请求取消的时候触发

IE的兼容

虽然现在的标准浏览器已经可以支持Ajax的跨域了,但是IE却不支持

它是自己弄了这么个XDomainRequest(); (IE 7+都支持)

英文原档

其中send() 和 open() 方法都还在

但是已经把onreadystatechange()取消了

改成下面这些事件 onerror 上同 onload 上同 onprogress 正在加载的时候 ontimeout 上同

总结一下

标准浏览器: 同域使用XMLHttpResquest 跨域 可以使用jsonp 和 XMLHttpResquest (两种方法都需要后端配合)

IE7+ : 跨域 使用XDomainRequest()或jsonp(需要后端配合) 同域 使用XMLHttpResquest

IE6 : 同域使用 ActiveXObject() 跨域使用jsonp

使用ajax无刷新上传

我们以前如果需要上传数据需要刷新


<form method = "post" action = "提交到的后端脚本的路径(不是上传文件保存的路径)" enctype="multipart/form-data">
<input type="file" name="一定要和后端约定好保持相同">
<input type="submit" value="上传">
</form>

但是现在我们可以用Ajax异步的上传

=== html ===
<input type="file"  id="myiframe" >
<input type="button" id="btn" value="上传">
<div id="div1">
	<div id="div2"></div>
	<div id="div3">0%</div>
</div>

=== css ===
#div1{ width:300px; height:30px; bordr:1px soild #000; position:relative; }
#div2{ width:0px; height:30px; background:#CCC; }
#div3{ width:300px; height:30px; position:absolute; line-height:30px; text-align:center; top:0; left:0;}

=== JavaScript ===
window.onload = function()
{
	var omyIframe = document.querySelector("#myiframe");
	var oBtn = document.querySelector("#btn");
	var oDiv1 = document.querySelector("#div1");
	var oDiv2 = document.querySelector("#div2");
	var oDiv3 = document.querySelector("#div3");
	
	oBtn.onclick = function()
	{
		try
		{
			var xhr = new XMLHttpResquest();
		}catch()
		{
			var xhr = new XDomainRequest();
		}
		
		xhr.open( 'open' , '后端脚本文件路径' , true );
		
		// 首先当你浏览本地目录上传文件的时候,表单将数据
		// 保存在 omyIframe.files 这个属性里面
		// 而omyIframe.value 是这个上传文件的文件名称
		console.log( omyIframe.value );
		console.log( omyIframe.files );
		
		// 当我们需要上传文件的时候不能像之前用post传数据那样 xhr.send( 'file=' + omyIframe.files[0] );
		// 这样传递的并不是个文件,而是一个[object File]的一个字符串
		// 解决方法是将这文件对象转化为二进制文件,在把二进制文件对象发送过去

		xhr.setRequestHeader( 'X-Resquest-With' , 'XMLHttpResquest' );

		// 声明一个二进制数据转化对象
		var oFormData = new FormData();
		
		// 调用append方法 append( '和后端约定好的文件名' , 文件对象 );
		oFormData.append('file' , omyIframe.files[0] );

		// 发送 
		xhr.send(oFormData);

		// 当文件传输成功后后端返回信息
		xhr.onload = function()
		{
			var data = JSON.parse(this.responseText);

			alert( data.msg ) // 返回上传成功的信息
		}

		var oUpload = xhr.upload;

		// 监控上传进度事件
		// 包含ev.total(总共要发送的量) 和 ev.loaded(已经发送的量)
		// 我们可以通过他们的比例来 制作进度条
		oUpload.onprogress = function(ev)
		{
			console.log( ev.total + " : " + ev.loaded );
			var iScale = ev.loaded / ev.total;
			
			// 加载过程中动态改变进度条的div
			oDiv2.style.width = 300 * iScale + "px";
			// 值动态修改
			oDiv3.innerHTML = iScale*100 + "%";
		}
	}
}

感谢

妙味课堂

关于幂等性的理解

百度知道关于url长度的实验

引用了陈曦明前辈的一些话

引用了yibuyisheng前辈的一些话

JSONP 引自 bob.ippoli.to

itwriter XMLHttpRequest Level 2 使用指南

引用书籍

《javascript高级程序设计》

Table of Contents