源碼注釋
// Zepto.js
// (c) 2010-2015 Thomas Fuchs
// Zepto.js may be freely distributed under the MIT license.
;(function($){
var jsonpID = 0,
document = window.document,
key,
name,
rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
scriptTypeRE = /^(?:text|application)\/javascript/i,
xmlTypeRE = /^(?:text|application)\/xml/i,
jsonType = 'application/json', //各種響應的contentType類型
htmlType = 'text/html',
blankRE = /^\s*$/,
originAnchor = document.createElement('a')
originAnchor.href = window.location.href
// trigger a custom event and return false if it was cancelled
/**
*
* 觸發自定義事件
* @param context
* @param eventName
* @param data
* @returns {boolean}
*/
function triggerAndReturn(context, eventName, data) {
var event = $.Event(eventName) //創建Event對象
$(context).trigger(event, data) //觸發
return !event.isDefaultPrevented() //TODO:返回event.isDefaultPrevented?
}
// trigger an Ajax "global" event
/**
* 觸發ajax的全局事件
* @param settings
* @param context
* @param eventName
* @param data
* @returns {*}
*/
function triggerGlobal(settings, context, eventName, data) {
//settings.global 是否觸發ajax的全局事件
if (settings.global) return triggerAndReturn(context || document, eventName, data)
}
// Number of active Ajax requests
//統計ajax request數量,用於相關全局事件 ajaxStart ajaxStop的計數
$.active = 0
/**
*觸發全局 ‘ajaxStart’事件
* @param settings
*/
function ajaxStart(settings) {
//settings.global 傳遞進來的是否觸發全局事件參數
//$.active++ === 0 $.active = 0,此判斷才會true
if (settings.global && $.active++ === 0) triggerGlobal(settings, null, 'ajaxStart')
}
/**
* 嘗試拋出所有請求停止事件,寫法和ajaxStart相同
* @param settings
*/
function ajaxStop(settings) {
if (settings.global && !(--$.active)) triggerGlobal(settings, null, 'ajaxStop')
}
// triggers an extra global event "ajaxBeforeSend" that's like "ajaxSend" but cancelable
//觸發全局ajaxBeforeSend事件,如果返回false,則取消此次請求
/**
* 請求前置器, beforeSend ,返回false,取消此次請求
* 或拋出 ajaxBeforeSend 全局事件
拋出ajaxSend事件
*
* @param xhr
* @param settings
* @returns {boolean}
*/
function ajaxBeforeSend(xhr, settings) {
var context = settings.context
if (settings.beforeSend.call(context, xhr, settings) === false ||
triggerGlobal(settings, context, 'ajaxBeforeSend', [xhr, settings]) === false)
return false
triggerGlobal(settings, context, 'ajaxSend', [xhr, settings])
}
/**
* 請求成功調用函數
* @param data
* @param xhr
* @param settings
* @param deferred
*/
function ajaxSuccess(data, xhr, settings, deferred) {
var context = settings.context, status = 'success'
//調用success函數
settings.success.call(context, data, status, xhr)
//調用所有成功回調函數
if (deferred) deferred.resolveWith(context, [data, status, xhr])
//拋出全局事件 'ajaxSuccess'
triggerGlobal(settings, context, 'ajaxSuccess', [xhr, settings, data])
//請求完成
ajaxComplete(status, xhr, settings)
}
// type: "timeout", "error", "abort", "parsererror"
/**
* 請求失敗調用函數
* @param error
* @param type
* @param xhr
* @param settings
* @param deferred
*/
function ajaxError(error, type, xhr, settings, deferred) {
var context = settings.context
settings.error.call(context, xhr, type, error)
if (deferred) deferred.rejectWith(context, [xhr, type, error])
triggerGlobal(settings, context, 'ajaxError', [xhr, settings, error || type])
ajaxComplete(type, xhr, settings)
}
// status: "success", "notmodified", "error", "timeout", "abort", "parsererror"
/**
* 請求完成調用函數
* @param status
* @param xhr
* @param settings
*/
function ajaxComplete(status, xhr, settings) {
var context = settings.context
//執行complete
settings.complete.call(context, xhr, status)
triggerGlobal(settings, context, 'ajaxComplete', [xhr, settings])
//嘗試拋出所有請求停止事件
ajaxStop(settings)
}
// Empty function, used as default callback
function empty() {}
/**
* 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 = ($.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。因為已加載完了
$(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
}
/**
* ajax 必傳的默認值
*/
$.ajaxSettings = {
// Default type of request
type: 'GET',
// Callback that is executed before request
beforeSend: empty,
// Callback that is executed if the request succeeds
success: empty,
// Callback that is executed the the server drops error
error: empty,
// Callback that is executed on request complete (both: error and success)
complete: empty,
// The context for the callbacks
context: null,
// Whether to trigger "global" Ajax events
global: true,
// Transport
xhr: function () {
return new window.XMLHttpRequest()
},
// MIME types mapping
// IIS returns Javascript as "application/x-javascript"
//媒體數據源,簡寫對應實際寫法
accepts: {
script: 'text/javascript, application/javascript, application/x-javascript',
json: jsonType,
xml: 'application/xml, text/xml',
html: htmlType,
text: 'text/plain'
},
// Whether the request is to another domain
crossDomain: false,
// Default timeout
timeout: 0,
// Whether data should be serialized to string
processData: true,
// Whether the browser should be allowed to cache GET responses
cache: true
}
/**
* 根據響應回來的媒體類型,轉換成易讀的類型 html,json,scirpt,xml,text等
* @param mime
* @returns {*|string|string}
*/
function mimeToDataType(mime) {
if (mime) mime = mime.split(';', 2)[0]
return mime && ( mime == htmlType ? 'html' :
mime == jsonType ? 'json' :
scriptTypeRE.test(mime) ? 'script' :
xmlTypeRE.test(mime) && 'xml' ) || 'text'
}
/**
* 將查詢參數追加到URL后面
* @param url
* @param query 查詢參數
* @returns {*}
*/
function appendQuery(url, query) {
if (query == '') return url
//replace(/[&?]{1,2}/, '?') 匹配到的第一個[&?]{1,2} 替換成?
return (url + '&' + query).replace(/[&?]{1,2}/, '?')
}
// serialize payload and append it to the URL for GET requests
/**
* 序列化
* 針對options.data 轉換成 a=b&c=1
*/
function serializeData(options) {
//options.processData: 對於非get請求,是否將請求參數options.data轉換為字符串
if (options.processData && options.data && $.type(options.data) != "string")
//將data數據序列化為字符串, 轉換成 a=b&c=1
// options.traditional
options.data = $.param(options.data, options.traditional)
// get請求,將序列化的數據追加到url后面
if (options.data && (!options.type || options.type.toUpperCase() == 'GET'))
options.url = appendQuery(options.url, options.data), options.data = undefined
}
/**
* ajax 請求
*/
$.ajax = function(options){
var settings = $.extend({}, options || {}), //創建新的options對象,不影響options的值
deferred = $.Deferred && $.Deferred(), //設置異步隊列
urlAnchor, hashIndex
//未傳 $.ajaxSettings里的值,復制$.ajaxSettings的值
for (key in $.ajaxSettings) if (settings[key] === undefined) settings[key] = $.ajaxSettings[key]
//觸發全局事件 'ajaxStart'
ajaxStart(settings)
//是否設置了跨域,未設置,需通過ip 協議 端口一致來判斷跨域
if (!settings.crossDomain) {
urlAnchor = document.createElement('a')
//如果沒有設置請求地址,則取當前頁面地址
urlAnchor.href = settings.url
// cleans up URL for .href (IE only), see https://github.com/madrobby/zepto/pull/1049
urlAnchor.href = urlAnchor.href
//通過ip 協議 端口來判斷跨域 location.host = host:port
settings.crossDomain = (originAnchor.protocol + '//' + originAnchor.host) !== (urlAnchor.protocol + '//' + urlAnchor.host)
}
//未設置url,取當前地址欄
if (!settings.url) settings.url = window.location.toString()
//如果有hash,截掉hash,因為hash ajax不會傳遞到后台
if ((hashIndex = settings.url.indexOf('#')) > -1) settings.url = settings.url.slice(0, hashIndex)
//將data進行轉換
serializeData(settings)
//TODO: /\?.+=\?/.test(settings.url) 有xxx.html?a=1?=cccc類似形式,為jsonp
var dataType = settings.dataType, hasPlaceholder = /\?.+=\?/.test(settings.url)
if (hasPlaceholder) dataType = 'jsonp'
//不設置緩存,加時間戳 '_=' + Date.now()
if (settings.cache === false || (
(!options || options.cache !== true) &&
('script' == dataType || 'jsonp' == dataType)
))
settings.url = appendQuery(settings.url, '_=' + Date.now())
//如果是jsonp,調用$.ajaxJSONP,不走XHR,走script
if ('jsonp' == dataType) {
if (!hasPlaceholder)
settings.url = appendQuery(settings.url,
settings.jsonp ? (settings.jsonp + '=?') : settings.jsonp === false ? '' : 'callback=?')
return $.ajaxJSONP(settings, deferred)
}
var mime = settings.accepts[dataType], //媒體類型
headers = { },
setHeader = function(name, value) { headers[name.toLowerCase()] = [name, value] }, //設置請求頭的方法
//如果URL沒協議,讀取本地URL的協議
protocol = /^([\w-]+:)\/\//.test(settings.url) ? RegExp.$1 : window.location.protocol,
xhr = settings.xhr(),
nativeSetHeader = xhr.setRequestHeader,
abortTimeout
//將xhr設為只讀Deferred對象,不能更改狀態
if (deferred) deferred.promise(xhr)
//如果沒有跨域
// x-requested-with XMLHttpRequest //表明是AJax異步
//x-requested-with null//表明同步,瀏覽器工具欄未顯示,在后台request可以獲取到
if (!settings.crossDomain) setHeader('X-Requested-With', 'XMLHttpRequest')
setHeader('Accept', mime || '*/*') //默認接受任何類型
if (mime = settings.mimeType || mime) {
//媒體數據源里對應多個,如 script: 'text/javascript, application/javascript, application/x-javascript',
//設置為最新的寫法, text/javascript等都是老瀏覽廢棄的寫法
if (mime.indexOf(',') > -1) mime = mime.split(',', 2)[0]
//對Mozilla的修正
// 來自服務器的響應沒有 XML mime-type 頭部(header),則一些版本的 Mozilla瀏覽器不能正常運行。
// 對於這種情況,xhr.overrideMimeType(mime); 語句將覆蓋發送給服務器的頭部,強制mime 作為 mime-type。*/
xhr.overrideMimeType && xhr.overrideMimeType(mime)
}
//如果不是Get請求,設置Content-Type
//Content-Type: 內容類型 指定響應的 HTTP內容類型。決定瀏覽器將以什么形式、什么編碼讀取這個文件. 如果未指定 ContentType,默認為TEXT/HTML。
/**
application/x-www-form-urlencoded:是一種編碼格式,窗體數據被編碼為名稱/值對,是標准的編碼格式。
當action為get時候,瀏覽器用x-www-form-urlencoded的編碼方式把form數據轉換成一個字串(name1=value1&name2=value2...),然后把這個字串append到url后面,用?分割,加載這個新的url。 當action為post時候,瀏覽器把form數據封裝到http body中,然后發送到server
**/
/**
* 如果有 type=file的話,需要設為multipart/form-data了。瀏覽器會把整個表單以控件為單位分割,並為每個部分加上 Content-Disposition(form-data或者file),Content-Type(默認為text/plain),name(控件 name)等信息,並加上分割符(boundary)。
*/
if (settings.contentType || (settings.contentType !== false && settings.data && settings.type.toUpperCase() != 'GET'))
setHeader('Content-Type', settings.contentType || 'application/x-www-form-urlencoded')
//設置請求頭
if (settings.headers) for (name in settings.headers) setHeader(name, settings.headers[name])
xhr.setRequestHeader = setHeader
xhr.onreadystatechange = function(){
/**
* 0:請求未初始化(還沒有調用 open())。
1:請求已經建立,但是還沒有發送(還沒有調用 send())。
2:請求已發送,正在處理中(通常現在可以從響應中獲取內容頭)。
3:請求在處理中;通常響應中已有部分數據可用了,但是服務器還沒有完成響應的生成。
4:響應已完成;您可以獲取並使用服務器的響應了。
*/
if (xhr.readyState == 4) {
xhr.onreadystatechange = empty
clearTimeout(abortTimeout) //清除超時
var result, error = false
//根據狀態來判斷請求是否成功
//>=200 && < 300 表示成功
//304 文件未修改 成功
//xhr.status == 0 && protocol == 'file:' 未請求,打開的本地文件,非localhost ip形式
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304 || (xhr.status == 0 && protocol == 'file:')) {
//獲取媒體類型
dataType = dataType || mimeToDataType(settings.mimeType || xhr.getResponseHeader('content-type'))
//響應值
result = xhr.responseText
//對響應值,根據媒體類型,做數據轉換
try {
// http://perfectionkills.com/global-eval-what-are-the-options/
//(1,eval)(result) (1,eval)這是一個典型的逗號操作符,返回最右邊的值
// (1,eval) eval 的區別是:前者是一個值,不可以再覆蓋。后者是變量,如var a = 1; (1,a) = 1; 會報錯;
// (1,eval)(result) eval(result) 的區別是:前者變成值后,只能讀取window域下的變量。而后者,遵循作用域鏈,從局部變量上溯到window域
//顯然 (1,eval)(result) 避免了作用域鏈的上溯操作,性能稍好
if (dataType == 'script') (1,eval)(result)
else if (dataType == 'xml') result = xhr.responseXML
else if (dataType == 'json') result = blankRE.test(result) ? null : $.parseJSON(result)
} catch (e) { error = e }
//解析出錯,拋出 'parsererror'事件
if (error) ajaxError(error, 'parsererror', xhr, settings, deferred)
//執行success
else ajaxSuccess(result, xhr, settings, deferred)
} else {
//如果請求出錯
// xhr.status = 0 / null 執行abort, 其他 執行error
ajaxError(xhr.statusText || null, xhr.status ? 'error' : 'abort', xhr, settings, deferred)
}
}
}
// 執行請求前置器
if (ajaxBeforeSend(xhr, settings) === false) {
xhr.abort()
ajaxError(null, 'abort', xhr, settings, deferred)
return xhr
}
// xhrFields 設置 如設置跨域憑證 withCredentials
if (settings.xhrFields) for (name in settings.xhrFields) xhr[name] = settings.xhrFields[name]
//'async' in settings ajaxSetting未設置過async為false,設置過,包括null,都為true
var async = 'async' in settings ? settings.async : true
//准備xhr請求
xhr.open(settings.type, settings.url, async, settings.username, settings.password)
//設置請求頭
for (name in headers) nativeSetHeader.apply(xhr, headers[name])
//超時處理:設置了settings.timeout,超時后調用xhr.abort()中斷請求
if (settings.timeout > 0) abortTimeout = setTimeout(function(){
xhr.onreadystatechange = empty
xhr.abort()
ajaxError(null, 'timeout', xhr, settings, deferred)
}, settings.timeout)
// avoid sending empty string (#319)
// 一般來說 post是settings.data get為null 因為get的查詢參數和url一起
xhr.send(settings.data ? settings.data : null)
return xhr
}
// handle optional data/success arguments
/**
* 參數轉換成ajax格式
* @param url
* @param data
* @param success
* @param dataType
* @returns {{url: *, data: *, success: *, dataType: *}}
*/
function parseArguments(url, data, success, dataType) {
if ($.isFunction(data)) dataType = success, success = data, data = undefined //如果data是function,則認為它是請求成功后的回調
if (!$.isFunction(success)) dataType = success, success = undefined
return {
url: url
, data: data //如果data不是function實例
, success: success
, dataType: dataType
}
}
/**
* 便捷方法 get請求
* @returns {*}
*/
$.get = function(/* url, data, success, dataType */){
return $.ajax(parseArguments.apply(null, arguments))
}
/**
* 便捷方法 post請求
* @returns {*}
*/
$.post = function(/* url, data, success, dataType */){
var options = parseArguments.apply(null, arguments)
options.type = 'POST'
return $.ajax(options)
}
/**
* 便捷方法 響應數據類型為JSON
* content-type: 'application/json'
* @returns {*}
*/
$.getJSON = function(/* url, data, success */){
var options = parseArguments.apply(null, arguments)
options.dataType = 'json'
return $.ajax(options)
}
/**
* 載入遠程 HTML 文件代碼並插入至 DOM 中
* @param url HTML 網頁網址 可以指定選擇符,來篩選載入的 HTML 文檔,DOM 中將僅插入篩選出的 HTML 代碼。語法形如 "url #some > selector"。
* @param data 發送至服務器的 key/value 數據
* @param success 載入成功時回調函數
* @returns {*}
*/
$.fn.load = function(url, data, success){
if (!this.length) return this
var self = this, parts = url.split(/\s/), selector,
options = parseArguments(url, data, success),
callback = options.success
//parts.length > 1 代表url后面有選擇符selector
if (parts.length > 1) options.url = parts[0], selector = parts[1]
options.success = function(response){
// response.replace(rscript, "") 過濾出script標簽
//$('<div>').html(response.replace(rscript, "")) innerHTML方式轉換成DOM
self.html(selector ?
$('<div>').html(response.replace(rscript, "")).find(selector)
: response)
//執行回調
callback && callback.apply(self, arguments)
}
$.ajax(options)
return this
}
//URI編碼方法。原生的escape/unescape已被廢棄。使用encodeURIComponent/decodeURIComponent
var escape = encodeURIComponent
/**
* 序列化
* @param params 結果數組
* @param obj 數組會按照name/value對進行序列化,普通對象按照key/value對進行序列化
* @param traditional 是否使用傳統的方式淺層序列化
* @param scope 和traditional=true一起使用。遞歸時,標記原始key。僅本身遞歸使用參數
*/
function serialize(params, obj, traditional, scope){
var type, array = $.isArray(obj), hash = $.isPlainObject(obj)
$.each(obj, function(key, value) {
type = $.type(value)
//如果是遞歸,scope有原始key值。key值修正
if (scope) key = traditional ? scope :
scope + '[' + (hash || type == 'object' || type == 'array' ? key : '') + ']'
// handle data in serializeArray() format
//當處理的數據為[{},{},{}]這種情況的時候,一般指的是序列化表單后的結果
//如果是表單
if (!scope && array) params.add(value.name, value.value)
// recurse into nested objects
//value是數組或對象,traditional為false,需要深層序列化,繼續遞歸序列化。
//在這里標記scope值
else if (type == "array" || (!traditional && type == "object"))
serialize(params, value, traditional, key)
//默認 obj屬性賦值到params
else params.add(key, value)
})
}
/**
* 將表單元素數組或者對象序列化
* @param obj 數組會按照name/value對進行序列化,普通對象按照key/value對進行序列化
* @param traditional 是否使用傳統的方式淺層序列化
* @returns {string}
*/
$.param = function(obj, traditional){
var params = []
//URI編碼后添加到數組里
params.add = function(key, value) {
if ($.isFunction(value)) value = value()
if (value == null) value = ""
//encodeURIComponent 編碼
this.push(escape(key) + '=' + escape(value))
}
serialize(params, obj, traditional)
return params.join('&').replace(/%20/g, '+')
}
})(Zepto)
方法圖

