JS高程:读书摘要(十七)JSON & Ajax

一、JSON

JSON是一种数据格式,与XML相比,JSON 是在JavaScript中读写结构化数据的更好的方式。因为可以把JSON直接传给eval(),而且不必创建DOM对象。

  • JSON 对象没有声明变量(JSON 中没有变量的概念)
  • 没有末尾的分号,除最后一项外,每项末尾都需要有逗号
  • 对象的属性必须加双引
  • 属性的值可以是简单值,也可以是复杂类型值。
  • 能使用下标、方括号、点的方式直接访问 JSON对象中的属性值
1.1 JSON 解析与序列化
  • JSON.stringify()

把一个JavaScript对象序列化为一个JSON 字符串然后返回,默认情况下,JSON.stringify()输出的JSON 字符串不包含任何空格字符或缩进。

在序列化JavaScript 对象时,所有函数及原型成员都会被有意忽略,不体现在结果中。此外,值为undefined 的任何属性也都会被跳过。结果中最终都是值为有效JSON数据类型的实例属性。

还可以接收另外两个参数,第一个参数是个过滤器,可以是一个数组,也可以是一个函数;第二个参数是一个选项,表示是否在JSON字符串中保留缩进。

var book = {
    "title": "Professional JavaScript",
    "authors": [
        "Nicholas C. Zakas"
    ],
    "edition": 3,
    "year": 2011
};
var jsonText = JSON.stringify(book, ["title", "edition"]);
// 在返回的结果字符串中,就只会包含这两个属性:"title", "edition"

第二个参数是函数,行为会稍有不同。


var jsonText = JSON.stringify(book, function(key, value){
    switch(key){
        case "authors":
            return value.join(",")
        case "year":
            return 5000;
        case "edition":
            return undefined; // 通过返回undefined 删除该属性。
        default:
            return value;
    }
});

// {"title":"Professional JavaScript","authors":"Nicholas C. Zakas","year":5000}

第三个参数用于控制结果中的缩进和空白符。如果这个参数是一个数值,那它表示的是每个级别缩进的空格数。只要传入有效的控制缩进的参数值,结果字符串就会包含换行符。(只缩进而不换行意义不大。)最大缩进空格数为10,所有大于10 的值都会自动转换为10。在使用字符串的情况下,可以将缩进字符设置为制表符,或者两个短划线之类的任意字符

var jsonText = JSON.stringify(book, null, " - -");
{
--"title": "Professional JavaScript",
--"authors": [
----"Nicholas C. Zakas"
--],
--"edition": 3,
--"year": 2011
}
// 缩进字符串最长不能超过10 个字符长。如果字符串长度超过了10 个,结果中将只出现前10 个字符。
  • toJSON()

有时候,JSON.stringify()还是不能满足对某些对象进行自定义序列化的需求。在这些情况下,可以给对象定义toJSON()方法,在JSON.stringify()时返回其自定义数据格式。

序列化JSON对象的顺序如下:

(1) 如果存在toJSON()方法而且能通过它取得有效的值,则调用该方法。否则,返回对象本身。
(2) 如果提供了第二个参数,应用这个函数过滤器。传入函数过滤器的值是第(1)步返回的值。
(3) 对第(2)步返回的每个值进行相应的序列化。
(4) 如果提供了第三个参数,执行相应的格式化。

  • JSON.parse()

JSON字符串直接传递给JSON.parse()就可以得到相应的JavaScript 值,JSON.parse()方法也可以接收另一个参数,该参数是一个函数,将在每个键值对上调用。这个函数接收两个参数,一个键和一个值,而且都需要返回一个值。

var book = {releaseDate: new Date(2011, 11, 1)};
var jsonText = JSON.stringify(book);
var bookCopy = JSON.parse(jsonText, function(key, value){
    if (key == "releaseDate"){
        return new Date(value);
    } else {
        return value;
    }
})
alert(bookCopy.releaseDate.getFullYear()); 
// 最后解析出来的还是一个Date对象

二、Ajax

Ajax 技术的核心是XMLHttpRequest 对象(简称XHR),这是由微软首先引入的一个特性,其他浏览器提供商后来都提供了相同的实现。XHR 为向服务器发送请求和解析服务器响应提供了流畅的接口。能够以异步方式从服务器取得更多信息,意味着用户单击后,可以不必刷新页面也能取得新据。也就是说,可以使用XHR 对象取得新数据,然后再通过DOM 将新数据插入到页面中。

