问题描述
实现一个LazyMan,可以按照以下方式调用:
LazyMan(“Hank”)输出:
Hi! This is Hank!
LazyMan(“Hank”).sleep(10).eat(“dinner”)输出
Hi! This is Hank!
//等待10秒..
Wake up after 10
Eat dinner~
LazyMan(“Hank”).eat(“dinner”).eat(“supper”)输出
Hi This is Hank!
Eat dinner~
Eat supper~
LazyMan(“Hank”).sleepFirst(5).eat(“supper”)输出
//等待5秒
Wake up after 5
Hi This is Hank!
Eat supper
思路分析
1.看题目输出示例,可以确定这是拟人化的输出,也就是说:应该编写一个类来定义一类人,叫做LazyMan。可以输出名字、吃饭、睡觉等行为。
2.从输出的句子可以看出,sleepFrist的优先级是最高的,其他行为的优先级一致。
3.从三个例子来看,都得先调用LazyMan来初始化一个人,才能继续后续行为,所以LazyMan是一个接口。
4.句子是按调用方法的次序进行顺序执行的,是一个队列。
使用观察者模式
我们可以对任务队列使用观察者模式,当执行LazyMan方法的时候,调用订阅方法,将需要执行的信息存入taskList中,缓存起来。存储的信息,会先保留着,等发布方法进行提取,执行和输出。
订阅方法的调用方式设计:subscribe("lazyMan", "Hank")
var taskList = [];
//这里所谓的订阅,其实就是更新lazyMan的任务队列,用订阅来封装
function subscribe(){
var params = {};
var args = Array.prototype.slice.call(arguments);
if(args.length == 0){
throw new Error('subscribe参数不能为空!');
}
//消息名
param.msg = args[0];
//参数列表
param.args = args.slice(1);
if(param.msg == 'sleepFirst'){
//直接在队首插入,其实可以借助优先队列
//unshift和shift方法,低版本浏览器不支持
taskList.unshift(param);
} else {
taskList.push(param);
}
}
//发布方法
//所谓的发布,其实是根据lazyMan的任务队列的顺序一个一个执行任务,用发布来封装
//第一次调用发布的时候,一定要放到浏览器的事件队列中,这样就不会在lazyMan的任务队列还没更新完的时候就开始输出
function publish(){
if(taskList.length > 0){
run(taskList.shift());
}
}
使用优先队列
上文中我们对sleepFirst
的入队进行了unshift插入,我们可以使用优先队列来重写subscribe方法
function subscribe(){
var param = {};
var args = Array.prototype.slice.call(arguments);
param.msg = args[0];
param.args = args.slice(1);
if(param.msg == 'sleepFirst'){
//code代表优先级,这里定义1为最高级
param.code = 1;
} else {
param.code = 2;
}
taskList.push(param);
}
那么相应的,在出队,也就是publish中,要对优先级进行判断
function publish(){
var entry = 0;
for(var i = 0, i = taskList.length; i < len; i++){
if(taskList[i].code < taskList[entry].code){
entry = i;
}
}
//splice既改变了原来的数组,又返回了切割的元素
return taskList.splice(entry, 1);
}
实现lazyMan类
function LazyMan(){
}
LazyMan.prototype = {
sleep: function(num){
subscribe('sleep', num);
return this;
},
eat: function(str){
subscribe('eat', str);
return this;
},
sleepFirst: function(num){
subscribe('sleepFirst', num);
return this;
}
}
实现输出console.log的包装方法
function lazyManLog(str){
console.log(str)
}
为什么还要为console.log包装一层,是因为在实战项目中,产经经常会修改输出提示的UI。如果每一处都用console.log直接调用,那改起来就麻烦很多。(将UI实现封装的必要性)
另外,如果要兼容IE等低级版本浏览器,也可以很方便的修改。
也就是DRY原则(Don’t Repeat Youself)
// 具体方法
function lazyMan(str){
lazyManLog("Hi!This is "+ str +"!");
publish();
}
function eat(str){
lazyManLog("Eat "+ str +"~");
publish();
}
function sleep(num){
setTimeout(function(){
lazyManLog("Wake up after "+ num);
publish();
}, num * 1000);
}
function sleepFirst(num){
setTimeout(function(){
lazyManLog("Wake up after "+ num);
publish();
}, num * 1000);
}
实现run方法(在发布中使用)
function run(option){
var msg = option.msg;
//其实这里的args只有一个参数
var args = option.args;
switch(args){
case 'lazyMan': lazyMan.call(null, args);break;
case 'eat': eat.apply(null, args);break;
case 'sleep': sleep.apply(null, args);break;
case 'sleepFirst': sleepFirst.apply(null, args);break;
default:;
}
}
最后,我们把方法暴露出去
(function(window){
// 暴露接口
window.LazyMan = function(str){
subscribe("lazyMan", str);
setTimeout(function(){
publish();
}, 0);
return new LazyMan();
};
)(window);
//每次调用LazyMan都会先订阅一次lazyMan函数
//然后进行publish,publish要嵌套上setTimeout扔进浏览器的任务队列(这样保证先更新lazyMan的任务队列,再进行第一次发布)
//返回一个LazyMan对象对lazyMan的任务队列进行更新
//到了第一次publish的时候,浏览器线程已经空闲,每调用一次任务队列中的函数,结束之后都再进行了一次publish
LazyMan("Hank").sleep(10).eat("dinner");
优先队列的优化
刚才我们使用的优先队列算法复杂度为n,那么如果我们使用堆这种数据结构来实现优先队列,算法复杂度可以下降到log(n),这里我们演示最大堆
//最大堆的构造函数
function maxHeap(){
this.dataStore = [];
this.count = 0;
}
maxHeap.prototype.shiftup = function(k){
while(k > 1 && this.dataStore[k] > this.dataStore[Math.floor(k / 2)]){
swap(this.dataStore[k], this.dataStore[Math.floor(k / 2)]);
k = Math.floor(k / 2);
}
}
maxHeap.prototype.shiftdown = function(k){
while(2 * k <= count){
//左节点索引
var j = 2 * k;
if(j + 1 <= count && data[j+1] > data[j]){
j += 1;
}
if(data[j] <= data[k]) break;
swap(data[j], data[k]);
k = j;
}
}
maxHeap.prototype.extract = function(){
swap(this.dataStore[1], this.dataStore[this.count]);
count--;
return this.dataStore[1];
}
maxHeap.prototype.insert = function(item){
this.dataStore[++this.count] = item;
this.shiftup(this.count);
}
对于最大堆来说,只要我们每次insert的时候以元素的优先级做为比较依据,那么每次extract的元素都是优先级最高的元素,进出堆的时间复杂度都是logn。