准备知识

在看ajax实现的时候,如果对ajax技术知识不是很懂的话,可以参看下ajax基础,以便读分析时不会那么迷糊

全局ajax事件

默认$.ajaxSettings设置中的global为true,因此在Ajax请求的生命周期内,这些事件将被触发:

ajaxStart:如果没有其他Ajax请求当前活跃将会被触发
ajaxBeforeSend:再发送请求前,可以被取消
ajaxSend:像 ajaxBeforeSend,但不能取消
ajaxSuccess:当返回成功时
ajaxError:当有错误时
ajaxComplete:请求已经完成后,无论请求是成功或者失败
ajaxStop:如果这是最后一个活跃着的Ajax请求,将会被触发

默认情况下,ajax事件在document对象上触发;然而如果请求的context是一个DOM节点,该事件会在此节点上触发然后再DOM中冒泡;唯一的例外是ajaxStart和ajaxStop这两个全局事件

源码实现如下

// 触发事件并返回布尔值
function triggerAndReturn(context, eventName, data) {
// 创建eventName事件对象
var event = $.Event(eventName)
// 触发对应事件
$(context).trigger(event, data)
// 判断是否调用了preventDefault()
return !event.isDefaultPrevented()
}
// 触发Ajax全局事件
function triggerGlobal(settings, context, eventName, data) {
if (settings.global) return triggerAndReturn(context || document, eventName, data)
}
// 请求活跃数
$.active = 0
// 如果没有其他Ajax请求操作,将会被触发(在$.ajax操作开始时会被内部调用)
function ajaxStart(settings) {
if (settings.global && $.active++ === 0) triggerGlobal(settings, null, 'ajaxStart')
}
// 如果这是最后一个活跃着的Ajax请求,将会被触发(在$.ajax操作结束时会被内部调用)
function ajaxStop(settings) {
if (settings.global && !(--$.active)) triggerGlobal(settings, null, 'ajaxStop')
}
// 再发送请求前,可以被取消(在$.ajax操作开始时会被内部调用)
function ajaxBeforeSend(xhr, settings) {
var context = settings.context
// 如果在$.ajax中的beforeSend方法中返回了return false
// 如果在全局的ajaxBeforeSend侦听回调中返回了return false
// 那么返回return false
if (settings.beforeSend.call(context, xhr, settings) === false ||
triggerGlobal(settings, context, 'ajaxBeforeSend', [xhr, settings]) === false)
return false
// ajaxBeforeSend事件侦听中如果没返回return false,那么主动触发ajaxSend事件
triggerGlobal(settings, context, 'ajaxSend', [xhr, settings])
}
// 当返回成功时
function ajaxSuccess(data, xhr, settings, deferred) {
var context = settings.context, status = 'success'
// 执行$.ajax的success方法
settings.success.call(context, data, status, xhr)
// 如果存在deferred模块,执行resolveWith方法
if (deferred) deferred.resolveWith(context, [data, status, xhr])
// 触发ajaxSuccess全局事件
triggerGlobal(settings, context, 'ajaxSuccess', [xhr, settings, data])
// 执行ajaxComplete函数
ajaxComplete(status, xhr, settings)
}
// 当有错误时
function ajaxError(error, type, xhr, settings, deferred) {
var context = settings.context
// 执行$.ajax的error方法
settings.error.call(context, xhr, type, error)
// 如果存在deferred模块,执行resolveWith方法
if (deferred) deferred.rejectWith(context, [xhr, type, error])
// 触发ajaxError全局事件
triggerGlobal(settings, context, 'ajaxError', [xhr, settings, error || type])
// 执行ajaxComplete函数
ajaxComplete(type, xhr, settings)
}
// 请求已经完成后,无论请求是成功或者失败
function ajaxComplete(status, xhr, settings) {
var context = settings.context
// 执行$.ajax的complete方法
settings.complete.call(context, xhr, status)
// 触发ajaxComplete全局事件
triggerGlobal(settings, context, 'ajaxComplete', [xhr, settings])
// 执行ajaxStop函数
ajaxStop(settings)
}

