JSON 是一種輕量的數據交互格式,與 AJAX 配合完成前端與服務端的信息傳遞,本文介紹 JSON 的使用、原生 AJAX 寫法、JSONP 跨域解決方法以及 AJAX 和 JSONP 工具函數的封裝 ...
JavaScript JSON 與 AJAX
JSON 是一種輕量的數據交互格式,與 AJAX 配合完成前端與服務端的信息傳遞,本文介紹 JSON 的使用、原生 AJAX 寫法、JSONP 跨域解決方法以及 AJAX 和 JSONP 工具函數的封裝
JSON 的概念
-
所有編程語言都離不開的三大數據結構
scalar 標量:數字和字元串
sequence 序列:數組和列表
mapping 映射:鍵值對
-
JSON:JavaScript Object Notation,輕量級的數據交互格式
-
JSON 數據是沒有方法的普通對象,或者是包含這種對象的數組
var person = { "name": "jett", "age": "22", "sex": "男" } var persons = [ { "name": "jett", "age": "22", "sex": "男" }, { "name": "lily", "age": "20", "sex": "女" } ]
JSON 的基本格式
- 鍵值對以冒號隔開
- 鍵名強制使用雙引號
- 併列數據用逗號隔開
- 併列數據集合用
[]
包圍
JSON 與對象的轉換
-
JSON 轉換成 JS 對象
JSON.parse()
<div id='box' data-info='{"name":"Jett","age":"22"}'></div>
// JSON.parse(str) str -> object var box = document.getElementById('box'); var jsonData = box.getAttribute('data-info'); var obj = JSON.parse(jsonData); console.log(obj); // {name:"Jett",age:"22"}
eval()
var obj = eval('('+jsonData+')'); // eval 可以執行任何 JS 代碼,所以可以將 jsonData 當作代碼執行 // 為安全性考慮,最好使用 JSON.parse()
-
JS 對象轉換 JSON
JSON.stringify()
var obj = { name: 'Jett', age: 22, sex: '男' } var jsonData = JSON.stringify(obj); console.log(jsonData); // {"name":"Jett","age":22,"sex":"男"}
AJAX 的概念
-
AJAX:Asynchronous JavaScript and XML,非同步的 JavaScript 和 XML
-
AJAX 不是新的編程語言,而是一種使用現有標準的新方法
-
AJAX 最大的優點是在不重新載入整個頁面的情況下,可以與伺服器交換數據並更新部分網頁內容
-
AJAX 工作原理
瀏覽器創建 XMLHttpRequest 對象,發送 AJAX 請求
伺服器接收請求,創建響應,返回數據
瀏覽器接收數據,動態渲染頁面
AJAX 的基本寫法
-
創建 XMLHttpRequest 對象
XMLHttpRequest 用於在後臺與伺服器交換數據
相容 IE7 及以上
var xmlhttp; if (window.XMLHttpRequest) { xmlhttp = new XMLHttpRequest(); } else { // 相容 IE6/5 xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); }
-
發送 AJAX 請求
GET 請求
url 為請求地址,在地址後使用
?
拼接消息內容,如 ?name=Jett&age=22xmlhttp.open('GET',url, true); xmlhttp.send();
POST 請求
send 方法內傳入消息內容,如:name=Jett&age=22
xmlhttp.open('POST',url, true); // xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded"); // 通過 setRequestHeader 設置請求頭 xmlhttp.send(data);
GET 請求比 POST 更快,但安全性低,且傳輸數據的大小有限制
第三參數 true 代表非同步,fasle 代表同步
-
監聽響應狀態
XMLHttpRequest 對象有 readystatechange 事件,用於監聽 readystate 的改變
XMLHttpRequest 對象的 readystate 表示 AJAX 請求的狀態
0: 請求未初始化 1: 伺服器連接已建立 2: 請求已接收 3: 請求處理中 4: 請求已完成,且響應已就緒
XMLHttpRequest 對象的 status 表示 http 請求的狀態碼
200: 請求 404: 未找到頁面
監聽 readystatachange 事件,並判斷狀態
當 xmlhttp.readyState 為 4,xmlhttp.status 為 200 時,代表請求成功且響應就緒
xmlhttp.onreadystatechange=function() { if (xmlhttp.readyState==4 && xmlhttp.status==200) { // ... } }
-
處理響應
XMLHttpRequest 對象的 responseText 或 responseXML 屬性用於接收服務端返回的數據
顧名思義,respnseXML 用於接收 XML 格式的響應數據,responseText 用於接收一般數據
xmlhttp.onreadystatechange=function() { if (xmlhttp.readyState==4 && xmlhttp.status==200) { console.log(xmlhttp.responseText); // 獲得響應數據並使用 } }
JSONP 的概念
-
跨域請求
URL = 協議名 + 主機名 + 埠號 + 資源名稱
域 = 協議名 + 主機名 + 埠號
出於安全性考慮,只有當頁面所在域和請求的目的地址在同一域才允許訪問
-
JSONP 是一種跨域解決方案
目前解決瀏覽器跨域問題的方法有 JSONP、cors 策略等,cors 策略是 HTML5 的新特性,老版本瀏覽器可能不支持,JSONP 是最常用的處理方式
JSONP 的原理
-
在 HTML 眾多標簽中,有些標簽具有跨域功能,如 script、img、iframe
-
JSONP 就是利用 script 標簽的跨域能力,動態生成一個 script 標簽,指定 src 為請求地址
頁面中定義的方法
<scrip type="text/javascript"> funtion test(data) { console.log(data); } </scrip> <!--動態生成的 script 標簽--> <script type="text/javascript" src="htpp://localhost:8888"></script>
htpp://localhost:8888 地址返回的數據
test('這是請求返回的數據')
將生成的 script 標簽添加到 DOM 中,瀏覽器根據 src 請求目的地址,得到返回的數據,因為是 script 標簽,瀏覽器會將返回的數據當成 JS 代碼來執行,就是執行頁面中定義的方法,剛好可以將其中的參數順利帶到頁面中
-
我們在頁面上定義一個函數,將其函數名通過 URL 查詢字元串傳到服務端,服務端拼接字元串,返回執行該函數的 JS 代碼,並將要傳遞的數據放在參數中,這樣在頁面上定義好的函數就可以被執行,並且得到了服務端傳來的數據,在該函數內執行成功回調,就可以對服務端數據進行處理了
JSONP 回調函數
-
對於普通 AJAX 請求我們可以通過監聽 XMLHttpRequest 的 readystatechange 事件,判斷 readystate 和 status 來知曉請求和響應是否完成,以執行成功回調或出錯回調
-
JSONP 方式本質上是利用 script 標簽的 src 進行請求,響應情況如下:
-
如果 src 指向資源存在,且其返回的字元串被當成 JS 代碼成功執行
即頁面內定義好的函數被成功執行,該函數內的成功回調函數可以通過參數拿到數據進行處理
-
如果 src 指向的目的資源訪問不到
script 標簽會觸發 error 事件,監聽此事件可以獲得執行出錯回調的時機
var script = document.createElement('script'); script.onerror = funtion() { // 執行出錯回調函數 }
-
如果 src 指向資源存在,返回的字元串會因為是 script 標簽而被執行,執行過程中出錯
在執行成功回調函數前,對 script 標簽對象添加一個標記屬性,監聽 script 的 load 事件發生時對象是否有該標簽屬性
因為 onload 函數在 script 標簽載入完成後執行,script 標簽在其引入的代碼執行後,才會響應 onload 處理函數,通過判斷標記屬性是否添加成功,可以知曉 script 標簽引入的代碼是否成功執行,如果標記屬性為 undefined,則執行出錯回調
var script = document.createElement('script'); window.callback = function (res) { script['jsonp'] = 1; // 執行成功回調函數 } script.onload = function () { if (typeof script['jsonp'] === 'undefined') { // 執行出錯回調函數 } }
-
需要註意的是,IE8 及以下 script 標簽對象不支持 onerror,也不支持 onload,但支持 onreadystatechange
通過判斷 readystate 來知曉 script 標簽的載入狀態,當 readystate 為 loaded 或 complete 時,表示 script 標簽載入完成,即 script 標簽引入的代碼已經執行,同樣的,在成功回調函數前為 script 對象添加標記屬性,通過判斷標記屬性是否添加成功,可以知曉 script 標簽引入的代碼是否成功執行,如果標記屬性為 undefined,則執行出錯回調
script.onreadystatechange = function () { // 正則表達式判斷 readystate 是否為 loaded 或 complete if (/loaded|complete/i.test(this.readyState)) { if (typeof script['jsonp'] === 'undefined') { // 執行出錯回調函數 } } }}
-
函數名動態生成,利用 onload 配合 onreadystate 判斷載入狀態,執行完畢後 delete 對應函數,並 remove 對應 script 標簽節點
在自己封裝 JSONP 函數時,我們可能會在 window 對象下動態添加函數如 callback,這樣 script 的 src 指定資源返回形如 callback('數據') 的字元串數據,就可以直接執行此函數並獲取數據,但是我們在優化 JSONP 函數時,會希望將動態創建的函數刪除,在 IE8 中 delete window 下的屬性會報不支持,我們可以在 Window.prototype 上添加函數,同樣可以在直接執行,且支持 delete
AJAX 與 JSONP 的封裝
-
封裝一個 ajax 函數,支持 get、post、jsonp 三種形式的請求,以對象形式傳入參數
-
配置項
var opt = { type: 'get', url: 'http://...', data: { // 數據使用對象形式 name: 'zzh', age: '21' }, async: true, // 預設 true success: function(res) { }, error: function() { }, timeout: 3000 // 預設 60000 }
-
代碼
function ajax(option) { // 設置預設參數 var opt = { type: option.type.toUpperCase(), url: option.url, data: option.data || null, async: option.async || false, success: option.success, error: option.error, timeout: option.timeout || 60000 }; // 用於 jsonp 的回調函數名 var callback = 'callback' + new Date().getTime(); var type = opt.type, success = opt.success, error = opt.error, data = parseData(opt.data); // 將 data 對象裝換成查詢字元串 if (type === 'GET' || type === 'POST') { var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function () { if (xhr.readyState === 4 && xhr.status === 200) { success && success(xhr.responseText, xhr.status); } else { error && error(xhr.status); } } if (type == 'GET') { opt.url += data; data = null; } xhr.open(type, opt.url, opt.async); xhr.send(data); setTimeout(function () { xhr.abort(); console.error(opt.url + '請求超時'); }, opt.timeout); } else if (type === 'JSONP') { var script = document.createElement('script'); script.src = opt.url + data; // 選則存放在 Window 原型上,window 下可以使用 // 如果直接存放在 window 上,IE8 window 屬性不支持 dalete Window.prototype[callback] = function (res) { script['jsonp'] = 1; success && success(res); } document.body.appendChild(script); // -[1,] 在 IE8 返回 NaN,IE9 及以上返回 -1 if (-[1,]) { // IE9 及以上支持 onerror // onerror 用於請求失敗,未執行 callback // onload 用於請求成功,但執行 callback 出錯 script.onerror = script.onload = function () { if (typeof script['jsonp'] === 'undefined') { error && error(); } script.parentNode.removeChild(script); delete Window.prototype[callback]; } } else { // script.onreadystatechange 相容 IE8 script.onreadystatechange = function () { // -[1,] 在 IE8 返回 NaN,IE9 及以上返回 -1 if (/loaded|complete/i.test(this.readyState)) { if (typeof script['jsonp'] === 'undefined') { error && error(); } script.parentNode.removeChild(script); delete Window.prototype[callback]; } } } } function parseData(data) { var arr = [], str; if (type === 'GET') { str = '?'; } else if (type === 'POST') { str = ''; } else if (type === 'JSONP') { str = '?callback=' + callback + '&'; } for (var k in data) { arr.push(k + '=' + data[k]); } return str + arr.join('&'); } } // 使用示例 ajax({ type: 'jsonp', url: 'http://127.0.0.1:8888/', data: { name: 'jett', age: 22 }, success: function (res) { console.log('接收數據:' + res); }, error: function () { console.log('error() 執行了'); } })