簡要: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為只讀deferred if (deferred) deferred.promise(xhr) //監聽加載完,加載出錯事件 $(script).on('load error', function(e, errorType){ //清除超時設置timeout clearTimeout(abortTimeout) //刪除加載用的script。因為已加載完了,.off()清除掉綁定到dom的所有事件 $(script).off().remove() //錯誤調用error if (e.type == 'error' || !responseData) { ajaxError(null, errorType || 'error', xhr, options, deferred) } else { //成功調用success ajaxSuccess(responseData[0], xhr, options, deferred) } //回調函數 window[callbackName] = originalCallback if (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() == 1471504727756 settings.url = appendQuery(settings.url, '_=' + Date.now()) //如果是jsonp,調用$.ajaxJSONP,不走XHR,走script if ('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的服務器與客戶端任務流程圖:

