在学习zepto的源码的时候,不得不称赞这些人的厉害,我虽然能看明白,但是要我写,估计吭哧吭哧写不出来。虽然现在很少人使用zepto了,但是学习这些源码我相信每次看都能给咱们不同的感受。Deferred对象待会讲,deferred我认为是zepto设计最巧妙的地方。先来看zepto的ajax模块。
;(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触发他,如果默认行为被取消了,则返回false。
$.ajaxSettings = {
type: 'GET',
success: empty,
xhr: function () {
return new window.XMLHttpRequest()
},
cache: true,
crossDomain: false
}
这是ajax的初始化,默认是GET请求,xhr是新建的XMLHttpRequest()对象,cache表示浏览器是否应该被允许缓存GET响应。crossDomain表示是否可以来自另外一个域。
下面来看看最核心的$.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存储到本地,deferred咱们暂时可以把它看成一个promise对象,遍历$.ajaxSettings,如果用户没有设置里边有的属性,那就使用默认的属性。然后调用ajaxStart开始的函数。
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)
}
这里首先crossDomain为false的情况下,进入该逻辑,通过判断我们传入的url和当前window.location.href做对比,判断是不是跨域。
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。'#'代表网页中的一个位置,右边的字符,就是该位置的标识符,#是用来指导浏览器动作的,对浏览器没有用,所以在截取url的时候,没必要把后面的部分传给服务器。hasPlaceholder这里的正则表达式,用于匹配类似'?name=?'这种字符串,如果hasPlaceholder为true,则dataType为jsonp。下面的时不使用留在缓存的数据,第一种是设置cache为false,或者dataType为script和jsonp的情况,需要在url后面添加事件。下面是appendQuery方法,就是把字符串拼接到url后边,但是需要把'&'替换成'?'。如果我们的请求是jsonp请求,需要在url后面添加一些callback=?这种参数。
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类型,指定dataType的值,包括script、json、xml、html、text。mime存储的就是类似'application/json'的字符串。protocol就是匹配咱们类似'http://'的这种协议。
如果deferred存在,就把xhr转换为deferred对象。接着看下去,如果不是跨域的,那就是ajax请求。然后后面的判断条件查了一下是针对一些mozillar浏览器进行修正(有个问题就是他们上哪儿知道的用这种方式来修正啊)。
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
xhr.onreadystatechange = empty
if (/*如果成功*/){
// 对返回结果进行处理
}
}
}
xhr.open(settings.type, settings.url, async, settings.username, settings.password)
没什么难的,就是根据传入的参数不同进行处理。
$.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)
})
}
params是带有add方法的数组,遍历obj的键值对,属性值可能是对象也可能是数组或者字符串分别进行处理。如果obj的某个属性值是对象或者数组,scope就代表是该属性,那么这时候向params传入的key就需要变化,变成类似'a[b]'这里的b是obj的某个属性值是对象的一个属性。