在学习zepto
的源码的时候,即使现在zepto
使用的很少,但是能够让我们学习的地方还是很多,无论是项目结构,还是细微的地方。
triggerAndReturn
;(function($){
var jsonpID = +new Date();
function triggerAndReturn(context, eventName, data) {
var event = $.Event(eventName);
$(context).trigger(event, data);
return !event.isDefaultPrevented();
}
})
复制代码
这里为什么文件开头都要使用';'这是因为在对多个js
文件进行打包的时候,如果使用换行分隔代码,当合并压缩多个文件之后,换行符会被删掉,连在一起可能出错,加上分号就保险了。jsonpID
在跨域jsonp
的时候会使用到,这是为了禁止使用cache
。triggerAndReturn()
的目的在于创建一个Event
事件,然后context
作为上下文触发event
事件,如果默认行为被阻止,则返回true
。
ajaxSettings
$.ajaxSettings = {
type: 'GET',
success: empty,
xhr: function () {
return new window.XMLHttpRequest()
},
cache: true,
crossDomain: false
}
复制代码
这是ajax
的一些默认设置,请求默认是GET
,xhr
是XMLHttpRequest
对象,cache
表示浏览器是否应该被允许缓存GET
响应。crossDomain
表示是否能够向另外的一个域请求数据。
$.ajax
下面来看看最核心的$.ajax
方法。
$.ajax = function (options) {
var settings = $.extend({}, options || {}),
deferred = $.Deferred && $.Deferred(),
urlAnchor, hashIndex
for (key in $.ajaxSettings)
if (settings[key] === undefined) settings[key] = $.ajaxSettings[key]
ajaxStart(settings)
...
}
复制代码
首先传入options
,然后将传入的options
存储到settings
,deferred
对象是来自$.Deferred
,deferred
对象近似看成promise
对象,具体的可以看这篇文章zepto
源码deferred
模块学习。这里如果用户传入相关的设置,则使用$.ajaxSettings
中默认的设置。
if (!settings.crossDomain) {
urlAnchor = document.createElement('a');
urlAnchor.href = settings.url;
urlAnchor.href = urlAnchor.href;
settings.crossDomain = (originAnchor.protocol + '//' + originAnchor.host) !== (urlAnchor.protocol + '//' + urlAnchor.host)
}
复制代码
进入该逻辑的条件是settings.crossDomain
为false
,这里前面三行是为了修复IE
浏览器的bug不用管,这里主要是通过判断当前域名和传入域名是否相同,来设置crossDomain
的值
if ((hashIndex = settings.url.indexOf('#')) > -1) settings.url = settings.url.slice(0, hashIndex)
var dataType = settings.dataType,
hasPlaceholder = /\?.+=\?/.test(settings.url);
if (hasPlaceholder) dataType = 'jsonp';
if (settings.cache === false || (
(!options || options.cache !== true) &&
('script' == dataType || 'jsonp' == dataType)
))
settings.url = appendQuery(settings.url, '_=' + Date.now())
function appendQuery(url, query) {
if (query == '') return url;
return (url + '&' + query).replace(/[&?]{1,2}/, '?')
}
if ('jsonp' == dataType) {
if (!hasPlaceholder)
settings.url = appendQuery(settings.url,
settings.jsonp ? (settings.jsonp + '=?') : settings.jsonp === false ? '' : 'callback=?')
return $.ajaxJSONP(settings, deferred)
}
复制代码
如果我们没有传入url
,那么url
赋值为window.location.href
。'#'代表网页中的一个位置,右边的字符,就是该位置的标识符,例如此时我们页面的A位置,那么'#'后面接的可以是'A',如'#A'。#是用来指导浏览器动作的,对服务器来说没有用,所以在截取url
的时候,没必要把后面的部分传给服务器。hasPlaceholder
这里的正则表达式,用于匹配类似'?name=?'这种字符串,这里的就是用'?'进行占位,我们并没有给name
赋值,如果hasPlaceholder
为true
,则dataType
设置为jsonp
, dataType
表示的是从服务器返回的数据类型。 下面是禁用缓存的情况,如果设置cache
为false
,或者 dataType
设置为script
或者jsonp
的情况下,禁用缓存,方法就是在url
后边添加时间,这样每次请求地址都不一样,浏览器自然就没有缓存了。appendQuery
方法的作用在url添加参数,同时把&&
、&?
,??
这三种字符替换为?
。
var mime = settings.accepts[dataType],
headers = {},
setHeader = function(name, value) {headers[name.toLowerCase()] = [name, value]},
protocol = /^([\w-]+:)\/\//.test(settings.url) ? RegExp.$1 : window.location.protocol,
xhr = settings.xhr(),
nativeSetHeader = xhr.setRequestHeader,
abortTimeout
if (deferred) deferred.promise(xhr);
if (!settings.crossDomain) setHeader('X-Requested-With', 'XMLHttpRequest');
setHeader('Accept', mime || '*/*')
if (mime = settings.mimeType || mime) {
if (mime.indexOf(',') > -1) mime = mime.split(',', 2)[0]
xhr.overrideMimeType && xhr.overrideMimeType(mime)
}
复制代码
settings
中的accepts
表示从服务器请求的MIME
类型,这其中的对应关系如下:
script: "text/javascript, application/javascript"
json: "application/json"
xml: "application/xml, text/xml"
html: "text/html"
text: "text/plain"
复制代码
protocol
就是匹配类似'http://'的这种协议(不过不明白为什么要加'-',难道还有前面加'-'的协议?)
如果deferred
对象存在,就把xhr
扩展为一个具有promise
方法的对象。然后通过crossDomain
的值,来设置头部信息,请求是否应该是ajax
请求。然后后面的判断条件查了一下是针对一些mozillar浏览器进行修正(有个问题就是他们上哪儿知道的用这种方式来修正啊)。
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
xhr.onreadystatechange = empty;
clearTimeout(abortTimeout);
if (/*如果成功*/){
// 对返回结果进行处理
if (dataType == 'script') (1, eval)(result)
else if (dataType == 'xml') result = xhr.responseXML
...
}
}
}
xhr.open(settings.type, settings.url, async, settings.username, settings.password)
复制代码
xhr
是具有promise
方法的对象,这里就是设置xhr
的onreadystatechange
处理函数,如果readystate
改变就触发这个函数,清除定时器,这个定时器根据我们是否传入timeout
而定。if
条件就是如果回调成功,但是需要对传回来的值做处理,因为可能是json
数据,也可能是script
格式,或者xml
格式。
if (ajaxBeforeSend(xhr, settings) === false) {
xhr.abort()
ajaxError(null, 'abort', xhr, settings, deferred)
return xhr
}
xhr.send(settings.data ? settings.data : null)
复制代码
这里表示如果发送请求出错了,就终止该请求调用abort
方法,调用ajaxError
方法。然后后面是防止传递空的字符,如果没有数据,我们就传null
到服务端。最后返回xhr
对象,然后发送数据到服务端
$.param
$.param = function (obj, traditional) {
var params = []
params.add = function (key, value) {
if ($.isFunction(value)) value = value()
if (value == null) value = ""
this.push(escape(key) + '=' + escape(value))
}
serialize(params, obj, traditional)
return params.join('&').replace(/%20/g, '+')
}
复制代码
传入一个对象,和一个标记,这个traditional
表示激活传统的方式通过$.param
来得到data
。首先定义一个空数组,如果在上面添加方法,这个方法的主要作用向params
里边添加序列化的对象,escape=encodeURIComponent
,然后调用serialize
,将obj
对象添加到params
中,最后返回将params
数组用'&'拼接,然后这里的%20
表示空格,意思是将空格替换成'+'
serialize
function serialize(params, obj, traditional, scope) {
var type, array = $.isArray(obj),
hash = $.isPlainObject(obj)
$.each(obj, function (key, value) {
type = $.type(value)
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
else if (type == "array" || (!traditional && type == "object"))
serialize(params, value, traditional, key)
else params.add(key, value)
})
}
复制代码
这个函数的作用主要是序列化参数。如果obj
是数组,则array
为true
,如果为纯粹对象,则hash
为true
。遍历需要序列化的对象obj
,判断value
的类型type
,这个type
后面会用到。scope
是记录深层嵌套时的key
值,这个key
值受traditional
的影响。如果traditional
为true
,则key
为原始的scope
值,即对象第一层的key
值。否则,用[]拼接当前循环中的key
,最终的key
值会是这种形式scope[key][key2]...。如果obj
为数组,并且scope
不存在,即为第一层,直接调用params.add
方法 否则如果value
的类型为数组或者非传统序列化方式下为对象,则递归调用serialize
方法,用来处理key
。其他情况调用params.add
方法。