JSON (JavaScript Object Notation) is a lightweight data-interchange format. 即 JSON 是一种轻量级的数据交换格式。
1. JSON 的结构
JSON 构建于于两种(除去简单值)结构(JSON is built on two structures)
① 对象:名称(键) / 值对的集合(A collection of name / value pairs):
{ "key1": "value1", "key2": "value2" }
或者
{ "name": "dee", "age": 28, "stack": { "frontend": "JavaScript", "backend": "php" } }
说明:
一个对象以 { 开始,} 结束。每个名称(name)后跟一个 :
每个键值对之间使用 , 分隔
注意:JSON 对象属性里的非数字型键值必须要加双引号,不能不加或者加单引号。
② 数组:值(value)的有序集合(An array is an ordered collection of values)
类似于 JavaScript 中的数组:
["dee", 28, "developer"]
把对象和数组结合起来,可以构建更复杂的数据集合:
[ { "title": "PHP Cookbook", "authors": [ "Sklar", "Adam" ], "edition": 3 }, { "title": "Modern PHP", "authors": "Lockhart" } ]
说明:
一个数组以 [ 开始,] 结束。值之间使用 , 分隔。
值(value)可以是字符串(string)、数值(number)、true
、false
、 null
、对象(object)或者数组(array)。这些结构可以嵌套。字符串类型的值必须双引号括起来。
2. JavaScript 解析和序列化 JSON
解析:把 JSON 字符串转换为 JavaScript 对象
序列化: 把Javascript 对象转换为 JSON 字符串
JSON 流行的原因除了与 JavaScript 类似的语法,还有一个原因就是可以把 JSON 数据结构解析为 JavaScript 对象。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <script> // JavaScript 对象 var book = [ { "title": "PHP Cookbook", "authors": [ "Sklar", "Adam" ], "edition": 3 }, { "title": "Modern PHP", "authors": "Lockhart" } ]; console.log(book); // 把 JavaScript 序列化为 JSON 字符串 var jsonText = JSON.stringify(book); console.log(jsonText); // 把 JSON 字符串解析为 JavaScript 对象 var bookCopy = JSON.parse(jsonText); console.log(bookCopy); // 旧版本浏览器使用 eval() 函数解析 JSON 字符串为 JavaScript 对象 // 结果和使用 JSON.parse() 一样 var bookCopy_ = eval(jsonText); console.log(bookCopy_); </script> </head> <body> </body> </html>
结果如下(FireFox 44.0.2):
3. jQuery 和 Ajax 获取与发送 JSON
① 获取 JSON 数据
客户端 json.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <script src="http://lib.sinaapp.com/js/jquery/1.9.1/jquery-1.9.1.min.js"></script> <script> $.ajax({ url: "index.php", type: "post", dataType: "json", // 表明接收的返回值是 JSON 格式的 data: { "name": "dee", "job": "developer" }, success: function(data) { console.log(data); $.each(data.stack, function(name, value) { console.log(value); }); }, error: function() { console.log("error"); } }); </script> </head> <body> </body> </html>
服务器端 index.php
<?php if(isset($_POST['name']) && $_POST['name'] == 'dee') { $data['code'] = 200; $data['message'] = 'success'; $data['stack']['frontend'] = ['html', 'css', 'javascript']; $data['stack']['backend'] = ['php', 'mysql', 'nosql', 'linux', 'node.js']; header('content-type:text/json'); exit(json_encode($data)); }
输出:
说明:在服务端写 API 接口的时候,如果是以 JSON 作为数据交换格式,要加上:
header('content-type:text/json');
此时接收到数据的客户端接受到的数据已经是一个 JavaScript 对象,不需要再做任何的解析工作,jQuery 已经自动把 JSON 字符串转换为了 JSON 对象。
在客户端接收以 JSON 为数据交换格式的 API 接口返回的数据时,也要加上:
dataType: "json",
表明接收的返回值是 JSON 格式的。
② 发送 JSON 数据
客户端 json2.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <script src="http://lib.sinaapp.com/js/jquery/1.9.1/jquery-1.9.1.min.js"></script> </head> <body> <form> <table> <tr> <td>name:</td> <td><input name="name" type="text"></td> </tr> <tr> <td>job:</td> <td><input type="text" name="job"></td> </tr> <tr> <td clospan="2"> <input type="button" id="submit" value="submit"> </td> </tr> </table> </form> </body> <script> $('#submit').click(function(){ $.ajax({ url: "index2.php", type: "post", contentType: "application/json", data: JSON.stringify($('form').serializeArray()), dataType: "json", success: function(data) { console.log(data); }, error: function() { console.log("error"); } }); }); </script> </html>
说明:
把 contentType 设置为 application/json,即 JSON 的 MIME 类型,发送的数据的类型是 JSON 字符串,后端使用 PHP 处理请求时,需要从 php://input 里获得原始输入流,再 json_decode 成对象;
serializeArray 会生成一个数组对象,每一个元素都表示一个表单 input 元素的属性的对象。
服务器端:
index2.php
<?php $data = file_get_contents('php://input'); // 从 php://input 里获得原始输入流 $data = json_decode($data, true); $response['code'] = 200; $response['message'] = 'success'; $response['data'] = $data; header('content-type:text/json'); exit(json_encode($response));
POST 数据:
响应:
4. JSONP
同源策略:
浏览器有一种同源策略(Same origin policy),同源是指域名、协议、端口相同,当浏览器执行一个脚本(例如 JavaScript)的时候会检查这个脚本是否同源,否则不会执行该脚本,因为浏览器认为来自同站点的资源是安全的。在浏览器中,<script>、<img>、<iframe>、<link> 等标签都可以加载跨域资源,而不受同源限制,但浏览器限制了JavaScript 的权限使其不能读、写加载的内容。另外同源策略只对网页的 HTML 文档做了限制,对加载的其他静态资源如 JavaScript、css、图片等仍然认为属于同源。(参考:
例如,客户端通过 XMLHttpRequest 跨域与服务端交互,访问 http://www.site1.com/ajaxjson.html
ajaxjson.html:
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <script src="http://lib.sinaapp.com/js/jquery/1.9.1/jquery-1.9.1.min.js"></script> </head> <body> <script> $.ajax({ url: "http://www.site2.com/index.php", type: "post", success: function(data) { console.log(data); }, error: function() { console.log("error"); } }); </script> </body> </html>
由于请求的域是 www.site2.com,属于跨域请求,所以在访问 http://www.site1.com/ajaxjson.html 时,浏览器会给出提示:
火狐下:
Chrome 下:
要解决这类跨域问题,可以使用 JSONP 作为解决方案。
JSONP:JSON with padding(参数式 JSON),包含在函数调用中的 JSON。如:
callback({"name": "dee"});
JSONP 利用在页面中创建 <script> 节点的方法向不同域提交 HTTP请求。例,两个域 http://www.site1.com 和 http://www.site2.com 实现跨域交互
客户端 site/index.html:
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>site1</title> </head> <body> <p id="response"></p> </body> <script> function jsonCallback(data) { document.getElementById("response").innerText = data.name; console.log(data); } </script> <script src="http://www.site2.com/index.php?callback=jsonCallback "></script> </html>
服务端 site2/index.php:
<?php $callback = isset($_GET['callback']) ? $_GET['callback'] : ''; echo $callback.'({"name": "emperor"})';
访问 http://www.site1.com/index.html,返回:
说明:
其中 jsonCallback 是客户端注册的,获取跨域服务器上的 json 数据后,回调的函数。http://www.site2.com/index.php?callback=jsonpCallback 这个 url 是跨域服务器取 json 数据的接口,参数为回调函数的名字,返回的格式为:jsonpCallback({"name": "dee"}) 简述原理与过程:首先在客户端注册一个 callback, 然后把 callback 的名字传给服务器。此时,服务器先生成 json 数据。 然后以 javascript 语法的方式,生成一个function , function 名字就是传递上来的参数 jsonp 。最后将 json 数据直接以入参的方式,放置到 function 中,这样就生成了一段 js 语法的文档,返回给客户端。 客户端浏览器,解析 script 标签,并执行返回的 javascript 文档,此时数据作为参数,传入到了客户端预先定义好的 callback 函数里。(动态执行回调函数)
(参考 JSONP跨域的原理解析)
同样的,jQuery 也对 JSONP 进行了封装,可以使用 jQuery 进行跨域请求:
site1/ajaxjsonp.html
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <script src="http://lib.sinaapp.com/js/jquery/1.9.1/jquery-1.9.1.min.js"></script> </head> <body> <script> $.ajax({ url: "http://www.site2.com", dataType: "jsonp", // Tell jQuery we're expecting JSONP jsonp: "callback", // 回调参数的名称 The name of the callback parameter success: function(data) { console.log(data); }, error: function() { console.log("error"); } }); </script> </body> </html>
浏览器此时不会再提示已组织跨源请求而返回了服务端的数据:
JSONP 的优点是:它不像 XMLHttpRequest 对象实现的 Ajax 请求那样受到同源策略的限制;它的兼容性更好,在更加古老的浏览器中都可以运行,不需要 XMLHttpRequest 或 ActiveX 的支持;并且在请求完毕后可以通过调用 callback 的方式回传结果。
JSONP 的缺点则是:它只支持 GET 请求而不支持 POST 等其它类型的 HTTP 请求。(参考 JSONP跨域的原理解析)
JSONP的最基本的原理是:动态添加一个 <script> 标签,而 script 标签的 src 属性是没有跨域的限制的。这样说来,这种跨域方式其实与 ajax XmlHttpRequest 协议无关了。更多原理可以参考 JSONP跨域的原理解析:
如果设为dataType: 'jsonp',这个$.ajax方法就和ajax XmlHttpRequest没什么关系了,取而代之的则是JSONP协议。JSONP是一个非官方的协议,它允许在服务器端集成Script tags返回至客户端,通过javascript callback的形式实现跨域访问。 JSONP即JSON with Padding。由于同源策略的限制,XmlHttpRequest只允许请求当前源(域名、协议、端口)的资源。如果要进行跨域请求, 我们可以通过使用html的script标记来进行跨域请求,并在响应中返回要执行的script代码,其中可以直接使用JSON传递javascript对象。 这种跨域的通讯方式称为JSONP。 jsonCallback 函数jsonp1236827957501(....):是浏览器客户端注册的,获取跨域服务器上的json数据后,回调的函数 Jsonp的执行过程如下: 首先在客户端注册一个callback (如:'jsoncallback'), 然后把callback的名字(如:jsonp1236827957501)传给服务器。注意:服务端得到callback的数值后,要用jsonp1236827957501(......)把将要输出的json内容包括起来,此时,服务器生成 json 数据才能被客户端正确接收。 然后以 javascript 语法的方式,生成一个function, function 名字就是传递上来的参数 'jsoncallback'的值 jsonp1236827957501 . 最后将 json 数据直接以入参的方式,放置到 function 中,这样就生成了一段 js 语法的文档,返回给客户端。 客户端浏览器,解析script标签,并执行返回的 javascript 文档,此时javascript文档数据,作为参数, 传入到了客户端预先定义好的 callback 函数(如上例中jquery $.ajax()方法封装的的success: function (json))里。 可以说jsonp的方式原理上和<script src="http://跨域/...xx.js"></script>是一致的(qq空间就是大量采用这种方式来实现跨域数据交换的)。JSONP是一种脚本注入(Script Injection)行为,所以有一定的安全隐患。
5.其他跨域解决方案
CORS(跨域资源共享,Cross-Origin Resource Sharing)是 W3 的一项机制(https://www.w3.org/TR/cors/),跨源资源共享标准通过新增一系列 HTTP 头,让服务器能声明哪些来源可以通过浏览器访问该服务器上的资源。
例:
在 site2/cors.php 中添加 header:
header('Access-Control-Allow-Origin: *');
* 表示允许任何域向服务端提交请求
服务端代码:
<?php header('Access-Control-Allow-Origin: *'); $response['code'] = 200; $response['message'] = 'success'; $response['name'] = 'dee'; header('Content-type: text/json'); exit(json_encode($response));
客户端 site1/cors.html
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <script src="http://lib.sinaapp.com/js/jquery/1.9.1/jquery-1.9.1.min.js"></script> </head> <body> <script> $.ajax({ url: "http://www.site2.com/cors.php", type: "post", dataType: "json", success: function(data) { console.log(data); }, error: function() { console.log("error"); } }); </script> </body> </html>
浏览器 console 输出:
当把 header 改为:
header('Access-Control-Allow-Origin: http://www.site3.com');
即只允许来自 http://www.site3.com 的 AJAX 请求
再次访问 http://www.site1.com,由于设置了只允许 site3 的 AJAX 请求,所以浏览器输出:
把 header 再次改为:
header('Access-Control-Allow-Origin: http://www.site1.com');
输出:
参考:
《JavaScript高级程序设计》3nd
《精通jQuery》2nd
https://learn.jquery.com/ajax/working-with-jsonp/