其实具体实现很简单,就是通过定义一系列触发函数,在具体的ajax接口操作中穿插调用,利用库本身实现的$.Event模块进行dom事件触发

全局ajax方法

ajax

使用

$.ajax({
type: 'GET',
url: '/projects',
data: { name: 'Zepto.js' },
dataType: 'json',
timeout: 300,
context: $('body'),
success: function(data){},
error: function(xhr, type){}
})

实现内容如下

$.ajax = function(options) {
// 1.拷贝传入配置options
// 2.拷贝$.ajaxSettings默认配置
// 3.触发全局ajaxStart事件
// 4.判断是否请求跨域(对settings.crossDomain进行设置)
// 5.取得实际有效的url
// 6.序列化数据
// 7.判断是否为jsonp并进行处理
// 8.获取mime和xhr, 定义内部头部处理方法
// 9.配置核心头部(表明请求类型, 表明能够处理的类型, 强制响应时的mime类型, 确定发送信息至服务器时内容编码类型)
// 10.侦听onreadystatechange
// 11.处理ajaxBeforeSend的情况
// 12.建立xhr.open
// 13.设置header
// 14.超时判断和处理
// 15.执行send
}

具体源码

$.ajax = function(options){
// 拷贝传入配置
var settings = $.extend({}, options || {}),
// 如果存在Deferred模块就创建deferred对象
deferred = $.Deferred && $.Deferred(),
urlAnchor, hashIndex;
// 拷贝$.ajaxSettings默认配置
for (key in $.ajaxSettings) if (settings[key] === undefined) settings[key] = $.ajaxSettings[key]
// 触发全局ajaxStart事件
ajaxStart(settings)
// 判断是否请求跨域
if (!settings.crossDomain) {
urlAnchor = document.createElement('a')
urlAnchor.href = settings.url
// ie游览器bug——重新赋值可以才能获得host
urlAnchor.href = urlAnchor.href
settings.crossDomain = (originAnchor.protocol + '//' + originAnchor.host) !== (urlAnchor.protocol + '//' + urlAnchor.host)
}
// 如果请求url不存在,就以当前页面地址为请求地址
if (!settings.url) settings.url = window.location.toString()
// 如果存在hash,则只取非hash的url部分
if ((hashIndex = settings.url.indexOf('#')) > -1) settings.url = settings.url.slice(0, hashIndex)
// 序列化数据
serializeData(settings)
// 设置dataType
var dataType = settings.dataType, hasPlaceholder = /\?.+=\?/.test(settings.url)
// 如果存在?name=?占位符,就设置dataType为jsonp
if (hasPlaceholder) dataType = 'jsonp'
// 如果判断条件满足,则不允许缓存,通过添加时间戳实现
if (settings.cache === false || (
(!options || options.cache !== true) &&
('script' == dataType || 'jsonp' == dataType)
))
settings.url = appendQuery(settings.url, '_=' + Date.now()) // 如果dataType为jsonp
if ('jsonp' == dataType) {
// 如果不存在占位符
if (!hasPlaceholder)
// 如果settings.jsonp存在,则追加=?
// 如果settings.jsonp为false,则不向url中追加内容
// 否则追加callback=?
settings.url = appendQuery(settings.url,
settings.jsonp ? (settings.jsonp + '=?') : settings.jsonp === false ? '' : 'callback=?')
return $.ajaxJSONP(settings, deferred)
} // 根据dataType获取mime
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对象
xhr = settings.xhr(),
// 存储原生header设置方法
nativeSetHeader = xhr.setRequestHeader,
abortTimeout // 如果存在deferred对象,执行promise
if (deferred) deferred.promise(xhr)
// 表明请求类型
if (!settings.crossDomain) setHeader('X-Requested-With', 'XMLHttpRequest')
// 表明能够处理的类型
setHeader('Accept', mime || '*/*')
// 强制响应时的mime类型
if (mime = settings.mimeType || mime) {
if (mime.indexOf(',') > -1) mime = mime.split(',', 2)[0]
xhr.overrideMimeType && xhr.overrideMimeType(mime)
}
// 发送信息至服务器时内容编码类型
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])
// 重置header设置方法
xhr.setRequestHeader = setHeader xhr.onreadystatechange = function(){
// 请求过程结束
if (xhr.readyState == 4) {
// 重置onreadystatechange为空函数
xhr.onreadystatechange = empty
// 清除终止句柄
clearTimeout(abortTimeout)
var result, error = false
// 如果满足请求正常的状态码
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304 || (xhr.status == 0 && protocol == 'file:')) {
// 保证dataType有值
dataType = dataType || mimeToDataType(settings.mimeType || xhr.getResponseHeader('content-type'))
// 根据responseType设置result
if (xhr.responseType == 'arraybuffer' || xhr.responseType == 'blob')
result = xhr.response
else {
result = xhr.responseText
try {
// 通过settings中dataFilter字段进行数据过滤
result = ajaxDataFilter(result, dataType, settings)
// (1,eval)确保eval执行的作用域是在window下
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 }
// 如果error有值,发出转换异常
if (error) return ajaxError(error, 'parsererror', xhr, settings, deferred)
}
ajaxSuccess(result, xhr, settings, deferred)
} else {
ajaxError(xhr.statusText || null, xhr.status ? 'error' : 'abort', xhr, settings, deferred)
}
}
} // 如果ajaxBeforeSend侦听存在return false操作,则取消请求,发送异常
if (ajaxBeforeSend(xhr, settings) === false) {
xhr.abort()
ajaxError(null, 'abort', xhr, settings, deferred)
return xhr
} var async = 'async' in settings ? settings.async : true
// 附带username&password凭据字段参数
xhr.open(settings.type, settings.url, async, settings.username, settings.password)
// 复制其它配置到xhr实例上
if (settings.xhrFields) for (name in settings.xhrFields) xhr[name] = settings.xhrFields[name]
// 真正设置header
for (name in headers) nativeSetHeader.apply(xhr, headers[name])
// 如果存在超时设置,执行超时处理,发送超时异常
if (settings.timeout > 0) abortTimeout = setTimeout(function(){
xhr.onreadystatechange = empty
xhr.abort()
ajaxError(null, 'timeout', xhr, settings, deferred)
}, settings.timeout)
// 避免发送空字符串
xhr.send(settings.data ? settings.data : null)
return xhr
}

