簡要:jsonp是一種服務器和客戶端信息傳遞方式,一般是利用script元素賦值src來發起請求。一般凡是帶有src屬性的元素發起的請求都是可以跨域的。
那么jsonp是如何獲取服務器的數據的呢?
jsonp先將指定的一個函數名作為url后面的參數傳遞到服務器,服務器取得函數名并將要傳遞的數據形成json格式與函數名包裝起來形成腳本傳遞給客戶端執行。
/*** jsonp請求* @param options* @param deferred* @returns {*}*/$.ajaxJSONP = function(options, deferred){//未設置type,就走 ajax 讓參數初始化.//如直接調用ajaxJSONP,type未設置if (!('type' in options)) return $.ajax(options)var _callbackName = options.jsonpCallback, //回調函數名//得到最終的回調函數名,callbackName為回調函數名稱callbackName = ($.isFunction(_callbackName) ?_callbackName() : _callbackName) || ('jsonp' + (++jsonpID)), //沒有回調,賦默認回調script = document.createElement('script'),originalCallback = window[callbackName], //回調函數 responseData,//中斷請求,拋出error事件//這里不一定能中斷script的加載,但在下面阻止回調函數的執行abort = function(errorType) {$(script).triggerHandler('error', errorType || 'abort')},xhr = { abort: abort }, abortTimeout//xhr為只讀deferredif (deferred) deferred.promise(xhr)//監聽加載完,加載出錯事件$(script).on('load error', function(e, errorType){//清除超時設置timeout clearTimeout(abortTimeout)//刪除加載用的script。因為已加載完了,.off()清除掉綁定到dom的所有事件 $(script).off().remove()//錯誤調用errorif (e.type == 'error' || !responseData) {ajaxError(null, errorType || 'error', xhr, options, deferred)} else {//成功調用successajaxSuccess(responseData[0], xhr, options, deferred)}//回調函數window[callbackName] = originalCallbackif (responseData && $.isFunction(originalCallback))originalCallback(responseData[0])//清空閉包引用的變量值,不清空,需閉包釋放,父函數才能釋放。清空,父函數可以直接釋放originalCallback = responseData = undefined})if (ajaxBeforeSend(xhr, options) === false) {abort('abort')return xhr}//回調函數設置,給后臺執行此全局函數,數據塞入window[callbackName] = function(){responseData = arguments}//回調函數追加到請求地址script.src = options.url.replace(/\?(.+)=\?/, '?$1=' + callbackName)document.head.appendChild(script)//超時處理,通過setTimeout延時處理if (options.timeout > 0) abortTimeout = setTimeout(function(){abort('timeout')}, options.timeout)return xhr}
$.ajaxJSONP(option,deffered) 這個方法在$.ajax中被調用,jsonp的請求和異步請求形式很像,但jsonp和ajax異步請求要嚴格去分開,jsonp是腳本加載形式。
但在zepto里面,jsonp和ajax請求做了形式上的融合,都是調用$.ajax方法,那么在$.ajax里面針對jsonp請求做出了哪些處理呢?
var dataType = settings.dataType, hasPlaceholder = /\?.+=\?/.test(settings.url);if (hasPlaceholder) dataType = 'jsonp'//不設置緩存,加時間戳 '_=' + Date.now()// 當settings.cache === null時if (settings.cache === false || ((!options || options.cache !== true) &&('script' == dataType || 'jsonp' == dataType)))//Date.now() == 1471504727756settings.url = appendQuery(settings.url, '_=' + Date.now())//如果是jsonp,調用$.ajaxJSONP,不走XHR,走scriptif ('jsonp' == dataType) {if (!hasPlaceholder) //判斷url是否有類似jsonp的參數settings.url = appendQuery(settings.url,settings.jsonp ? (settings.jsonp + '=?') : settings.jsonp === false ? '' : 'callback=?')return $.ajaxJSONP(settings, deferred)}
1:首先判斷settings.dataType ,如果是jsonp格式則在url后面添加settings.jsonp = ?,默認添加callback=?。然后再調用$.ajaxJSONP方法。但這里為什么不直接調用$.ajaxJSONP而多出一步呢?這里會校驗url,如果url后面有callback=?的形式,則dataType強制為jsonp。其實這里將hasPlaceholder作為$.ajaxJSONP的第三個參數,然后將settings.url的參數的處理放在$.ajaxJSONP內部進行,也是可以的。但作者為了能使$.ajaxJSONP重用并符合語義化要求,也就默認認定傳入其中的settings參數帶有callback=?的形式。
2:對于settings.url做appendQuery處理,其實就是保證settings.url后面一定得跟一個形式為callback=?的參數,如果之前url后面有,則不做處理。appendQuery是個工具,如果我們有在url后面加入參數的功能的時候,用它就可以了。
3:調用$.ajaxJSONP ,
流程總結如下:
1:獲取url參數中的回調函數名callbackName,若沒有則默認一個。
2:將callbackName函數對象保存起來。callbackName將在下面引用一個新的函數。
3:創建一個新腳本元素命名為script。
3:$(script).on("load error",function(){.....})
4:判斷ajaxBeforeSend,是否中斷請求
5:callbackName引用 function(){responseData = arguments},其作用是獲取腳本里面的參數內容,然后以類似ajax的形式返回數據。
6:將options.url最后的callback=?換成callback=callbackName,并賦值給script.src。
7:設置超時處理
$(script).on("load error",function(){.....})中大致內容如下:
1:清除超時設置。2:off()清除掉script綁定的所有事件后清除掉remove()。
3:若e.type == 'error' 或者 responseData為空,觸發ajaxError,否則觸發ajaxSuccess。
4:將原來的callbackName傳入responseData運行。
5:很重要的一步,釋放內存。
?
以下是關于jsonp的服務器與客戶端任務流程圖:
?