使用

在使用XHR 对象时,要调用的第一个方法是open(),它接受3 个参数:要发送的请求的类型("get""post"等)、请求的URL 和表示是否异步发送请求的布尔值。

var xhr = new XMLHttpRequest();
xhr.open("get", "example.php", false);
xhr.send(null);
  • URL相对于执行代码的当前页面(当然也可以使用绝对路径);
  • 调用open()方法并不会真正发送请求,而只是启动一个请求以备发送。要发送特定的请求,必须调用send()方法,这里的send()方法接收一个参数,即要作为请求主体发送的数据。如果不需要通过请求主体发送数据,则必须传入null,因为这个参数对有些浏览器来说是必需的。调用send()之后,请求就会被分派到服务器。
  • 第三个参数false代表请求是同步的,JavaScript代码会等到服务器响应之后再继续执行,默认为true

服务器响应的数据会自动填充XHR对象的属性,相关的属性简介如下。

  • responseText:作为响应主体被返回的文本。
  • responseXML:如果响应的内容类型是"text/xml""application/xml",这个属性中将保存包含着响应数据的XML DOM 文档。
  • status:响应的HTTP 状态。
  • statusTextHTTP 状态的说明。

但多数情况下,我们还是要发送异步请求,才能让JavaScript 继续执行而不必等待响应。此时,可以检测XHR 对象的readyState 属性,该属性表示请求/响应过程的当前活动阶段。

  • 0:未初始化。尚未调用open()方法。
  • 1:启动。已经调用open()方法,但尚未调用send()方法。
  • 2:发送。已经调用send()方法,但尚未接收到响应。
  • 3:接收。已经接收到部分响应数据。
  • 4:完成。已经接收到全部响应数据,而且已经可以在客户端使用了。

只要readyState 属性的值由一个值变成另一个值,都会触发一次readystatechange 事件。可以利用这个事件来检测每次状态变化后readyState的值。通常,我们只对readyState 值为4的阶段感兴趣,因为这时所有数据都已经就绪。不过,必须在调用open()之前指定onreadystatechange事件处理程序才能确保跨浏览器兼容性。

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
    if (xhr.readyState == 4){
        if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
            alert(xhr.responseText);
        } else {
            alert("Request was unsuccessful: " + xhr.status);
        }
    }
};
xhr.open("get", "example.txt", true);
xhr.send(null);

另外,在接收到响应之前还可以调用abort()方法来取消异步请求,如下所示:
xhr.abort();
调用这个方法后,XHR 对象会停止触发事件,而且也不再允许访问任何与响应有关的对象属性。在终止请求之后,还应该对XHR对象进行解引用操作。由于内存原因,不建议重用XHR 对象。

HTTP头部信息

每个HTTP 请求和响应都会带有相应的头部信息,其中有的对开发人员有用,有的也没有什么用。XHR 对象也提供了操作这两种头部(即请求头部和响应头部)信息的方法。

默认情况下,在发送XHR请求的同时,还会发送下列头部信息。

  • Accept:浏览器能够处理的内容类型。
  • Accept-Charset:浏览器能够显示的字符集。
  • Accept-Encoding:浏览器能够处理的压缩编码。
  • Accept-Language:浏览器当前设置的语言。
  • Connection:浏览器与服务器之间连接的类型。
  • Cookie:当前页面设置的任何Cookie
  • Host:发出请求的页面所在的域 。
  • Referer:发出请求的页面的URI
  • User-Agent:浏览器的用户代理字符串。

setRequestHeader()方法可以设置自定义的请求头部信息。这个方法接受两个参数:头部字段的名称和头部字段的值。

要成功发送请求头部信息,必须在调用open()方法之后且调用send()方法之前调用setRequestHeader()

xhr.open("get", "example.php", true);
xhr.setRequestHeader("MyHeader", "MyValue");
xhr.send(null);
  • 响应头信息

调用XHR对象的getResponseHeader()方法并传入头部字段名称,可以取得相应的响应头部信息。而调用getAllResponseHeaders()方法则可以取得一个包含所有头部信息的长字符串。

GET请求

GET 是最常见的请求类型,最常用于向服务器查询某些信息。必要时,可以将查询字符串参数追加到URL 的末尾,以便将信息发送给服务器。对XHR而言,位于传入open()方法的URL末尾的查询字符串必须使用encodeURIComponent()进行编码。

