$.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>
输出结果: