ajax的核心是通過XmlHttpRequest獲取非本頁內容,而jsonp的核心則是動態添加<script>標簽來調用服務器提供的js腳本
json核心就是:允許用戶傳遞一個callback參數給服務端,然后服務端返回數據時會將這個callback參數作為函數名來包裹住JSON數據,這樣客戶端就可以隨意定制自己的函數來自動處理返回數據了。
jquery ext dojo這類庫的實現手段其實大同小異
在同源策略下,在某個服務器下的頁面是無法獲取到該服務器以外的數據的,但img、iframe、script等標簽是個例外,這些標簽可以通過src屬性請求到其他服務器上的數據。
利用script標簽的開放策略,我們可以實現跨域請求數據,當然,也需要服務端的配合。
先看一段jQuery處理jsonp的情況
通過發送php請求
客戶端
$.ajax({ async: false, // 同步加載數據,即等到ajax執行完畢再接着執行下面的語句 url: 'http://192.168.1.114/yii/demos/test.php', //不同的域 type: 'GET', // jsonp模式只有GET是合法的 data: { 'action': 'aaron' }, // 預傳參的數組 dataType: 'jsonp', // 數據類型 jsonp: 'backfunc', // 指定回調函數名,與服務器端接收的一致,並回傳回來 success: function(json) { console.log(json); } })
php服務端
<?php $act = trim($_GET['action']); if($act == 'aaron' ){ echo trim($_GET['backfunc']).'('. json_encode(array('status'=>1,'info'=>'OK')) .')'; } ?>
一般的ajax是不能跨域請求的,因此需要使用一種特別的方式來實現跨域,其中的原理是利用 <script> 元素的這個開放策略
這里有2個重要的參數
- jsonpCallback:
為jsonp請求指定一個回調函數名。這個值將用來取代jQuery自動生成的隨機函數名。這主要用來讓jQuery生成一個獨特的函數名,這樣管理請求更容易,也能方便地提供回調函數和錯誤處理。你也可以在想讓瀏覽器緩存GET請求的時候,指定這個回調函數名。從jQuery 1.5開始,你也可以使用一個函數作為該參數設置,在這種情況下,該函數的返回值就是jsonpCallback的結果。
- jsonp:
在一個jsonp請求中重寫回調函數的名字。這個值用來替代在"callback=?"這種GET或POST請求中URL參數里的"callback"部分,比如{jsonp:'onJsonPLoad'}會導致將"onJsonPLoad=?"傳給服務器。在jQuery 1.5,,設置jsonp選項為false,阻止了jQuery從加入"?callback"字符串的URL或試圖使用"=?"轉換。在這種情況下,你也應該明確設置jsonpCallback設置。例如, { jsonp: false, jsonpCallback: "callbackName" }、
當我們正常地請求一個JSON數據的時候,服務端返回的是一串JSON類型的數據,而我們使用JSONP模式來請求數據的時候
服務端返回的是一段可執行的JavaScript代碼
所以我們可見服務器代碼最后一行
$_GET['backfunc']).'('. json_encode(array('status'=>1,'info'=>'OK')) .')
就是執行的 backfunc方法,然后把數據通過回調的方式傳遞過去
OK,就是整個流程就是:
客戶端發送一個請求,規定一個可執行的函數名(這里就是jQuery做了封裝的處理,自動幫你生成回調函數並把數據取出來供success屬性方法來調用,不是傳遞的一個回調句柄),服務端接受了這個backfunc函數名,然后把數據通過實參的形式發送出去
jQuery的實現:
通過ajax請求不同域的實現,底層不是靠XmlHttpRequest而是script,所以不要被這個方法給迷惑了
在ajax請求中類型如果是type是get post,其實內部都只會用get,因為其跨域的原理就是用的動態加載script的src,所以我們只能把參數通過url的方式傳遞
比如
$.ajax({ url: 'http://192.168.1.114/yii/demos/test.php', //不同的域 type: 'GET', // jsonp模式只有GET是合法的 data: { 'action': 'aaron' }, // 預傳參的數組 dataType: 'jsonp', // 數據類型 jsonp: 'backfunc', // 指定回調函數名,與服務器端接收的一致,並回傳回來 })
其實jquery內部會轉化成
然后動態加載
<script type="text/javascript" src="http://192.168.1.114/yii/demos/test.php?backfunc=jQuery2030038573939353227615_1402643146875&action=aaron"></script>
然后php方就會執行backfunc(傳遞參數);
所以流程就會分二步:
1:針對jsonp的預處理,主要是轉化拼接這些參數,然后處理緩存,因為jsonp的方式也是靠加載script所以要關閉瀏覽器緩存
inspectPrefiltersOrTransports中,當作了jsonp的預處理后,還要在執行inspect(dataTypeOrTransport);的遞歸,就是為了關閉這個緩存機制
var dataTypeOrTransport = prefilterOrFactory(options, originalOptions, jqXHR); /** * 針對jonsp處理 */ if (typeof dataTypeOrTransport === "string" && !seekingTransport && !inspected[dataTypeOrTransport]) { //增加cache設置標記 //不需要緩存 //dataTypes: Array[2] // 0: "script" // 1: "json" options.dataTypes.unshift(dataTypeOrTransport); inspect(dataTypeOrTransport); return false; } else if (seekingTransport) { return !(selected = dataTypeOrTransport); }
具體的預處理的代碼
// Detect, normalize options and install callbacks for jsonp requests // 向前置過濾器對象中添加特定類型的過濾器 // 添加的過濾器將格式化參數,並且為jsonp請求增加callbacks jQuery.ajaxPrefilter("json jsonp", function(s, originalSettings, jqXHR) { var callbackName, overwritten, responseContainer, // 如果是表單提交,則需要檢查數據 jsonProp = s.jsonp !== false && (rjsonp.test(s.url) ? "url" : typeof s.data === "string" && !(s.contentType || "").indexOf("application/x-www-form-urlencoded") && rjsonp.test(s.data) && "data" ); // Handle iff the expected data type is "jsonp" or we have a parameter to set // 這個方法只處理jsonp,如果json的url或data有jsonp的特征,會被當成jsonp處理 if (jsonProp || s.dataTypes[0] === "jsonp") { // Get callback name, remembering preexisting value associated with it // s.jsonpCallback時函數,則執行函數用返回值做為回調函數名 callbackName = s.jsonpCallback = jQuery.isFunction(s.jsonpCallback) ? s.jsonpCallback() : s.jsonpCallback; // Insert callback into url or form data // 插入回調url或表單數據 // "test.php?symbol=IBM&callback=jQuery20309245402452070266_1402451299022" if (jsonProp) { s[jsonProp] = s[jsonProp].replace(rjsonp, "$1" + callbackName); } else if (s.jsonp !== false) { s.url += (ajax_rquery.test(s.url) ? "&" : "?") + s.jsonp + "=" + callbackName; } // Use data converter to retrieve json after script execution s.converters["script json"] = function() { if (!responseContainer) { jQuery.error(callbackName + " was not called"); } return responseContainer[0]; }; // force json dataType // 強制跟換類型 s.dataTypes[0] = "json"; // Install callback // 增加一個全局的臨時函數 overwritten = window[callbackName]; window[callbackName] = function() { responseContainer = arguments; }; // Clean-up function (fires after converters) // 在代碼執行完畢后清理這個全部函數 jqXHR.always(function() { // Restore preexisting value window[callbackName] = overwritten; // Save back as free if (s[callbackName]) { // make sure that re-using the options doesn't screw things around s.jsonpCallback = originalSettings.jsonpCallback; // save the callback name for future use oldCallbacks.push(callbackName); } // Call if it was a function and we have a response if (responseContainer && jQuery.isFunction(overwritten)) { overwritten(responseContainer[0]); } responseContainer = overwritten = undefined; }); // Delegate to script return "script"; } });
jquery會在window對象中加載一個全局的函數,當代碼插入時函數執行,執行完畢后就會被移除。同時jquery還對非跨域的請求進行了優化,如果這個請求是在同一個域名下那么他就會像正常的Ajax請求一樣工作。
分發器執行代碼:
當我們所有的參數都轉化好了,此時會經過請求發送器用來處理發送的具體
為什么會叫做分發器,因為發送的請求目標
ajax因為參雜了jsonp的處理,所以實際上的請求不是通過 xhr.send(XmlHttpRequest)發送的
而是通過get方式的腳本加載的
所以
transports對象在初始化構件的時候,會生成2個處理器
- *: Array[1] 針對xhr方式
- script: Array[1] 針對script,jsonp方式
所以
transport = inspectPrefiltersOrTransports(transports, s, options, jqXHR);
那么得到的transport就會根據當前的處理的類型,來選擇采用哪種發送器(*、script)
針對script的請求器
jQuery.ajaxTransport("script", function(s) { // This transport only deals with cross domain requests if (s.crossDomain) { var script, callback; return { send: function(_, complete) { script = jQuery("<script>").prop({ async: true, charset: s.scriptCharset, //"http://192.168.1.114/yii/demos/test.php?backfunc=jQuery20308569577629677951_1402642881663&action=aaron&_=1402642881664" src: s.url }).on( "load error", callback = function(evt) { script.remove(); callback = null; if (evt) { complete(evt.type === "error" ? 404 : 200, evt.type); } } ); document.head.appendChild(script[0]); }, abort: function() { if (callback) { callback(); } } }; } });
此時就很明了吧
所以最終的實現就是通過動態加載腳本!