相关内部方法实现

// 通过mime值得到dateType
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添加Query
function appendQuery(url, query) {
if (query == '') return url
return (url + '&' + query).replace(/[&?]{1,2}/, '?')
} // 序列化数据($.param实现可看具体源码)
function serializeData(options) {
// 如果processData属性为true,并且存在的数据不为字符串,则调用$.param序列化
if (options.processData && options.data && $.type(options.data) != "string")
options.data = $.param(options.data, options.traditional)
// 如果请求为get或jsonp,则调用appendQueryj将data追加到url
if (options.data && (!options.type || options.type.toUpperCase() == 'GET' || 'jsonp' == options.dataType))
options.url = appendQuery(options.url, options.data), options.data = undefined
} // 数据过滤
function ajaxDataFilter(data, type, settings) {
if (settings.dataFilter == empty) return data
var context = settings.context
return settings.dataFilter.call(context, data, type)
}

get

$.get = function(/* url, data, success, dataType */){
return $.ajax(parseArguments.apply(null, arguments))
}

post

$.post = function(/* url, data, success, dataType */){
var options = parseArguments.apply(null, arguments)
options.type = 'POST'
return $.ajax(options)
}

getJSON

$.getJSON = function(/* url, data, success */){
var options = parseArguments.apply(null, arguments)
options.dataType = 'json'
return $.ajax(options)
}

拓展接口的依托方法

function parseArguments(url, data, success, dataType) {
if ($.isFunction(data)) dataType = success, success = data, data = undefined
if (!$.isFunction(success)) dataType = success, success = undefined
return {
url: url
, data: data
, success: success
, dataType: dataType
}
}

load

