一、问题描述:
实现一个LazyMan,可以按照以下方式调用:
(1)LazyMan(“Hank”)输出:
Hi! This is Hank!(2)LazyMan(“Hank”).sleep(10).eat(“dinner”)
输出:
Hi! This is Hank!
//等待10秒..
Wake up after 10
Eat dinner~(3)LazyMan(“Hank”).eat(“dinner”).eat(“supper”)
输出:
Hi This is Hank!
Eat dinner~
Eat supper~(4)LazyMan(“Hank”).sleepFirst(5).eat(“supper”)
输出:
//等待5秒
Wake up after 5
Hi This is Hank!
Eat supper以此类推。
二、思路:
关键是输出的顺序,以及需要在每一个任务执行完再执行下一步,类似promise(可以使用promise的方式实现),使用队列也可以。链式调用则要求每个方法都得返回当前对象。
- 看题目输出示例,可以确定这是拟人化的输出,也就是说:应该编写一个类来定义一类人,叫做LazyMan。可以输出名字、吃饭、睡觉等行为。
- 从输出的句子可以看出,sleepFrist的优先级是最高的,其他行为的优先级一致。明显我们需要一个任务队列,将
sleepFirst
放在最前面; - 从三个例子来看,都得先调用LazyMan来初始化一个人,才能继续后续行为,所以LazyMan是一个接口。
- 句子是按调用方法的次序进行顺序执行的,是一个队列。
注意:
- 执行任务不能紧跟在插入任务的后面执行,等所有任务都进队了,才开始执行任务队列。那我们借助
setTimeout
函数,将他们分进两个事件队列就好了; - 一个任务完成了,我们通过尾调用,通知任务队列去取下一个任务;
三、常规方法实现
var LazyMan = function(name) { //LazyMan方法
if (!(this instanceof LazyMan)) {
return new LazyMan(name);
}
this.quene = [];
var self = this;
var fn = function() { //定义打印名字的方法fn
console.log('Hi! This is ' + name);
self.next();
};
this.quene.push(fn); //将方法fn入队
//通过settimeout的方法,将执行函数放入下一个事件队列中,从而达到先注册事件,后执行的目的
setTimeout(function() {
self.next();
}, 0);
}
LazyMan.prototype = {//LazyMan的原型
next: function() { //尾调用函数。一个任务执行完再调用队列中的下一个任务
if (this.quene.length) {
var fn = this.quene.shift();
if ((typeof fn).toLowerCase() === 'function') {
fn();
}
}
},
sleep: function(time) {
var self = this;
var fn = function() { //睡觉方法
setTimeout(function() {
console.log('Wake up after ' + time);
self.next();
}, time * 1000);
};
this.quene.push(fn); //将睡觉的方法进队
return this;
},
sleepFirst: function(time) {
var self = this;
var fn = function() {
setTimeout(function() {
console.log('Wake up after ' + time);
self.next();
}, time * 1000);
};
//sleepFirst函数需要最先执行,所以我们需要在任务队列前面放入,然后再执行后面的任务
this.quene.unshift(fn);
return this;
},
eat: function(food) {
var self = this;
var fn = function() {
console.log('Eat ' + food + '~');
self.next();
};
this.quene.push(fn);
return this;
}
};
//LazyMan("Hank")
//LazyMan("Hank").sleep(10).eat("dinner")
//LazyMan("Hank").eat("dinner").eat("supper")
//LazyMan("Hank").sleepFirst(5).eat("supper")
//LazyMan("Hank").next()
四、使用promise实现
参考:https://www.jianshu.com/p/9552ca809035
//1._lazyMan构造函数
function _lazyMan(name){
this.name = name;
this.promises = []; //存放所有的promise操作
var myfunc = () =>{ //返回一个promise
console.log(`Hi this is ${this.name}!`);
return Promise.resolve();
}
this.promises.push(myfunc); //将打印名字的方法放到promises数组
var template = Promise.resolve(); //获取一个Promise对象
setTimeout(()=>{ //箭头函数,使其中的this指向_lazyMan; 不使用将指向window
this.promises.forEach(v => { //遍历所有的promise,并执行
template = template.then(v);
})
},0)
}
//2. lazyMan原型
_lazyMan.prototype = {
sleepFirst: function(time){
var sfpfun = function(){
return new Promise(function(resolve,reject){ //返回Promise对象
setTimeout(()=>{
console.log(`Wake up after ${time}s`);
resolve();
},time*1000);
})
}
this.promises.unshift(sfpfun); //放到队头即可
return this;
},
sleep: function(time){
var spfun = function(){
return new Promise(function(resolve,reject){ //返回Promise对象
setTimeout(()=>{
console.log(`Wake up after ${time}s`);
resolve();
},time*1000);
});
}
this.promises.push(spfun);
return this; //返回整个对象,就能实现链式调用
},
eat: function(food){
var epfun = function(){ //返回Promise对象。保证遍历数组时,执行then操作
console.log(`Eat ${food}~`);
return Promise.resolve();
}
this.promises.push(epfun);
return this;
}
}
//3. 实例化_lazyMan对象
function lazyMan(name){
return new _lazyMan(name);
}
lazyMan('ch').eat('泡芙').eat('冰淇淋').sleepFirst(4).sleep(2);
输出:
Wake up after 4s
Hi this is ch!
Eat 泡芙~
Eat 冰淇淋~
Wake up after 2s
算法分析:
1) 箭头函数
箭头函数是ES6 新推出的形式,值得注意的是在setTimeOut的函数中用到了this,如果是传统函数,这个this指向的是window,并不是_LazyMan,但是如果是箭头函数,this就是指向 _LazyMan
2)setTimeOut
setTimeOut函数并不会直接将回调函数放在事件循环队列中,而是等到定时器时间到了之后才会将函数放到事件循环队列中。正是基于setTimeOut函数的这种特性,所以在_LazyMan构造函数中用setTimeout()去获取promises中数组的数据,此时promises的数据是调用链中的所有Promise对象,如果不用setTimeout函数那么在构造函数中,语句顺序执行,promises里的对象只有构造函数中的一个Promise对象,链式调用函数中存入的Promise对象获取不到。
3) 链式调用
return this :返回整个对象,就能实现链式调用。(在原型的eat、sleep、sleepFirst中)