js实现lazyMan

问题描述

实现一个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。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值