$.fn.load = function(url, data, success){
// 不存在zepto对象就不执行操作
if (!this.length) return this
// 获取zepto对象,url根据空格生成数组
var self = this, parts = url.split(/\s/), selector,
// 获取默认请求配置
options = parseArguments(url, data, success),
// 存储回调函数
callback = options.success
// 根据parts分理出url和选择器
if (parts.length > 1) options.url = parts[0], selector = parts[1]
// 修改success
options.success = function(response){
// 如果存在选择器,就取response里选择器的部分,否则取全部
self.html(selector ?
$('<div>').html(response.replace(rscript, "")).find(selector)
: response)
callback && callback.apply(self, arguments)
}
$.ajax(options)
return this
}

ajaxJSONP

使用

 // options就是$.ajax方法的options,主要传递`jsonpCallback:全局jsonp函数名`
$.ajaxJSONP(options);

源码

$.ajaxJSONP = function(options, deferred){
// 不存在type的时候就走正常的ajax流程,使用默认type
if (!('type' in options)) return $.ajax(options)
// 获取全局JSONP回调函数的字符串名
var _callbackName = options.jsonpCallback,
callbackName = ($.isFunction(_callbackName) ?
_callbackName() : _callbackName) || ('Zepto' + (jsonpID++)),
// 创建script标签
script = document.createElement('script'),
// 存储全局JSONP回调函数的引用
originalCallback = window[callbackName],
responseData,
// 定义取消方法
abort = function(errorType) {
$(script).triggerHandler('error', errorType || 'abort')
},
xhr = { abort: abort }, abortTimeout // 如果存在deferred,执行promise方法
if (deferred) deferred.promise(xhr) // 对script标签定义load error方法
$(script).on('load error', function(e, errorType){
// 清除定时函数,去除script标签
clearTimeout(abortTimeout)
$(script).off().remove()
// 操作成功和失败时
if (e.type == 'error' || !responseData) {
ajaxError(null, errorType || 'error', xhr, options, deferred)
} else {
ajaxSuccess(responseData[0], xhr, options, deferred)
}
// 交还全局jsonp函数引用
window[callbackName] = originalCallback
// 如果存在返回数据和全局jsonp函数
if (responseData && $.isFunction(originalCallback))
originalCallback(responseData[0])
// 重置变量
originalCallback = responseData = undefined
})
// 如果ajaxBeforeSend侦听存在return false操作,则执行取消
if (ajaxBeforeSend(xhr, options) === false) {
abort('abort')
return xhr
}
// 重置全局jsonp回调函数,用于获取数据
window[callbackName] = function(){
responseData = arguments
}
// 对script标签设置url(发送请求)
script.src = options.url.replace(/\?(.+)=\?/, '?$1=' + callbackName)
document.head.appendChild(script)
// 如果存在延迟设置,则定时执行取消
if (options.timeout > 0) abortTimeout = setTimeout(function(){
abort('timeout')
}, options.timeout) return xhr
}

ajaxJSONP实现的关键在于,对全局jsonp函数引用的存取和交还

一开始将原始函数引用进行存储

originalCallback = window[callbackName],

接着重置它用于在script请求后被执行以便获得返回数据

window[callbackName] = function(){
responseData = arguments
}
script.src = options.url.replace(/\?(.+)=\?/, '?$1=' + callbackName)
document.head.appendChild(script)

然后在script标签的load事件触发后,执行存储的原始函数并把数据传递过去

$(script).on('load error', function(e, errorType){
// .... if (responseData && $.isFunction(originalCallback))
originalCallback(responseData[0]) // ....
})

