$.Deferred功能描述
以下代碼運行三秒後會在控制檯輸出"執行完畢".
$.Deferred函數運行會生成一個延時對象dtd,該對象本身具有很多屬性比如resolve,reject,progress.其中在一個異步函數中,比如下面代碼中的setTimeout或者ajax回調函數裏,如果執行dtd.resolve(),則會立刻執行done裏面的函數回調.dtd.reject()會觸發fail裏面的函數回調.dtd.progress()會觸發notify裏面的函數回調.如果想詳細瞭解$.Deferred延時對象的其他用法可參照jquery文檔.
var wait = function(){
var dtd = $.Deferred(); // 生成Deferred對象
var task = function(){
dtd.resolve('執行完畢');
};
setTimeout(task,3000);
return dtd;
};
$.when(wait())
.done(function(msg){ console.log(msg); })
.fail(function(){ alert("出錯啦!"); });
需求分析
下面我們就來實現一下$.Deferred的核心代碼.從上面的調用方式看$.Deferred會返回一個延時對象dtd,dtd上面有resolve,reject和progress方法,由此我們可以確定$.Deferred()的返回值是一個帶有上面三個方法的對象.緊接着我們可以使用上節課所學到的$.Callback函數來實現此需求.對於resolve,reject和progress這三種函數對應的狀態分別創立三個隊列,而後面的done,fail的功能就是往這個隊列中執行add()(收集函數),等到異步的邏輯調用完成時例如調用dtd.resolve(),那麼就對resolve這個狀態的隊列執行fire操作依次運行此隊列的函數,如此變完成了延遲加載函數的需求.
代碼實現
將前面講述的jquery源碼系列的第一課的extend函數和第三課的$.Callbacks函數代碼實現拷貝到下面文件中,新建一個Deferred函數並暴露在$對象下,Deferred函數的代碼便是延遲功能的核心部分.
按照前面所提到的在Deferred函數中新建一個dfr空對象,並將它作爲函數的返回值返回.另外需要新建一個promise對象,它裏面有state和promise兩個方法,其中state()方法調用是爲了獲取當前的狀態,也就是對應着函數裏形成的閉包變量let state,另外通過遍歷arr數組給promise加上三種隊列的add方法.而在dfr對象上掛載隊列的fire方法,最後將promise上的所有屬性和方法掛載到dfr上.
$.when(dfr)返回值就是Deferred函數定義的變量promise,所以它擁有done和fail這些方法來完成函數收集並裝載到對應的隊列中,dfr和promise這兩個對象共同操作在函數內部形成閉包的三個隊列,所以dfr一旦執行resolve,reject和progress函數就會將隊列上收集到的函數依次執行.
代碼如下:
(function (root) {
/**
* 生成配置
*/
function genConfig(params) {
const array = params.split(/\s+/);
const object = {};
array.forEach((column) => {
object[column] = true;
});
return object;
}
function callback(params) {
const options = typeof params === 'string' ? genConfig(params) : {};
const list = [];
let i, fired, start_index; //fired用來記錄是否已經使用fire函數觸發過
let memory;
function fireHandler(context, parameter) {
fired = true;
memory = options['memory'] && parameter;
i = start_index ? start_index : 0;
for (; i < list.length; i++) {
if (
list[i].apply(context, parameter) === false &&
options['topOnFalse']
) {
break;
}
}
start_index = null;
}
const result = {
add: function () {
const fn_list = Array.prototype.slice.call(arguments);
fn_list.forEach((fn) => {
if (toString.call(fn) === '[object Function]') {
if (!list.includes(fn) || !options['unique']) {
list.push(fn);
if (options['memory'] && fired) {
start_index = list.length - 1;
fireHandler(result, memory);
}
}
}
});
},
fire: function () {
if (!options['once'] || !fired) {
const parameter = Array.prototype.slice.call(arguments);
fireHandler(result, parameter);
}
},
};
return result;
}
/**
* 實現延遲對象功能
*/
function Deferred() {
const arr = [
['resolve', 'done', callback('once memory'), 'resolved'],
['reject', 'fail', callback('once memory'), 'rejected'],
['progress', 'notify', callback('memory')],
];
let state = "pending";
const dfr = {};
const promise = {
promise(dfr = null) {
return dfr === null ? promise : extend(dfr, promise);
},
state(){
return state;
}
};
arr.forEach((item) => {
const stateString = item[3];
const list = item[2];
if(stateString){
list.add(function (){
state = stateString;
})
}
dfr[item[0]] = function () {
if (state !== 'pending') {
return false;
}
list.fire.apply(this, Array.prototype.slice.call(arguments));
};
promise[item[1]] = function () {
const array = Array.prototype.slice.call(arguments);
array.length > 0 ? list.add(array[0]) : null;
return promise;
};
});
promise.promise(dfr);
return dfr;
}
const extend = function () {
var target = arguments[0] || {};
var i = 1,
isDeep = false,
isArrayData = false;
if (typeof arguments[0] === 'boolean') {
//第一個參數是boolean型
isDeep = arguments[0] ? true : false;
target = arguments[1] || {};
i = 2;
}
var arr = Array.prototype.slice.call(arguments, i);
if (arguments.length == 1) {
target = this;
arr = [arguments[0]];
}
arr.forEach((obj) => {
for (let key in obj) {
if (isDeep) {
//深拷貝
let src = obj[key];
let des = target[key];
let copy;
if (
jQuery.isPlainObject(src) ||
(isArrayData = jQuery.isArray(src))
) {
//src是數組或者對象類型
if (isArrayData) {
copy = jQuery.isArray(des) ? des : [];
} else {
copy = jQuery.isPlainObject(des) ? des : {};
}
target[key] = jQuery.fn.extend(isDeep, copy, src); //這一句代碼是精髓之處
} else {
target[key] = obj[key];
}
isArrayData = false;
} else {
target[key] = obj[key];
}
}
});
return target;
};
const $ = {
Callbacks: callback,
Deferred: Deferred,
when(defferd) {
return defferd.promise();
},
};
root.$ = $;
})(window);
結果驗證
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
<script src="./lib/deferred.js"></script>
</head>
<body>
</body>
<script>
var wait = function () {
var dtd = $.Deferred(); // 生成Deferred對象
var tasks = function () {
dtd.resolve('執行完畢');
dtd.resolve('執行完畢');
dtd.reject();
};
for (i = 0; i < 10; i++) {
(() => {
var j = i;
setTimeout(() => {
dtd.progress(j * 10);
}, i * 1000);
})();
}
setTimeout(tasks, 10000);
return dtd;
};
$.when(wait()).done(function (msg) {
console.log(msg);
})
.fail(function () {
console.log('發生了錯誤');
})
.notify(function (data) {
console.log(data + '%');
});
</script>
</html>
輸出結果: