# ajax **Repository Path**: solocoding/ajax ## Basic Information - **Project Name**: ajax - **Description**: ajax设计方案 - **Primary Language**: HTML - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-07-09 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 原生js的ajax设计方案(现已支持npm安装) #### npm安装,文档见底部 npm i ajax-js / yarn add ajax-js #### 整理ajax设计方案原因如下 1. 从资源合理利用的角度以及网站优化角度去想,每次为了那几个功能,去引用一个框架,不划算 2. 拜读了w3c的ajax的设计方案,包括level1和level2的规范,有种豁然开朗的感觉 3. 有朋友遇到ajax的跨域方案,各种纠结在心里,导致内心不能舒畅 4. 自己的框架底层也要需要用到ajax的基础功能,(get post请求,对于level2的上传暂时没用到) 5. 最关键的也是之前对这块概念十分模糊,所以开始整理ajax这块的设计方案 #### 一些概念 * 浏览器的同源策略:浏览器最基本的安全功能,同源是指,域名,协议,端口相同(所以我写的接口部署端口分别为1122和2211即不是同源,属于跨域) * ajax:是一种技术方案,依赖的是CSS/HTML/Javascript,最核心依赖是浏览器提供的XMLHttpRequest对象,这个对象使得浏览器可以发出HTTP请求与接收HTTP响应。 * nginx:是一个高性能的HTTP和反向代理服务器 * IIS:微软开发的的服务器,window系统自带 * XMLHttpRequest 兼容性如下: ![](http://images2015.cnblogs.com/blog/801930/201611/801930-20161129224459115-1023971996.png) * XMLHttpRequest Level 1主要存在以下缺点: 1. 受同源策略的限制,不能发送跨域请求; 2. 不能发送二进制文件(如图片、视频、音频等),只能发送纯文本数据; 3. 发送和获取数据的过程中,无法实时获取进度信息,只能判断是否完成; * XMLHttpRequest Level 2中新增了以下功能: 1. 可以发送跨域请求,在服务端允许的情况下; 2. 支持发送和接收二进制数据; 3. 新增formData对象,支持发送表单数据; 4. 发送和获取数据时,可以获取进度信息; 5. 可以设置请求的超时时间; #### 开始准备 * 纯前端代码 * nginx反向代理服务器(前后端分离用) * 后台2套接口(端口:1122,端口:2211) PS:一份必须支持跨域请求 * IIS服务器(部署后台接口) * chrome插件postman(接口测试) * IE、chrome、firefox、Opera、safari、edge 6大浏览器,做兼容性测试 ### XMLHttpRequest发送请求步骤 1. 实例化XMLHttpRequest对象(IE8-9是微软封装的ActiveXObject('Microsoft.XMLHTTP'))获得一个实例 2. 通过实例open一个请求,设置发送类型和接口以及同异步 3. 如有需要配置报文,以及各种事件(success,error,timeout等) 4. 调用实例的send方法,发送http/https的请求 5. 服务器回调,客户端接收,并做响应处理 #### 关键代码 //每次清空请求缓存,并重新合并对象 var ajaxSetting = tool.checkParam(options), sendData = ''; //创建xhr对象 var xhr = tool.createXhrObject(); //针对某些特定版本的mozillar浏览器的BUG进行修正 xhr.overrideMimeType ? (xhr.overrideMimeType("text/javascript")) : (null); //针对IE8的xhr做处理 PS:ie8下的xhr无xhr.onload事件,所以这里做判断 xhr.onload === undefined ? (xhr.xhr_ie8 = true) : (xhr.xhr_ie8 = false); //参数处理(get和post),包括xhr.open get:拼接好url再open post:先open,再设置其他参数 if (ajaxSetting.data) { switch (ajaxSetting.contentType) { case '': tool.each(tool.MergeObject(ajaxSetting.data, ajaxSetting.publicData), function (item, index) { sendData += (index + "=" + item + "&") }); sendData = sendData.slice(0, -1); ajaxSetting.requestHeader['Content-Type'] = 'application/x-www-form-urlencoded' break case 'json': sendData = JSON.stringify(tool.MergeObject(ajaxSetting.data, ajaxSetting.publicData)) ajaxSetting.requestHeader['Content-Type'] = 'application/json' break case 'form': if (!tool.isEmptyObject(ajaxSetting.publicData)) { tool.each(ajaxSetting.publicData, function (item, index) { ajaxSetting.data.append(index, item) }) } sendData = ajaxSetting.data break } //请求前处理参数 sendData = ajaxSetting.transformRequest(sendData) //判断请求类型 if (ajaxSetting.type === 'get') { xhr.open(ajaxSetting.type, tool.checkRealUrl(ajaxSetting) + '?' + sendData, ajaxSetting.async) } else { xhr.open(ajaxSetting.type, tool.checkRealUrl(ajaxSetting), ajaxSetting.async) } } else { xhr.open(ajaxSetting.type, ajaxSetting.baseURL + ajaxSetting.url, ajaxSetting.async) } xhr.responseType = ajaxSetting.responseType; //设置超时时间(只有异步请求才有超时时间) ajaxSetting.async ? (xhr.timeoutEvent = ajaxSetting.timeout) : (null); //设置http协议的头部 tool.each(ajaxSetting.requestHeader, function (item, index) { xhr.setRequestHeader(index, item) }); //onload事件(IE8下没有该事件) xhr.onload = function (e) { if (this.status == 200 || this.status == 304) { /* * ie浏览器全系列不支持responseType='json',所以在ie下使用JSON.parse进行转换 * */ if (ajaxSetting.responseType === 'json') { if (isNaN(tool.getIEVersion())) { ajaxSetting.successEvent(ajaxSetting.transformResponse(xhr.response)); } else { ajaxSetting.successEvent(ajaxSetting.transformResponse(JSON.parse(xhr.responseText))); } } else { ajaxSetting.successEvent(ajaxSetting.transformResponse(xhr.response)); } } else { /* * 这边为了兼容IE8、9的问题,以及请求完成而造成的其他错误,比如404等 * 如果跨域请求在IE8、9下跨域失败不走onerror方法 * 其他支持了Level 2 的版本 直接走onerror * */ ajaxSetting.errorEvent(e.currentTarget.status, e.currentTarget.statusText); } }; //xmlhttprequest每次变化一个状态所监控的事件(可拓展) xhr.onreadystatechange = function () { switch (xhr.readyState) { case 1://打开 //do something break; case 2://获取header //do something break; case 3://请求 //do something break; case 4://完成 //在ie8下面,无xhr的onload事件,只能放在此处处理回调结果 if (xhr.xhr_ie8) { if (xhr.status == 200 || xhr.status == 304) { if (ajaxSetting.responseType == "json") { ajaxSetting.successEvent(ajaxSetting.transformResponse(JSON.parse(xhr.responseText))) } } } break; } ; }; //ontimeout超时事件 xhr.ontimeout = function (e) { ajaxSetting.timeoutEvent("000000", e ? (e.type) : ("timeoutEvent")); //IE8 没有e参数 xhr.abort(); //关闭请求 }; //错误事件,直接ajax失败,而不走onload事件 xhr.onerror = function (e) { ajaxSetting.errorEvent(e) //错误日志上传 // tool.uploadAjaxError({ // errInfo: e.currentTarget.statusText, // errUrl: tool.checkRealUrl(ajaxSetting), // errLine: e.currentTarget.status, // Browser: navigator.userAgent // }) }; //发送请求 xhr.send(ajaxSetting.type === 'get' ? '' : sendData); ### 测试代码 #### 前端同源测试代码 ajax.post("/api/ajax1/ajaxT1/",{"name":"测试异步post请求","age":"success"},function(data){alert(data)}); //该接口在1122上 #### 前端跨域测试代码 ajax.post("http://192.168.0.3:2211/api/weixin/ajaxT2/",{"name":"测试跨域post请求","age":"success"},function(data){alert(data)}); #### 后端跨域接口代码 /// /// 测试跨域请求 /// /// /// [Route("ajaxT2")] public String kuaAjaxT2([FromBody]TModule module) { String result = "跨域post传输成功:"+module.name+"-"+module.age; return result; } #### 后端同源测试代码 /// /// 测试ajax同源请求 /// /// /// result [Route("ajaxT2")] public String GetkuaAjaxT1(string name,string age) { String result = "1J跨域成功:" + name + "-" + age; return result; } ### 以下是各种浏览器的测试结果(仅提供同源post请求和跨域post请求) ##### 同源测试 ###### chrome ![](http://images2015.cnblogs.com/blog/801930/201611/801930-20161129230238927-2089656702.png) ###### IE8-9 ![](http://images2015.cnblogs.com/blog/801930/201611/801930-20161129230248709-1923043215.png) ![](http://images2015.cnblogs.com/blog/801930/201611/801930-20161129230306302-1703939611.png) ###### IE10+ ![](http://images2015.cnblogs.com/blog/801930/201611/801930-20161129230347068-1928619242.png) ###### firefox ![](http://images2015.cnblogs.com/blog/801930/201611/801930-20161129230452615-113489743.png) ###### opera ![](http://images2015.cnblogs.com/blog/801930/201611/801930-20161129230611365-1169854535.png) ###### safari ![](http://images2015.cnblogs.com/blog/801930/201611/801930-20161129230713615-2040676482.png) ###### edge ![](http://images2015.cnblogs.com/blog/801930/201611/801930-20161129230814818-1130849183.png) ##### 跨域测试 ###### chrome ![](http://images2015.cnblogs.com/blog/801930/201611/801930-20161129230913599-1189375449.png) ###### IE8-9 ![](http://images2015.cnblogs.com/blog/801930/201611/801930-20161129230942302-1611540664.png) ![](http://images2015.cnblogs.com/blog/801930/201611/801930-20161129230956990-1637046338.png) ###### IE10+ ![](http://images2015.cnblogs.com/blog/801930/201611/801930-20161129231021209-1271264367.png) ###### firefox ![](http://images2015.cnblogs.com/blog/801930/201611/801930-20161129231044943-1370903842.png) ###### opera ![](http://images2015.cnblogs.com/blog/801930/201611/801930-20161129231104552-1637987456.png) ###### safari ![](http://images2015.cnblogs.com/blog/801930/201611/801930-20161129231127834-855103713.png) ###### edge ![](http://images2015.cnblogs.com/blog/801930/201611/801930-20161129231145693-678151401.png) #### 1.4 版本更新 --- 轮询技术的实现(需要后台接口支持) /* * 长轮询的实现 * param: type 请求类型 * url 请求接口地址 * data 请求参数 * successEvent(data,this) 成功事件处理 如果得到正确数据,则让轮询停止,则在第二个回调参数设置stop属性就好 * timeFrequency 每隔多少时间发送一次请求 * errorEvent 错误事件 * timeoutEvent 超时处理 * */ longPolling: function (type, url, data, successEvent, timeFrequency, errorEvent, timeoutEvent) { var ajaxParam = { type: type, url: url, data: data, successEvent: function (dateCall) { successEvent(dateCall, this); if (!this.stop) { setTimeout(function () { tempObj.longPolling(type, url, data, successEvent, timeFrequency, errorEvent, timeoutEvent); }, timeFrequency); } ; }, //如果走了error说明该接口有问题,没必要继续下去了 errorEvent: errorEvent, timeoutEvent: function () { timeoutEvent(); setTimeout(function () { tempObj.longPolling(type, url, data, successEvent, timeFrequency, errorEvent, timeoutEvent) }, timeFrequency); } }; ajax.common(ajaxParam); }, > 考虑到业务需求 >> 聊天系统会要一直需求轮询,不间断的向后台使用数据,所以isAll = true >> 等待付款业务只需要得到后台一次响应是否支付成功,所以使用回调参数中的第二个参数的stop属性,结束轮询 #### 1.5.0版本更新 --- ajax的上传文件技术 /* * ajax上传文件 -- level2的新特性,请保证你的项目支持新的特性再使用 * url 文件上传地址 * fileSelector input=file 选择器 * size 文件限制大小 * fileType 文件限制类型 mime类型 * successEvent 上传成功处理 * errorEvent 上传失败处理 * timeoutEvent 超时处理 * * return: status: 0 请选择文件 * 1 超出文件限制大小 * 2 非允许文件格式 * */ upload: function (url, fileSelector, size, fileType, successEvent, errorEvent, timeoutEvent) { var formdata = new FormData(), fileNode = document.querySelector(fileSelector), fileCount = fileNode.files.length, data = {}, result = {}; //以下为上传文件限制检查 if (fileCount > 0) { tool.each(Array.prototype.slice.call(fileNode.files), function (value) { //检查文件大小 if (value.size > size) { result["status"] = 1; result["errMsg"] = "超出文件限制大小"; } else { if (fileType != "*") { //检查文件格式.因为支持formdata,自然支持数组的indexof(h5) if (fileType.indexOf(value.type) === -1) { result["status"] = 2; result["errMsg"] = "非允许文件格式"; } else { formdata.append(value.name, value); } ; } else { formdata.append(value.name, value); } } ; }); } else { result["status"] = 0; result["errMsg"] = "请选择文件"; } ; if (result.status !== undefined) return result; //如果有错误信息直接抛出去,结束运行 var ajaxParam = { type: "post", url: url, data: formdata, contentType: 'form', successEvent: successEvent, errorEvent: errorEvent, timeoutEvent: timeoutEvent }; ajax.common(ajaxParam); }, 如果想要看文件上传具体内容和测试各种结果,请转到这片博客:http://www.cnblogs.com/GerryOfZhong/p/6274536.html #### 1.5.1版本更新 --- ajax的大文件/超大文件上传技术 (需后台配合) /* * ajax大文件切割上传(支持单个文件) -- level2的新特性,请保证你的项目支持新的特性再使用 * url 文件上传地址 * fileSelector input=file 选择器 * cutSize 切割文件大小 * fileType 文件限制类型 mime类型 * successEvent 上传成功处理 * progressEvent 上传进度事件 * errorEvent 上传失败处理 * timeoutEvent 超时处理事件 * * return: status: 0 请选择文件 * 1 非允许文件格式 * */ upload_big: function (url, fileSelector, cutSize, fileType, successEvent, progressEvent, errorEvent, timeoutEvent) { var file = document.querySelector(fileSelector).files, result = {}; //以下为上传文件限制检查 if (file.length === 1) { if (fileType != "*") { if (fileType.indexOf(file.type) === -1) { result["status"] = 1; result["errMsg"] = "非允许文件格式"; } } } else { result["status"] = 0; result["errMsg"] = "请选择文件/只能上传一个文件"; } ; if (result.status !== undefined) return result; //如果有错误信息直接抛出去,结束运行 //判断上传文件是否超过需要切割的大小 if (file[0].size > cutSize) { var fileArr = tool.cutFile(file[0], cutSize); //切割文件 cutFile_upload(fileArr); } else { var fileForm = new FormData(); fileForm.append("name", file[0].name); fileForm.append("file".name, file[0]); fileForm.append("count", 1); fileForm.append("cutSize", cutSize); var ajaxParam = { type: "post", url: url, data: fileForm, contentType: 'form', successEvent: function (data) { successEvent(data); progressEvent(1, 1); }, errorEvent: errorEvent, timeoutEvent: timeoutEvent }; ajax.common(ajaxParam); } ; /* * 切割文件上传,配合后台接口进行对接 * 传输参数: * count -- 当前传输part的次数 * name -- 做过处理的文件名称 * file -- 上传的.part的切割文件 * isLast -- 是否为最后一次切割文件上传(默认值:"true" 字符串,只有最后一次才附加) * */ function cutFile_upload(fileArr, count) { var formData = new FormData(); if (count == undefined) { count = 0; formData.append("name", fileArr[0].name); formData.append("file".name, fileArr[0].file); } else { if (count === fileArr.length - 1) { formData.append("isLast", "true") } ; formData.append("name", fileArr[count].name); formData.append("file".name, fileArr[count].file); } formData.append("count", count); formData.append("cutSize", cutSize); ; var ajaxParam = { type: "post", url: url, data: formData, contentType: 'form', successEvent: function (data) { /* * data 参数设置 需要后台接口配合 * 建议:如果后台成功保存.part文件,建议返回下次所需要的部分,比如当前发送count为0,则data返回下次为1。 * 如果保存不成功,则可false,或者返回错误信息,可在successEvent中处理 * * */ progressEvent(count + 1, fileArr.length); //上传进度事件,第一个参数:当前上传次数;第二个参数:总共文件数 var currCount = Number(data); if (currCount) { if (currCount != fileArr.length) { cutFile_upload(fileArr, currCount); } ; } ; successEvent(data); //成功处理事件 }, errorEvent: errorEvent, timeoutEvent: timeoutEvent }; ajax.common(ajaxParam); } } 如果想要看文件上传具体内容和测试各种结果,请转到这片博客:http://www.cnblogs.com/GerryOfZhong/p/6295211.html 备注:ajax的上传技术,在es5+之后支持,浏览器的兼容性就是除了IE10以下,大部分都支持了 #### 1.6版本更新 --- 集成promise规范,更优雅操作异步(主要增加了promise代码) createPromise:function(){ var newPromise = function(fn){ var promise = this; //状态机的状态 var PROMISESTATE = { PENDING : 0 , FULFILLED : 1 , REJECTED : 2 }; //存储当前变量的回调函数和标记对象为promise promise._fullCalll =[],promise._rejCall = [];promise._name = "promise"; //执行过程中的状态变化(初始化状态为默认状态) var _state = PROMISESTATE.PENDING; //回调函数的参数 var _value = undefined; //状态变更 function setState(stateT,valueT){ var promise = this; _state = stateT; _value = valueT; handleFun.call(promise); //传递作用域,并且执行回调函数 }; //根据状态处理回调 function handleFun(){ var promise = this,isThen; if (_state === PROMISESTATE.FULFILLED && typeof promise._fullCalll[0] === 'function') { isThen = promise._fullCalll[0](_value); }; if (_state === PROMISESTATE.REJECTED && typeof promise._rejCall[0] === 'function') { isThen = promise._rejCall[0](_value); }; //对于是否可以继续进行then做判断 // 1. 不可then的,直接return结束(条件:无返回值、返回值不是promise对象的) // 2. 对于可以then的,将then的回调进行处理,然后对象之间传递。 if (isThen === undefined || !(typeof isThen === 'object' && isThen._name === 'promise')) return; promise._fullCalll.shift(); promise._rejCall.shift(); //清除当前对象使用过的对调 isThen._fullCalll =promise._fullCalll;isThen._rejCall = promise._rejCall; //将剩下的回调传递到下一个对象 }; //promimse入口 function doResolve(fn){ var promise = this; fn(function(param) { setState.call(promise,PROMISESTATE.FULFILLED,param); }, function(reason) { setState.call(promise,PROMISESTATE.REJECTED,reason); }); }; //函数then,处理回调,返回对象保证链式调用 this.then = function(onFulfilled,onRejected) { this._fullCalll.push(onFulfilled); this._rejCall.push(onRejected); return this; } doResolve.call(promise,fn); }; window.Promise = newPromise; }, 如果想要看文件上传具体内容和测试各种结果,请转到这片博客:http://www.cnblogs.com/GerryOfZhong/p/7096792.html #### 1.7版本更新 --- 全局配置、请求格式拓展和优化、请求二进制类型、浏览器错误搜集以及npm打包发布 由于内容比较多,比较难粘贴代码,如果想要看具体内容和测试各种结果,请转到博客 博客园:http://www.cnblogs.com/GerryOfZhong/p/7575820.html 知乎:https://zhuanlan.zhihu.com/p/29640921 #### 1.8版本更新 --- 增加请求错误监控、前端负载均衡、宕机切换、以及迭代问题修复 此次对于请求的错误监控进行了搜集,前端的负载均衡,将流量打到不同的服务器上,以及在请求宕机之后的备份切换 每次迭代都会对新的代码做测试讲解,所以篇幅比较多,so 博客园:https://www.cnblogs.com/GerryOfZhong/p/9245510.html 知乎:https://zhuanlan.zhihu.com/p/38685979 #### 1.9版本更新 --- 设计请求池,复用请求,让前端通信快、更快、再快一点 本次包括了设计阶段,摸索阶段,实现阶段,最后全面覆盖测试阶段(包括数据搜集清洗),还有与主流前端通信框架进行对比PK阶段。 博客园:https://www.cnblogs.com/GerryOfZhong/p/9630569.html 知乎:https://zhuanlan.zhihu.com/p/44477183 #### 具体代码已封装成一个js库,下面为API库 * 全局配置参数 -- ajax.config * get请求 -- ajax.get * post请求 -- ajax.post * post(参数为json)请求 -- ajax.postJSON * promise请求 -- ajax.promiseAjax * postForm请求 -- ajax.postFormData * 获取blob数据集 -- ajax.obtainBlob * 轮询请求 -- ajax.longPolling * 上传文件请求 -- ajax.upload * 大文件切割上传请求 -- ajax.upload_big * 通用配置请求 -- ajax.common #### 最近在研究原声js的ajax的技术和设计方案,总体说来还是有很大的收获的,对浏览器的了解,js的了解,服务器技术的了解,后端的温习还是有很大的进步的,特别是解决问题的能力,感觉又上了一个level。技术的未来,不会远... ### 版本更新介绍 1. 跨域不需要在前端设置跨域请求报文头,现已删除 ==>author:keepfool from cnblog 2. 更新tool一些方法,拥抱es5+新技术 ==>author:pod4g from github 3. 删除头部参数中的跨域参数设置 ==>author:wYhooo from github 4. 集成ajax的轮询技术 5. 集成ajax的上传文件技术,也更新了一些原来的代码 a. 增加FormData数据传输方法 b. 新增各种类型判断方法判断类型 c. 更新each方法,判断如果传入参数obj为数组而且浏览器支持es5的新特性直接用数组的forEach方法 6. 更新bug,更细ajax默认值相互影响问题,调试ajax长轮询bug 7. 集成promise规范,更优雅操作异步 8. 全局配置、请求格式拓展和优化、请求二进制类型、浏览器错误搜集以及npm打包发布 #### 程序员的小笑话 ![](http://images2015.cnblogs.com/blog/801930/201612/801930-20161210143609882-1515246004.gif) #### 个人介绍 * 性别:男 * 爱好:女 * 近期目标:前端架构师 * 职业目标:全栈架构师 * 博客园:http://www.cnblogs.com/GerryOfZhong * 知乎:https://www.zhihu.com/people/zhong-qiang-51-33/activities