function addURLParam(url, name, value) {
    url += (url.indexOf("?") == -1 ? "?" : "&");// 没有?加? 有问号加&
    url += encodeURIComponent(name) + "=" + encodeURIComponent(value);
    return url;
}

var url = "example.php";
//添加参数
url = addURLParam(url, "name", "Nicholas");
url = addURLParam(url, "book", "Professional JavaScript");
//初始化请求
xhr.open("get", url, false);
POST请求

POST 请求,通常用于向服务器发送应该被保存的数据。POST 请求应该把数据作为请求的主体提交,而GET请求传统上不是这样。POST 请求的主体可以包含非常多的数据,而且格式不限。

我们可以使用XHR 来模仿表单提交:首先将Content-Type头部信息设置为application/x-www-form-urlencoded,也就是表单提交时的内容类型,其次是以适当的格式创建一个字符串。

xhr.open("post", "postexample.php", true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
var form = document.getElementById("user-info");
xhr.send(serialize(form)); // serialize(form) 自定义方法 用来序列化表单

postexample.php 就可以通过$_POST取得提交的数据了。如果不设置Content-Type 头部信息,那么发送给服务器的数据就不会出现在$_POST 超级全局变量中。这时候,要访问同样的数据,就必须借助$HTTP_RAW_POST_DATA


XMLHttpRequest 2 级
  • FormData对象

FormData 为序列化表单以及创建与表单格式相同的数据(用于通过XHR 传输)提供了便利

var data = new FormData();
data.append("name", "Nicholas");

append()方法接收两个参数:键和值,分别对应表单字段的名字和字段中包含的值。可以像这样添加任意多个键值对。

而通过向FormData 构造函数中传入表单元素,也可以用表单元素的数据预先向其中填入键值对。

var data = new FormData(document.forms[0]);

// 也可以将它直接传给XHR 的send()方法,
xhr.open("post","postexample.php", true);
xhr.send(new FormData(document.forms[0]));
  • 超时设定

IE8XHR对象添加了一个timeout 属性,表示请求在等待响应多少毫秒之后就终止。在给timeout 设置一个数值后,如果在规定的时间内浏览器还没有接收到响应,那么就会触发timeout 事件,进而会调用ontimeout 事件处理程序。这项功能后来也被收入了XMLHttpRequest 2 级规范中。

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
    if (xhr.readyState == 4){
        try {
            if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
                alert(xhr.responseText);
            } else {
                alert("Request was unsuccessful: " + xhr.status);
            }
       } catch (ex){
            //假设由ontimeout 事件处理程序处理
       }
    }
};
xhr.open("get", "timeout.php", true);
xhr.timeout = 1000; //将超时设置为1 秒钟(仅适用于IE8+)
xhr.ontimeout = function(){
    alert("Request did not return in a second.");
};
xhr.send(null);

超时响应导致请求终止时,会调用ontimeout 事件处理程序。但此时readyStat可能已经改变为4了,这意味着会调用onreadystatechange 事件处理程序。可是,如果在超时终止请求之后再访问status 属性,就会导致错误。为避免浏览器报告错误,可以将检查status属性的语句封装在一个try-catch 语句当中。

  • overrideMimeType()方法

如果服务器返回的MIME类型是text/plain,但数据中实际包含的是XML。根据MIME类型,即使数据是XMLresponseXML 属性中仍然是null。通过调用overrideMimeType()方法,可以保证把响应当作XML 而非纯文本来处理。

调用overrideMimeType()必须在send()方法之前,才能保证重写响应的MIME类型。

xhr.open("get", "text.php", true);
xhr.overrideMimeType("text/xml");
xhr.send(null);
进度事件
  • loadstart:在接收到响应数据的第一个字节时触发。
  • progress:在接收响应期间持续不断地触发。
  • error:在请求发生错误时触发。
  • abort:在因为调用abort()方法而终止连接时触发。
  • load:在接收到完整的响应数据时触发。
  • loadend:在通信完成或者触发errorabortload事件后触发。

每个请求都从触发loadstart 事件开始,接下来是一或多个progress事件,然后触发errorabortload事件中的一个,最后以触发loadend事件结束。

  • load事件

用以替代readystatechange 事件,响应接收完毕后将触发load事件,因此也就没有必要去检查readyState 属性了。

