你是否發現項目中有很多頁面只用到了框架不到十分之一的內容,還引了壓縮后還有70多kb的jquery庫
你是否發現項目中就用了兩三個underscore提供的方法,其他大部分的你方法你甚至從來沒有看過
你是否發現fetch好像比ajax好用那么一點
你是否想過自己封裝個ajax....
純前端寫得久了,便想折騰點事情。比如先定一個小目標,年前自己寫個類jquery輕量級庫....
那么就從自己封裝一個ajax切入吧,首先我整理的一個思維導圖,一目了然
解析參數數據
通常我們的請求后面會有一些參數,如果是get請求當然可以直接通過'&'拼在url后面。那么post就需要做一下處理了,如果參數是字符串,則將字符串用‘&’符號切割轉化成鍵值對形式,同時用encodeURIComponent轉碼,最后類似於jquery的處理,將/%20/g(空格)替換成'+'。
getData: function(){
var name, value;
if (opts.data) {
if (typeof opts.data === "string") {
opts.data = opts.data.split("&");
for (var i = 0, len = opts.data.length; i < len; i++) {
name = opts.data[i].split("=")[0];
value = opts.data[i].split("=")[1];
opts.data[i] = encodeURIComponent(name) + "=" + encodeURIComponent(value);
}
opts.data = opts.data.replace("/%20/g", "+");
} else if (typeof opts.data === "object") {
var arr = [];
for (var name in opts.data) {
var value = opts.data[name].toString();
name = encodeURIComponent(name);
value = encodeURIComponent(value);
arr.push(name + "=" + value);
}
opts.data = arr.join("&").replace("/%20/g", "+");
}
//使用GET方法或JSONP,則手動添加到URL中
if (opts.type === "GET" || opts.dataType === "jsonp") {
opts.url += opts.url.indexOf("?") > -1 ? opts.data : "?" + opts.data;
}
}
},
創建jsonp
如果dataType為jsonp的話,其實我們就可以理解為不是ajax請求了,而是偽造了一個script標簽,通過script的src屬性相當於發起了一個請求,其中帶了一個callback(這里是名稱叫jsonp_timeName)的參數,最終約定好后台的數據包通過jsonp_name(data)這種形式包裹起來,這樣就相當於前端再回調了一個jsonp_name的方法,將數據通過參數的形式帶過來了,所以前端js需要實現一個名叫jsonp_name的方法。其實jsonp跨域的方法有很多種,比如還有偽造iframe等,跨域詳情解決方案可以參考我的博文
http://www.cnblogs.com/liliangel/p/5760426.html
createJsonp: function(){
var script = document.createElement("script"),
timeName = new Date().getTime() + Math.round(Math.random() * 1000),
callback = "jsonp_" + name;
window[callback] = function(data) {
clearTimeout(ajax.options.timeoutFlag);
document.body.removeChild(script);
try {
data && (data = JSON.parse(data));
} catch (e) {
console.error('ajax error for json parse responseText');
}
ajax.success(data);
}
script.src = url + (url.indexOf("?") > -1 ? "" : "?") + "callback=" + callback;
script.type = "text/javascript";
document.body.appendChild(script);
ajax.timeout(callback, script);
},
創建XHR
首先通過兼容性處理的getXHR()方法得到xhr對象,接着設置請求頭,區分post、get。每當 readyState 改變時,就會觸發 onreadystatechange 事件,readyState 屬性存有 XMLHttpRequest 的狀態信息。這里我相當於重寫readystate事件監聽,來做我自己的相應邏輯處理,由於執行abort()方法后,有可能觸發onreadystatechange事件,所以設置一個ajax.options.timeoutBool標識,來忽略中止觸發的事件。最后調用xhr的send()方法發送出請求,同時渲染timeout()方法。
createXHR: function(){
//創建對象
xhr = ajax.getXHR();
xhr.open(opts.type, opts.url, opts.async);
//設置請求頭
if (opts.type === "POST" && !opts.contentType) {
//若是post提交,則設置content-Type 為application/x-www-four-urlencoded
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
} else if (opts.contentType) {
xhr.setRequestHeader("Content-Type", opts.contentType);
}
//添加監聽
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (opts.timeout !== undefined) {
//由於執行abort()方法后,有可能觸發onreadystatechange事件,所以設置一個ajax.options.timeoutBool標識,來忽略中止觸發的事件。
if (ajax.options.timeoutBool) {
return;
}
clearTimeout(ajax.options.timeoutFlag);
}
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
var responseText = xhr.responseText;
try {
xhr.responseText && (responseText = JSON.parse(responseText));
opts.success(responseText);
} catch (e) {
console.error('ajax error for json parse responseText');
//opts.error(xhr);
}
} else {
opts.error(xhr);
}
}
};
//發送請求
xhr.send(opts.type === "GET" ? null : opts.data);
ajax.timeout(); //請求超時
}
兼容IE6
獲取xhr對象可能會存在低版本ie兼容性問題,為此這樣判斷處理一下
getXHR: function(){
if (window.XMLHttpRequest) {
return new XMLHttpRequest();
} else {
//遍歷IE中不同版本的ActiveX對象
var versions = ["Microsoft", "msxm3", "msxml2", "msxml1"];
for (var i = 0; i < versions.length; i++) {
try {
var version = versions[i] + ".XMLHTTP";
return new ActiveXObject(version);
} catch (e) {
console.log('error ajax',e)
}
}
}
}
設置請求超時
前面我定義了一個全局的屬性timeoutFlag,這里通過settimeout延時函數給它賦值。如果是jsonp,則移除原來追加的script標簽,否則通過全局的xhr條用abort()方法終止正在發送的請求!
timeout: function(callback, script){
if (opts.timeout !== undefined) {
ajax.options.timeoutFlag = setTimeout(function() {
if (opts.dataType === "jsonp") {
delete window[callback];
document.body.removeChild(script);
} else {
ajax.options.timeoutBool = true;
xhr && xhr.abort();
}
}, opts.timeout);
}
},
全局變量
var defaultOpts = {
url: '', //ajax 請求地址
type : 'GET', //請求的方法,默認為GET
data : null, //請求的數據
contentType : '',//請求頭
dataType : 'json', //請求的類型,默認為json
async : true, //是否異步,默認為true
timeout: 5000, //超時時間,默認5秒鍾
before : function() {
console.log('before')
}, //發送之前執行的函數
error: function() {
console.log('error')
}, //錯誤執行的函數
success: function() {
console.log('success')
} //請求成功的回調函數
}
for (i in defaultOpts) {
if (opts[i] === undefined) {
opts[i] = defaultOpts[i];
}
}
var xhr = null;
options: {
timeoutFlag: null, //超時標識
timeoutBool: false //是否請求超時
},
初始化調用
我這里是用面向對象函數方式寫的,核心初始化代碼如下:
init: function(){
opts.before();
ajax.getData();
opts.dataType === "jsonp" ? ajax.createJsonp() : ajax.createXHR();
},
ajax.init();
設置AMD等規范
如果是用requireJS這種方法引用,那么還需要設置一下amd、cmd或者commonJs規范
// AMD && CMD
if(typeof define === 'function'){
define(function(){
return li;
});
// CommonJS
}else if(typeof module !== "undefined" && module !== null){
module.exports = li;
// window
}else{
window.li = li;
}
調用示例
調用示例,我這里只貼出異步的方式,當然還有好幾種情況,比如出錯等,這里不一一展示
默認為gety異步請求,超時時間5秒鍾。before為請求前執行的函數,通常我們可以寫統一的loading動畫。當發生405/500等這種錯誤時,會調用error方法,當然我們通常在success回調函數里做邏輯處理。這里盡量跟jquery庫的ajax封裝保持一致,是為了更符合原有的開發編碼習慣
結果
控制台輸出如下:
我這里用$這個符合作為全局對象引入,也是為了更符合原來用jquery的編碼習慣,但是這里只實現了其中的ajax方法哦,其他項目中高頻出現的方法,以及常用的小組件后續慢慢補充....
ITer,請跟隨興趣一路前行,個人站點www.liliangel.cn,歡迎指導交流

