Javascript XMLHttpRequest网络请求
XMLHttpRequest 是一个 JavaScript 对象,它最初由微软设计,随后被 Mozilla、Apple 和 Google采纳. 如今,该对象已经被 W3C组织标准化. 通过它,你可以很容易的取回一个URL上的资源数据. 尽管名字里有XML, 但 XMLHttpRequest 可以取回所有类型的数据资源,并不局限于XML。 而且除了HTTP ,它还支持file 和 ftp 协议
XMLHttpRequest 让发送一个HTTP请求变得非常容易。你只需要简单的创建一个请求对象实例,打开一个URL,然后发送这个请求。当传输完毕后,结果的HTTP状态以及返回的响应内容也可以从请求对象中获取。XMLHttpRequest网络请求的一般步骤:
第一步:创建一个 XMLHttpRequest 实例
new XMLHttpRequest();
第二步:初始化HTTP请求参数
void open(
DOMString method,
DOMString url,
optional boolean async,
optional DOMString user,
optional DOMString password
);
method:请求所使用的HTTP方法; 例如 "GET", "POST", "PUT", "DELETE"等. 如果下个参数是非HTTP(S)的URL,则忽略该参数.
url:该请求所要访问的URL
async:一个可选的布尔值参数,默认为true,意味着是否执行异步操作,如果值为false,则send()方法不会返回任何东西,直到接受到了服务器的返回数据。如果为值为true,一个对开发者透明的通知会发送到相关的事件监听者。这个值必须是true,如果multipart属性是true,否则将会出现一个意外。
user:用户名,可选参数,为授权使用;默认参数为空string.
password:密码,可选参数,为授权使用;默认参数为空string.
第三步:发送请求
send();
发送请求. 如果该请求是异步模式(默认),该方法会立刻返回. 相反,如果请求是同步模式,则直到请求的响应完全接受以后,该方法才会返回.
如下例:
var xhr = new XMLHttpRequest();
xhr.onload = function () {
console.log(this.responseText);
};
xhr.onreadystatechange = function() {
console.log(this.readyState);
};
xhr.open("get", "https://www.baidu.com", true);
xhr.send();
我们在hbuilder里面打开,控制台会报错:
[Web浏览器] "XMLHttpRequest cannot load https://www.baidu.com/. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://127.0.0.1:8020' is therefore not allowed access."
用浏览器打开,按F12在控制台console下查看也会发现上述错误,这是为啥呢?
这是因为普通网页能够使用XMLHttpRequest对象发送或者接受服务器数据, 但是它们受限于同源策略。只要先获取了跨域请求许可,就可以进行跨域请求。
同源策略:如果两个页面的协议、域名和端口是完全相同的,那么它们就是同源的。同源策略是为了防止从一个地址加载的文档或脚本访问或者设置从另外一个地址加载的文档的属性。如果两个页面的主域名相同,则还可以通过设置 document.domain属性将它们认为是同源的。
我们用hbuilder新建一个app工程,然后运行下面的代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title></title>
</head>
<body>
<script type="text/javascript">
document.addEventListener('plusready', function(){
var xhr = new plus.net.XMLHttpRequest();
xhr.onreadystatechange = function () {
switch ( xhr.readyState ) {
case 0:
console.log( "xhr请求已初始化" );
break;
case 1:
console.log( "xhr请求已打开" );
break;
case 2:
console.log( "xhr请求已发送" );
break;
case 3:
console.log( "xhr请求已响应");
break;
case 4:
if ( xhr.status == 200 ) {
alert( "xhr请求成功:"+xhr.responseText );
} else {
console.log( "xhr请求失败:"+xhr.readyState );
}
break;
default :
break;
}
}
xhr.open("GET", "https://www.baidu.com/");
xhr.send();
}, false );
</script>
</body>
</html>
我们会发现最后弹出了弹框,里面写有百度首页的代码。
XMLHttpRequest对象:
创建一个XMLHttpRequest 对象,对象创建时不触发任何时间和网络请求,需和open,send方法配合使用。
var xhr = new plus.net.XMLHttpRequest();
xhr.open(method, url);
xhr.send();
XMLHttpRequest的属性:
- readyState: HTTP 请求的状态
- response: 请求从服务器接收到的响应数据
- responseText: 请求从服务器接收到的响应数据(字符串数据)
- responseType:请求响应数据response的类型
- responseXML: 请求响应的Document对象
- status: 服务器返回的HTTP状态代码
- statusText: 服务器返回的HTTP状态描述
- timeout: 请求服务器的超时时间,单位为毫秒(ms)
- withCredentials: 是否支持跨域请求
XMLHttpRequest的方法:
- abort: 取消当前响应,关闭连接并且结束任何未决的网络活动
- getAllResponseHeaders: 获取HTTP响应头部信息
- getResponseHeader: 获取指定的HTTP响应头部的值
- open: 初始化HTTP请求参数,例如URL和HTTP方法,但是并不发送请求
- send: 发送HTTP请求
- setRequestHeader: 指定一个HTTP请求的Header
XMLHttpRequest的事件:
- onreadystatechange: 网络请求状态发生变化事件
- onloadstart: 网络请求开始事件
- onprogress: 网络请求传输数据事件
- onabort: 网络请求取消事件
- onerror: 网络请求错误事件
- onload: 网络请求成功事件
- ontimeout: 网络请求超时事件
- onloadend: 网络请求结束事件
mui ajax
mui框架基于htm5plus的XMLHttpRequest,封装了常用的Ajax函数,支持GET、POST请求方式,支持返回json、xml、html、text、script数据类型; 本着极简的设计原则,mui提供了mui.ajax方法,并在mui.ajax方法基础上,进一步简化出最常用的mui.get()、mui.getJSON()、mui.post()三个方法。
mui.ajax( url [,settings] )
- url:请求发送的目标地址
- settings:key/value格式的json对象,用来配置ajax请求参数,支持的参数如下:
- data:发送到服务器的业务数据;
- type:请求方式,目前仅支持’GET’和’POST’,默认为’GET’方式;
- dataType:预期服务器返回的数据类型;如果不指定,mui将自动根据HTTP包的MIME头信息自动判断;
支持设置的dataType可选值:- ”xml”: 返回XML文档
- “html”: 返回纯文本HTML信息;
- “script”: 返回纯文本JavaScript代码
- “json”: 返回JSON数据
- “text”: 返回纯文本字符串
- success:Type: Functon(Anything data,String textStatus,XMLHttpRequest xhr)
请求成功时触发的回调函数,该函数接收三个参数:- data:服务器返回的响应数据,类型可以是json对象、xml对象、字符串等;
- textStatus:状态描述,默认值为’success’
- xhr:xhr实例对象
- error:Type: Functon(XMLHttpRequest xhr,String type,String errorThrown)请求失败时触发的回调函数;
该函数接收三个参数:- xhr:xhr实例对象
- type:错误描述,可取值:”timeout”, “error”, “abort”, “parsererror”、”null”
- errorThrown:可捕获的异常对象
- timeout:Type: Number,请求超时时间(毫秒),默认值为0,表示永不超时;若超过设置的超时时间(非0的情况),依然未收到服务器响应,则触发error回调;
- headers:Type: Object,格式为:{'Content-Type':'application/json'},
基本格式如下:
mui.ajax(url,{
data:{
username:'username',
password:'password'
},
dataType:'json',//服务器返回json格式数据
type:'post',//HTTP请求类型
timeout:10000,//超时时间设置为10秒;
success:function(data){
//服务器返回响应,根据响应结果,分析是否登录成功;
...
},
error:function(xhr,type,errorThrown){
//异常处理;
console.log(type);
}
});
mui.post( url [,data] [,success] [,dataType] )
mui.post()方法是对mui.ajax()的一个简化方法,直接使用POST请求方式向服务器发送数据、且不处理timeout和异常(若需处理异常及超时,请使用mui.ajax()方法)
mui.post('http://server-name/login.php',{
username:'username',
password:'password'
},function(data){
//服务器返回响应,根据响应结果,分析是否登录成功;
...
},'json'
);
mui.get( url [,data] [,success] [,dataType] )
mui.get()方法和mui.post()方法类似,只不过是直接使用GET请求方式向服务器发送数据、且不处理timeout和异常(若需处理异常及超时,请使用mui.ajax()方法)。如下为获得某服务器新闻列表的代码片段,服务器以json格式返回数据列表:
mui.get('http://server-name/list.php',
{category:'news'},
function(data){
//获得服务器响应
...
},'json'
);
mui.get( url [,data] [,success] )
mui.getJSON()方法是在mui.get()方法基础上的更进一步简化,限定返回json格式的数据,其它参数和mui.get()方法一致,如上获得新闻列表的代码换成mui.getJSON()方法后,更为简洁,如下:
mui.getJSON('http://server-name/list.php',
{category:'news'},
function(data){
//获得服务器响应
...
}
);
我们接着上一篇文章的项目开始进行本节的内容,上一篇我们讲解了html5+ webview的使用方法,并且实现了一个基于父子webview的tab bar切换的案例,我们这次利用网易音乐API接口请求数据,完成我们后续的音乐播放器功能。
API分析:
网易音乐搜索API:
- url:http://s.music.163.com/search/get/
- type:”GET”或“POST” //HTTP请求类型
- 请求参数:
- type: 1
- s: //关键词
- limit: 10 //限制返回结果数为10
- offset: 0 //偏移
- src: lofter //可为空
- filterDj: true | false //可为空
- callback: //为空时返回json,反之返回jsonp callback
由于HTTP请求类型包含get,我们只需要把请求的参数拼接到url里面就可以得到返回的数据,格式为url?key1=value1&key2=value2。
在这个例子中我们打开这个网址http://s.music.163.com/search/get/?type=1&limit=5&s=喜欢你,我们会发现一些数据返回了,如下图:
OK,我们下面讲解怎么通过程序得到这些数据,并且我们并且怎么解析这些数据。
在开始之前我们先对我们要得到的数据进行美化,这里我在网上随便搜了一个
在线JSON校验格式化工具,我们只需要把刚刚那些数据复制粘贴到我给的这个工具里面,然后点击校验就变整齐了。
{
"result": {
"songCount": 3224,
"songs": [
{
"id": 28949444,
"name": "喜欢你",
"artists": [
{
"id": 7763,
"name": "G.E.M.邓紫棋",
"picUrl": null
}
],
"album": {
"id": 2956076,
"name": "喜欢你",
"artist": {
"id": 0,
"name": "",
"picUrl": null
},
"picUrl": "http://p1.music.126.net/u_1EudmF8Swgow6vfgYe1g==/8896148580676276.jpg"
},
"audio": "http://m2.music.126.net/_icR1apQHVl8wa0EP_REkQ==/3269947581061892.mp3",
"djProgramId": 0
}
]
},
"code": 200
}
我们可以看到
"picUrl":"http://p1.music.126.net/u_1EudmF8Swgow6vfgYe1g==/8896148580676276.jpg"
打开这个图片地址我们就可以看到邓紫棋了,哈哈,也就是说我们只需要抓取到这个地址就可以进行下面的工作咯,其他的类似。
mui.ajax请求实例
我们在上次新建的M-BOX下的home.html下写这个例子:
<script src="../js/mui.min.js"></script>
<script type="text/javascript">
var url = "http://s.music.163.com/search/get/";
mui.ajax(url,{
data: {
'type': 1,
's': "喜欢你",
'limit': 10
},
dataType:'json',//服务器返回json格式数据
type:'post',//HTTP请求类型
timeout:10000,//超时时间设置为10秒;
success:function(data){
console.log(JSON.stringify(data));
},
error:function(xhr,type,errorThrown){
//异常处理;
console.log(type);
}
});
</script>
hbuilder很方便的一个功能就是真机调试,我们直接将电脑与手机连接,通过console.log() 函数可以很方便的将数据在控制台打印出来,例如上述例子如果我们在success的回调函数中执行console.log(data);,在控制台会输出[object Object],我们只需要利用JSON.stringify() 方法可以将任意的JavaScript 值序列化成 JSON 字符串。
JSON:JavaScript Object Notation(JavaScript 对象表示法),JSON 是存储和交换文本信息的语法,独立于语言。类似 XML。JSON 比 XML 更小、更快,更易解析,具有自我描述性,更易理解的特点。
JSON就是一串字符串,只不过元素会使用特定的符号标注。
- {} 双括号表示对象
- [] 中括号表示数组
- “” 双引号内是属性或值
- :表示后者是前者的值(这个值可以是字符串、数字、也可以是另一个数组或对象)
{"name": "Dcloud"}可以理解为是一个包含name为Dcloud的对象;
[{"name": "mui"},{"name": "html5+"}]就表示包含两个对象的数组。
通过JavaScript,您可以创建一个对象数组,并像这样进行赋值:
var employees = [
{ "firstName":"John" , "lastName":"Doe" },
{ "firstName":"Anna" , "lastName":"Smith" },
{ "firstName":"Peter" , "lastName": "Jones" }
];
可以像这样访问 JavaScript 对象数组中的第一项:
employees[0].lastName;
返回的内容是:
Doe
可以像这样修改数据:
employees[0].firstName = "Jonatan";
JSON.stringify和JSON.parse
这里需要特别说明的是两个非常有用的方法:JSON.stringify和JSON.parse。我们可以通过JSON.stringify将json对象转成json字符串,方便用alert,console.log打印出来,可以方便的查看json对象内容。反之我们可以通过JSON.parse将json字符串转成json对象以便可以方便对json对象取值。例:
// 场景1
var data = {
name: 'zhaomenghuan',
age: 22
}
console.log(typeof data); // "object"
console.log(JSON.stringify(data)) // "{"name":"zhaomenghuan","age":22}"
// 场景2
var str = '{"name":"zhaomenghuan","age":22}';
console.log(typeof str) // "string"
console.log(JSON.parse(str).name) // "zhaomenghuan"
上面的例子列举了两种应用场景,大家根据自己的情况合适的选取正确的方法。
运算符[ ] 和 .
[ ]可以用于数组和对象取值,数组中按下边取值。
var array=["one","two","three","four"];
array[0];
对象属性取值时,当我们不知道属性名或属性名本身包含点(.)的键,应当使用[ ]。
for(var key in obj){
console.log(key + ":" + obj[key]);
}
var obj={
id:"obj",
"self.ref":ref
}
console.log(obj["self.ref"]);
.运算符的左边为一个对象,右边为属性名。如:obj.id
可以动态访问属性:
var key="property";
console.log(key);
var obj={
property:"hello word"
}
console.log(obj[key]);
数组的map方法:
var array=[1,2,3,4,5];
array.map(function(item){
return item*2;
});
数组map方法可以接受一个匿名函数,数组中每个元素都会调用这个匿名函数,并且讲返回结果放在一个数组中。
获取数据更新UI
mui中的each()方法既是一个类方法,同时也是一个对象方法,两个方法适用场景不同;换言之,你可以使用mui.each()去遍历数组或json对象,也可以使用mui(selector).each()去遍历DOM结构。
mui.each( obj , handler )
- obj :
Type: Array||JSONObj
需遍历的对象或数组;若为对象,仅遍历对象根节点下的key - handler :
Type: Function( Integer||String index,Anything element)
为每个元素执行的回调函数;其中,index表示当前元素的下标或key,element表示当前匹配元素
对于前面说到的音乐api返回的json数据,首先我们要通过var songs=data.result.songs;获取"songs"的值,然后遍历"songs"对象的子对象。
···
success:function(data){
// console.log(JSON.stringify(data));
var songs=data.result.songs;
mui.each(songs,function(index,item){
var id = item.id,
name = item.album.name,
author = item.artists[0].name,
picUrl = item.album.picUrl,
audio = item.audio;
})
},
···
我们用上述代码可以得到我们想要的数据,下面就是需要将信息展示出来,这里我们只获取 歌曲id、歌曲所属专辑名name、歌曲第一作者author、歌曲所属专辑图片picUrl 、歌曲音频文件audio。
考虑到要加载很多图片,这里我们使用懒加载实现效果,这里我们直接使用hello mui里面的模板页面lazyload-image.html,我们需要引入mui.lazyload.js和mui.lazyload.img.js两个文件,还有占位图。下面先贴出修改后的基本的代码,然后再讲解其中的内容。
home.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>M-BOX</title>
<meta name="viewport" content="width=device-width, initial-scale=1,maximum-scale=1,user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<!--标准mui.css-->
<link rel="stylesheet" href="../css/mui.min.css">
<style type="text/css">
.mui-content>.mui-table-view:first-child {
margin-top: 0px;
}
</style>
</head>
<body>
<div class="mui-content">
<ul id="list" class="mui-table-view mui-table-view-chevron"></ul>
</div>
</body>
<script src="../js/mui.min.js "></script>
<script src="../js/mui.lazyload.js"></script>
<script src="../js/mui.lazyload.img.js"></script>
<script>
mui.init();
var url = "http://s.music.163.com/search/get/";
mui.ajax(url,{
data: {
'type': 1,
's': "喜欢你",
'limit': 10
},
dataType:'json',//服务器返回json格式数据
type:'post',//HTTP请求类型
timeout:10000,//超时时间设置为10秒;
success:function(data){
//console.log(JSON.stringify(data));
var songs=data.result.songs;
var list = document.getElementById("list");
var fragment = document.createDocumentFragment();
var li;
mui.each(songs,function(index,item){
var id = item.id,
name = item.album.name,
author = item.artists[0].name,
picUrl = item.album.picUrl,
audio = item.audio;
li = document.createElement('li');
li.className = 'mui-table-view-cell mui-media';
li.innerHTML = '<a class="mui-navigate-right" id='+ id +' data-audio='+ audio +'>'+
'<img class="mui-media-object mui-pull-left" data-lazyload="'+picUrl+'">'+
'<div class="mui-media-body">'+name+
'<p class="mui-ellipsis">'+author+'</p>'+
'</div>'+
'</a>';
fragment.appendChild(li);
})
list.appendChild(fragment)
mui(document).imageLazyload({
placeholder: '../img/60x60.gif'
});
},
error:function(xhr,type,errorThrown){
//异常处理;
console.log(type);
}
});
//列表点击事件
mui("#list").on('tap','li a',function(){
var id = this.getAttribute('id');
var audio = this.getAttribute('data-audio');
//打开详情页面
mui.openWindow({
url:'music.html',
id:'music.html',
extras:{
musicId:id,
audioUrl:audio
}
});
});
</script>
</html>
music.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<title></title>
<link href="../css/mui.min.css" rel="stylesheet"/>
</head>
<body>
<script src="../js/mui.min.js"></script>
<script type="text/javascript">
mui.init();
mui.plusReady(function(){
var self = plus.webview.currentWebview();
var musicId = self.musicId;
var audioUrl = self.audioUrl;
document.write("musicId:" + musicId + "<br />");
document.write("audioUrl:" + audioUrl);
});
</script>
</body>
</html>
我们这里说几个开发者常见的问题:
拼接html字符串
首先打开hello mui里面的模板页面lazyload-image.html,我们打开控制台查看elements,结构如下:
<ul id="list" class="mui-table-view mui-table-view-chevron">
<li class="mui-table-view-cell mui-media">
<a class="mui-navigate-right">
<img class="mui-media-object mui-pull-left" data-lazyload-id="0" src="http://www.dcloud.io/hellomui/images/1.jpg?version=447.4190210457891">
<div class="mui-media-body">
主标题
<p class="mui-ellipsis">列表二级标题</p>
</div>
</a>
</li>
</ul>
我们需要将获取的内容动态的填充到这个里面,这里常用的方法就是动态的生成节点,这里我们就要用到DOM的创建方法document.createElement,通过这个方法我们可以生成一个节点:
li = document.createElement('li');这个可以生成一个li节点,我们需要为这个li指定class,通过对对象的className赋值实现,如:li.className = 'mui-table-view-cell mui-media';,我们使用appendChild(li)方法将li节点挂着父节点上,比如:
var list = document.getElementById("list");
list.appendChild(li);
这个就是把li节点挂在list下,同理我们可以在li节点下 创建子节点,上面为了简单,我们使用给innerHTML属性赋值的做法,这样innerHTML的值同样会挂在li节点下,大家需要注意的是innerHTML属性的值我字符串,所以我们往中间插入了变量,要使用+连接起来。我们可以通过循环创建多个节点,如下面这样创建十个段落:
for(var i = 0 ; i < 10; i ++) {
var p = document.createElement("p");
var oTxt = document.createTextNode("段落" + i);
p.appendChild(oTxt);
document.body.appendChild(p);
}
上面我们使用的是mui.each()方法,效果类似。
如果细心的同学或许注意到我们使用了一个奇怪的写法:
var fragment = document.createDocumentFragment();
mui.each(songs,function(index,item){
...
fragment.appendChild(li);
});
list.appendChild(fragment);
为啥我们要在这里使用fragment呢?
如果是对前端技术感兴趣的同学说不定回去查一下,我相信大部分人可能就放过了吧!这里我查了一下资料:
在《javascript高级程序设计》一书的6.3.5:创建和操作节点一节中,介绍了几种动态创建html节点的方法,其中有以下几种常见方法:
· crateAttribute(name): 用指定名称name创建特性节点
· createComment(text): 创建带文本text的注释节点
· createDocumentFragment(): 创建文档碎片节点
· createElement(tagname): 创建标签名为tagname的节点
· createTextNode(text): 创建包含文本text的文本节点
其中最感兴趣且以前没有接触过的一个方法是createComment(text)方法,书中介绍说:在更新少量节点的时候可以直接向document.body节点中添加,但是当要向document中添加大量数据是,如果直接添加这些新节点,这个过程非常缓慢,因为每添加一个节点都会调用父节点的appendChild()方法,为了解决这个问题,可以创建一个文档碎片,把所有的新节点附加其上,然后把文档碎片一次性添加到document中。—— document的createDocumentFragment()方法
页面传值
我们点击了列表,我们会进入一个详情页,我们肯定是想知道我们究竟点了哪一个我们想把一些数据传入到详情页面,这里我们使用了mui.openWindow()方法中的拓展参数extras传入几个值,在详情页面通过plus.webview.currentWebview获取:
mui.plusReady(function(){
var self = plus.webview.currentWebview();
var musicId = self.musicId;
var audioUrl = self.audioUrl;
document.write("musicId:" + musicId + "<br />");
document.write("audioUrl:" + audioUrl);
});
这是在页面初始化时,通过扩展参数extras传值;另外还有页面已创建,通过自定义事件传值,参考mui官网中自定义事件的介绍,这里先不讲得太多,后面会专门花时间详细讲解相关细节。