onload 事件处理程序会接收到一个event对象,其target 属性就指向XHR 对象实例,因而可以访问到XHR对象的所有方法和属性。然而,并非所有浏览器都为这个事件实现了适当的事件对象。所以建议依然使用xhr.status而非e.target.status

只要浏览器接收到服务器的响应,不管其状态如何,都会触发load 事件。而这意味着你必须要检查status属性,才能确定数据是否真的已经可用了

xhr.onload = function(){
    if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
        alert(xhr.responseText);
    } else {
        alert("Request was unsuccessful: " + xhr.status);
    }
};
xhr.open("get", "altevents.php", true);
xhr.send(null);
  • progress事件

这个事件会在浏览器接收新数据期间周期性地触发。而onprogress事件处理程序会接收到一个event 对象,其target属性是XHR 对象,但包含着三个额外的属性:

  • lengthComputable:表示进度信息是否可用的布尔值。
  • position :表示已经接收的字节数byte
  • totalSize:表示根据Content-Length响应头部确定的预期字节数byte

为确保正常执行,必须在调用open()方法之前添加onprogress事件处理程序。

xhr.onprogress = function(event){
    var divStatus = document.getElementById("status");
        if (event.lengthComputable){
            divStatus.innerHTML = "Received " + event.position + " of " +
                                    event.totalSize +" bytes";
        }
};
xhr.open("get", "altevents.php", true);
xhr.send(null);
跨域

CORSCross-Origin Resource Sharing,跨源资源共享)是W3C的一个工作草案,定义了在必须访问跨源资源时,浏览器与服务器应该如何沟通。CORS 背后的基本思想,就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功,还是应该失败。

在发送请求时,需要给它附加一个额外的Origin头部,其中包含请求页面的源信息(协议、域名和端口),以便服务器根据这个头部信息来决定是否给予响应。

Origin: http://www.nczonline.net

如果服务器认为这个请求可以接受,就在Access-Control-Allow-Origin头部中回发相同的源信息(如果是公共资源,可以回发"*")。

Access-Control-Allow-Origin: http://www.nczonline.net

如果没有这个头部,或者有这个头部但源信息不匹配,浏览器就会驳回请求。正常情况下,浏览器会处理请求。注意,请求和响应都不包含cookie信息。

IECORS的实现

微软在IE8中引入了XDRXDomainRequest)类型。这个对象与XHR 类似,但能实现安全可靠的跨域通信。XDR 对象的安全机制部分实现了W3CCORS规范。

以下是XDRXHR 的一些不同之处。

  • cookie 不会随请求发送,也不会随响应返回。
  • 只能设置请求头部信息中的Content-Type 字段。
  • 不能访问响应头部信息。
  • 只支持GETPOST 请求

这些变化使CSRFCross-Site Request Forgery,跨站点请求伪造)和XSSCross-Site Scripting,跨站点脚本)的问题得到了缓解。被请求的资源可以根据它认为合适的任意数据(用户代理、来源页面等)来决定是否设置Access-Control- Allow-Origin 头部。作为请求的一部分,Origin 头部的值表示请求的来源域,以便远程资源明确地识别XDR请求。

使用也与XHR对象非常相似,也是创建一个XDomainRequest的实例,调用open()方法,再调用send()方法。XDR 对象的open()方法只接收两个参数:请求的类型和URL。所有XDR请求都是异步执行的,不能用它来创建同步请求。请求返回之后,会触发load事件,响应的数据也会保存在responseText属性中 。

var xdr = new XDomainRequest();
xdr.onload = function(){
    alert(xdr.responseText);
};
xdr.onerror = function(){
    alert("An error occurred.");
};
xdr.open("get", "http://www.somewhere-else.com/page/");
xdr.send(null);

如果失败(包括响应中缺少Access-Control-Allow-Origin 头部)就会触发error事件。遗憾的是,除了错误本身之外,没有其他信息可用,因此唯一能够确定的就只有请求未成功了。

也可以在请求返回前调用xdr.abort(); 来终止请求,也支持timeout属性以及ontimeout事件处理程序。

为支持POST请求,XDR 对象提供了contentType 属性,用来表示发送数据的格式。

xdr.open("post", "http://www.somewhere-else.com/page/");
xdr.contentType = "application/x-www-form-urlencoded";
xdr.send("name1=value1&name2=value2");
其他浏览器对CORS的实现