zepto源码分析·ajax模块的更多相关文章

  1. Zepto源码分析-ajax模块

    源码注释 // Zepto.js // (c) 2010-2015 Thomas Fuchs // Zepto.js may be freely distributed under the MIT l ...

  2. 读Zepto源码之Ajax模块

    Ajax 模块也是经常会用到的模块,Ajax 模块中包含了 jsonp 的现实,和 XMLHttpRequest 的封装. 读 Zepto 源码系列文章已经放到了github上,欢迎star: rea ...

  3. zepto源码分析·core模块

    准备说明 该模块定义了库的原型链结构,生成了Zepto变量,并将其以'Zepto'和'$'的名字注册到了window,然后开始了其它模块的拓展实现. 模块内部除了对选择器和zepto对象的实现,就是一 ...

  4. zepto源码分析·event模块

    准备知识 事件的本质就是发布/订阅模式,dom事件也不例外:先简单说明下发布/订阅模式,dom事件api和兼容性 发布/订阅模式 所谓发布/订阅模式,用一个形象的比喻就是买房的人订阅楼房消息,售楼处发 ...

  5. Zepto源码分析-deferred模块

    源码注释 // Zepto.js // (c) 2010-2015 Thomas Fuchs // Zepto.js may be freely distributed under the MIT l ...

  6. Zepto源码分析-event模块

    源码注释 // Zepto.js // (c) 2010-2015 Thomas Fuchs // Zepto.js may be freely distributed under the MIT l ...

  7. Zepto源码分析-form模块

    源码注释 // Zepto.js // (c) 2010-2015 Thomas Fuchs // Zepto.js may be freely distributed under the MIT l ...

  8. jQuery1.9.1源码分析--Ajax模块

    //Serialize an array of form elements or a set of //key/values into a query string // 将数组形式的表单元素或者哈希 ...

  9. Zepto源码分析-callbacks模块

    // Zepto.js // (c) 2010-2015 Thomas Fuchs // Zepto.js may be freely distributed under the MIT licens ...

随机推荐

  1. JAVA设计模式-单例模式(Singleton)线程安全与效率

    一,前言 单例模式详细大家都已经非常熟悉了,在文章单例模式的八种写法比较中,对单例模式的概念以及使用场景都做了很不错的说明.请在阅读本文之前,阅读一下这篇文章,因为本文就是按照这篇文章中的八种单例模式 ...

  2. CSS从大图中抠图然后显示其中的一部分

    相信大家在使用css时会遇到一个情况吧 就是一张大图片里面什么都有 各种图标都有 然而自己就是不太会使用其中的小图标 这是我最近的一次学习 首先上图 这么大一张图片 那么这么使其只显示一部分 并且为我 ...

  3. Julia初学备忘

    println("hello!") println("hello!") print("hello!") print("hello! ...

  4. xampp修改mysql 启动脚本

    打开xmapp,点击mysql对应的config按钮进入my.ini文件,如图所示: 修改mysqld服务的port参数3306为你想要设置的port,如图2所示: 重新启动mysql服务即可用客户端 ...

  5. flask+阿里云短信服务实现注册发送手机验证码

    效果图: 该效果主要讲解实现通过调用阿里云的SDK实现发送注册验证码短信(阿里云短信付费使用) 购买阿里云短信服务 购买链接:https://www.aliyun.com/product/sms 1. ...

  6. 检测MySQL主从是否异常

    #!bin/bash user='root' passwd="123" host="192.168.192.156" mycmd="mysql -u$ ...

  7. 【linux】Tomcat 安装

    登录linux后,切换目录到 /usr/local cd /user/local 在/usr/local目录新建文件夹servers用于存放tomcat文件 mkdir servers 在文件夹ser ...

  8. TCP三次握手和四次握手全过程 为什么要三次握手而不是二次握手?

    三次握手 第一次握手: 客户端发送syn包(syn=x)到服务器,并进入SYN_SEND状态,等待服务器确认: 第二次握手: 服务器收到syn包,必须确认客户的SYN(ack=x+1),同时自己也发送 ...

  9. html隐写术,使用摩尔兹电码/莫尔兹电码存储信息 水波纹样式 Morse code

    html水波纹样式,源码直接下载,代码有注释教程,小白可以看懂. 动画啥的都做好了,效果我觉得还不错 网上文章看到xbox 工程师使用隐写术,在界面的右下角放上了含有用户激活码的水波纹样式,一般人还真 ...

  10. .net core gRPC与IdentityServer4集成认证授权

    前言 随着.net core3.0的正式发布,gRPC服务被集成到了VS2019.本文主要演示如何对gRPC的服务进行认证授权. 分析 目前.net core使用最广的认证授权组件是基于OAuth2. ...