其他对象都通过XMLHttpRequest对象实现了对CORS的原生支持。在尝试打开不同来源的资源时,无需额外编写代码就可以触发这个行为。要请求位于另一个域中的资源,使用标准的XHR对象并在open()方法中传入绝对URL即可。

通过跨域XHR对象可以访问statusstatusText 属性,而且还支持同步请求。跨域XHR对象也有一些限制,但为了安全这些限制是必需的。以下就是这些限制。

  • 不能使用setRequestHeader()设置自定义头部。
  • 不能发送和接收cookie
  • 调用getAllResponseHeaders()方法总会返回空字符串。

由于无论同源请求还是跨源请求都使用相同的接口,因此对于本地资源,最好使用相对URL,在访问远程资源时再使用绝对URL。这样做能消除歧义,避免出现限制访问头部或本地cookie信息等问题。

Preflighted Reqeusts预检请求

CORS 通过一种叫做Preflighted Requests的透明服务器验证机制支持开发人员使用自定义的头部、使用GETPOST之外的方法,以及使用不同类型的主体内容。

在使用下列高级选项来发送请求时,就会向服务器发送一个Preflight 请求。这种请求使用OPTIONS 方法,发送下列头部。

  • Origin:与简单的请求相同。
  • Access-Control-Request-Method:请求自身使用的方法。
  • Access-Control-Request-Headers:(可选)自定义的头部信息,多个头部以逗号分隔。
Origin: http://www.nczonline.net
Access-Control-Request-Method: POST
Access-Control-Request-Headers: myHeaders  

发送这个请求后,服务器可以决定是否允许这种类型的请求。服务器通过在响应中发送如下头部与浏览器进行沟通。

  • Access-Control-Allow-Origin:与简单的请求相同。
  • Access-Control-Allow-Methods:允许的方法,多个方法以逗号分隔。
  • Access-Control-Allow-Headers:允许的头部,多个头部以逗号分隔。
  • Access-Control-Max-Age:应该将这个Preflight请求缓存多长时间(以秒表示)。
Access-Control-Allow-Origin: http://www.nczonline.net
Access-Control-Allow-Methods: POST, GET
Access-Control-Allow-Headers: NCZ
Access-Control-Max-Age: 1728000

Preflight请求结束后,结果将按照响应中指定的时间缓存起来。而为此付出的代价只是第一次发送这种请求时会多一次HTTP请求。

带凭据的请求

默认情况下,跨源请求不提供凭据(cookieHTTP 认证及客户端SSL证明等)。通过将withCredentials属性设置为true,可以指定某个请求应该发送凭据。如果服务器接受带凭据的请求,会用下面的HTTP头部来响应。

Access-Control-Allow-Credentials: true

如果发送的是带凭据的请求,但服务器的响应中没有包含这个头部,那么responseText 中将是空字符串,status的值为0,而且会调用onerror()事件处理程序。另外,服务器还可以在Preflight响应中发送这个HTTP头部,表示允许源发送带凭据的请求。

其他跨域技术

1、使用标签

一个网页可以从任何网页中加载图像,不用担心跨域不跨域。图像Ping是与服务器进行简单单向的跨域通信的一种方式。

图像Ping有两个主要的缺点,一是只能发送GET请求,二是无法访问服务器的响应文本。因此,图像Ping只能用于浏览器与服务器间的单向通信。

请求的数据是通过查询字符串形式发送的,而响应可以是任意内容,但通常是像素图或204响应。通过图像Ping浏览器得不到任何具体的数据,但通过侦听loaderror事件,它能知道响应是什么时候接收到的。

var img = new Image();
img.onload = img.onerror = function(){
    alert("Done!");
};
// 为什么指定为同一个函数 因为只能知道响应是什么时候接收到的。
// 无论是什么响应,只要请求完成,就能得到通知
img.src = "http://www.example.com/test?name=Nicholas";
// 请求从设置src 属性那一刻开始

2、JSONP

JSONPJSON with padding(填充式JSON 或参数式JSON)的简写,是应用JSON 的一种新方法。JSONP 看起来与JSON 差不多,只不过是被包含在函数调用中的JSON。例如:callback({ "name": "Nicholas" });

JSONP 由两部分组成:回调函数和数据。回调函数是当响应到来时应该在页面中调用的函数。回调函数的名字一般是在请求中指定的。而数据就是传入回调函数中的JSON数据。

JSONP 是通过动态

你可能感兴趣的:(JS高程:读书摘要(十七)JSON & Ajax)