javascript设计模式-迭代器模式(Iterator)

   1 <!doctype html>
   2 <html lang="en">
   3 <head>
   4     <meta charset="UTF-8">
   5     <title>迭代器模式</title>
   6 </head>
   7 <body>
   8     
   9 <script>
  10 /**
  11  * 迭代器模式
  12  *
  13  * 定义:
  14  * 提供一种方法顺序访问一个聚合对象中各个元素,而又不需要暴露该对象的内部表示。
  15  *
  16  * 本质:
  17  * 控制访问聚合对象中的元素
  18  *
  19  * 所谓聚合是指一组对象的组合结构。
  20  *
  21  * 一.功能
  22  * 迭代器模式的功能主要在于提供对聚合对象的迭代访问。迭代器就围绕着这个“访问”做文章,延伸出很多的功能。比如:
  23  * 1.以不同的方式遍历聚合对象,比如向前,向后等。
  24  * 2.对同一个聚合同时进行多个遍历。
  25  * 3.以不同的遍历策略来遍历聚合,比如是否需要过滤等。
  26  * 4.多态迭代,含义是:为不同的聚合结构提供统一的迭代接口,也就是说通过一个迭代接口可以访问不同的聚合结构,这就叫做多态迭代。事实上,标准的迭代模式实现基本上都是支持多态迭代的
  27  *
  28  * 二,关键思想
  29  * 聚合对象的类型很多,如果对聚合对象的迭代访问跟聚合对象本身融合在一起的话,会严重影响到聚合对象的可扩展性和可维护性。
  30  * 因此迭代器模式的关键思想就是把对聚合对象的遍历和访问从聚合对象中分离出来,放入单独的迭代器中。这样聚合对象会变得简单一些,而且迭代器和聚合对象可以独立地变化和发展,会大大加强系统的灵活性。
  31  *
  32  * 三,内部迭代器和外部迭代器
  33  * 所谓内部迭代器,指的是由迭代器自己来控制迭代下一个元素的步骤,客户端无法干预。因此,如果想要在迭代的过程中完成工作的话,客户端就需要把操作传递给迭代器。迭代器在迭代的时候会在每个元素上执行这个操作,即回调。
  34  * 所谓外部迭代,指的是客户端来控制迭代下一个元素的步骤,客户端必须显式地调用next来迭代下一个元素。
  35  * 总体来说外部迭代器比内部迭代器更灵活一些。
  36  */
  37 
  38 // 示例代码
  39 
  40 (function(){
  41 /**
  42  * 迭代器实现对象,示意的是聚合对象为数组的迭代器
  43  * 不同聚合对象相应的迭代器实现是不一样的
  44  * @param {Array} aggregate [聚合对象]
  45  */
  46 var Iterator = function(aggregate){
  47     this.aggregate = aggregate;
  48     // 当前索引位置
  49     this.index = -1;
  50 };
  51 Iterator.prototype = {
  52     first: function(){
  53         this.index = 0;
  54     },
  55     next: function(){
  56         if(this.index < this.aggregate.size()) {
  57             this.index++;
  58         }
  59     },
  60     isDone: function(){
  61         return this.index === this.aggregate.size();
  62     },
  63     currentItem: function(){
  64         return this.aggregate.get(this.index);
  65     }
  66 };
  67 
  68 var Aggregate = function(ss){
  69     this.ss = ss;
  70 };
  71 Aggregate.prototype = {
  72     createIterator: function(){
  73         return new Iterator(this);
  74     },
  75     get: function(index){
  76         var retObj = null;
  77         if(index < this.ss.length) {
  78             retObj = this.ss[index];
  79         }
  80 
  81         return retObj;
  82     },
  83     size: function(){
  84         return this.ss.length;
  85     }
  86 };
  87 
  88 new function(){
  89     var names = ['张三', '李四', '王五'];
  90     var aggregate = new Aggregate(names);
  91     var it = aggregate.createIterator();
  92     var obj;
  93 
  94     it.first();
  95     while(!it.isDone()) {
  96         obj = it.currentItem();
  97         console.log('the obj === ' + obj);
  98         it.next();
  99     }
 100 }();
 101 
 102 }());
 103 
 104 (function(){
 105     // 实现实例
 106     
 107     // 工资表数据的整合
 108     /*
 109     项目的客户方收购了一家小公司,这家小公司有自己的工资系统,现在需要整合到客户方已有的工资系统中。
 110     两方的工资系统数据结构可能不同,但用来描述工资的数据模型是差不多的。
 111      */
 112     
 113     var Iterator = function(aggregate){
 114         this.aggregate = aggregate;
 115         this.index = -1;
 116     };
 117     Iterator.prototype = {
 118         first: function(){
 119             this.index = 0;
 120         },
 121         next: function(){
 122             if(this.index < this.aggregate.size()) {
 123                 this.index++;
 124             }
 125         },
 126         isDone: function(){
 127             return this.index === this.aggregate.size();
 128         },
 129         currentItem: function(){
 130             return this.aggregate.get(this.index);
 131         }
 132     };
 133 
 134 
 135     // 工资描述模型对象
 136     var PayModel = function(){
 137         // 支付工资的人员
 138         this.userName;
 139         // 支付的工资数额
 140         this.pay;
 141     };
 142     PayModel.prototype = {
 143         getUserName: function(){
 144             return this.userName;
 145         },
 146         setUserName: function(userName){
 147             this.userName = userName;
 148         },
 149         getPay: function(){
 150             return this.pay;
 151         },
 152         setPay: function(pay){
 153             this.pay = pay;
 154         },
 155         toString: function(){
 156             return 'userName = ' + this.userName + ', pay = ' + this.pay;
 157         }
 158     };
 159 
 160     // 客户方已有的工资管理对象
 161     var PayManager = function(){
 162         this.list = [];
 163     };
 164     PayManager.prototype = {
 165         createIterator: function(){
 166             return new iterator(this);
 167         },
 168         get: function(index){
 169             var ret = null;
 170             if(index < this.list.length) {
 171                 ret = this.list[index];
 172             }
 173 
 174             return ret;
 175         },
 176         size: function(){
 177             return this.list.length;
 178         },
 179 
 180         // 计算工资,其实应该有很多参数,为了演示从简
 181         calcPay: function(){
 182             var pm1 = new PayModel();
 183             pm1.setPay(3800);
 184             pm1.setUserName('张三');
 185 
 186             var pm2 = new PayModel();
 187             pm2.setPay(5800);
 188             pm2.setUserName('李四');
 189 
 190             this.list.push(pm1);
 191             this.list.push(pm2);
 192         }
 193     };
 194 
 195     // 被客户方收购的那个公司的工资管理类
 196     var SalaryManager = function(){
 197         this.pms = [];
 198     };
 199     SalaryManager.prototype = {
 200         // 获取工资列表
 201         getPays: function(){
 202             return this.pms;
 203         },
 204         // 计算工资
 205         calcSalary: function(){
 206             var pm1 = new PayModel();
 207             pm1.setPay(2200);
 208             pm1.setUserName('王五');
 209 
 210             var pm2 = new PayModel();
 211             pm2.setPay(3600);
 212             pm2.setUserName('赵六');
 213 
 214             this.pms.push(pm1);
 215             this.pms.push(pm2);
 216         }
 217     };
 218 
 219     new function(){
 220         var payManager = new PayManager();
 221         payManager.calcPay();
 222         var it = payManager.createIterator();
 223         console.log('集团工资列表:');
 224         var pm;
 225         it.first();
 226         while(!it.isDone()){
 227             pm = it.currentItem();
 228             console.log('ths obj === ' + pm);
 229             it.next();
 230         }
 231 
 232         var salaryManager = new SalaryManager();
 233         salaryManager.calcSalary();
 234         var pms = salaryManager.getPays();
 235         console.log('新收购的公司工资列表:');
 236         for(var i = 0; i < pms.length; i++) {
 237             console.log(pms[i]);
 238         }
 239     }();
 240 
 241 }());
 242 
 243 (function(){
 244     // 带迭代策略的迭代器示例
 245     /*
 246     在实现过滤功能的迭代器中,又有两种常见的需要过滤的情况,一种是对数据进行整条过滤,比如只能查看自己部门的数据;另外一种情况是数据进行部分过滤,比如某些人不能查看工资数据。
 247     带迭代策略的迭代器实现的一个基本思路,就是先把聚合对象的聚合数据获取到,并存储到迭代器中,这样迭代器就可以按照不同的策略来迭代数据了。
 248      */
 249     var Iterator = function(aggregate){
 250         this.pms = [];
 251         this.index = 0;
 252 
 253         // 在这里先对聚合对象的数据进行过滤
 254         var tempCol = [];
 255         var i;
 256         for(i in aggregate) {
 257             if(!aggregate.hasOwnProperty(i)) continue;
 258 
 259             if(aggregate[i].getPay() < 3000) {
 260                 tempCol.push(aggregate[i]);
 261             }
 262         }
 263 
 264         this.pms = [];
 265         for(i = 0; i < tempCol.length; i++) {
 266             this.pms[i] = tempCol[i];
 267         }
 268     };
 269     Iterator.prototype = {
 270         hasNext: function(){
 271             return this.index <= (this.pms.length - 1);
 272         },
 273         next: function(){
 274             var ret = null;
 275             if(this.hasNext()) {
 276                 ret = this.pms[this.index++];
 277             }
 278 
 279             // 在这里对要返回的数据进行过滤,比如不让查看工资数据
 280             if(ret) ret.setPay(0.0);
 281 
 282             return ret;
 283         },
 284         remove: function(){}
 285     };
 286 
 287 /*
 288 谁定义遍历算法的问题
 289 
 290 在迭代器模式的实现中,常见的有两个地方可以来定义遍历算法,一个是聚合对象本身,另外一个就是迭代器负责遍历算法。
 291 
 292 在聚合对象本身定义遍历算法,通常会在遍历过程中,用迭代器来存储当前迭代的状态这种迭代器被称为游标,因为它仅用来指示当前的位置。
 293 
 294 在迭代器中定义遍历算法,会比在相同的聚合上使用不同的迭代器算法容易,同事也易于在不同的聚合上重用相同的算法。比如上面带策略的迭代器示例,迭代器把需要迭代的数据从聚合对象中取出并存放到自己的对象中,然后再迭代自己的数据,除了刚开始创建迭代器的时候需要访问聚合对象外,真正的迭代过程已经跟聚合对象无关了。
 295 
 296 
 297 迭代器健壮程度如何
 298 在遍历一个聚合的同时更改这个聚合可能是危险的。如果在遍历的时候增加或删除该聚合元素,可能会导致两次访问同一个元素或者遗漏掉某个元素。一个简单的解决办法是拷贝该聚合,并对该拷贝实施遍历,但一般来说代价太高。
 299 
 300 一个健壮的迭代器保证插入和删除不会干扰遍历,且不需要拷贝该聚合。有许多方法来实现健壮的迭代器。其中大多数需要向这个聚合注册该迭代器。当插入或删除时,该聚合要么调整迭代器的内部状态,要么在内部的维护额外的信息以保证正确的遍历。
 301 
 302 空迭代器
 303 一个空迭代器是一个退化的迭代器,它有助于处理边界条件。一个NullIterator总是已经完成了遍历。例如:叶节点元素返回NullIterator的一个实例。
 304 
 305  */
 306 
 307 /*
 308 双向迭代器
 309 
 310 可以同时向前和向后遍历数据的迭代器。
 311  */
 312 
 313 }());
 314 
 315 /**
 316  * 迭代器模式的优点
 317  *
 318  * 1.更好的封装性
 319  * 2.迭代器模式可以让你访问一个聚合对象的内容,而无需暴露该聚合对象的内部表示,从而提高聚合对象的封装性。
 320  * 3.可以以不同的遍历方式来遍历一个聚合。
 321  * 4.使用迭代器模式,使得聚合对象的内容和具体的迭代算法分离开。这样就可以通过使用不同的迭代器的实例,不同的遍历方式来遍历一个聚合对象了。
 322  * 5.迭代器简化了聚合的接口。
 323  * 6.简化客户端调用
 324  * 7.同一个聚合上可以有多个遍历。
 325  * 8.每个迭代器保持它自己的遍历状态。
 326  *
 327  *
 328  * 何时选用迭代器模式
 329  *
 330  * 1.如果你希望提供访问一个聚合对象的内容,但是又不想暴露它的内部表示的时候。
 331  * 2.如果你希望有多种遍历方式可以访问聚合对象,可以使用迭代器模式。
 332  * 3.如果你希望为遍历不同的聚合对象提供一个统一的接口。
 333  *
 334  *
 335  * 相关模式
 336  *
 337  * 迭代器模式和组合模式
 338  *
 339  * 这两个模式可以组合使用。
 340  * 组合模式是一种递归的对象结构,在枚举某个组合对象的子对象的时候,通常会使用迭代器模式。
 341  *
 342  * 迭代器模式和工厂方法模式
 343  * 
 344  * 这两个模式可以组合使用。
 345  * 在聚合对象创建迭代器的时候,通常会采用工厂方法模式来实例化相应的迭代器对象。
 346  *
 347  * 备忘模式
 348  * 可使用memento来捕获一个迭代的状态。迭代器在其内部存储memento
 349  */
 350 
 351 // 翻页迭代
 352  (function(){
 353     
 354     // 顺序翻页迭代其示例
 355     
 356     // 工资描述模型对象
 357     var PayModel = function(){
 358         // 支付工资的人员
 359         this.userName;
 360         // 支付的工资数额
 361         this.pay;
 362     };
 363     PayModel.prototype = {
 364         getUserName: function(){
 365             return this.userName;
 366         },
 367         setUserName: function(userName){
 368             this.userName = userName;
 369         },
 370         getPay: function(){
 371             return this.pay;
 372         },
 373         setPay: function(pay){
 374             this.pay = pay;
 375         },
 376         toString: function(){
 377             return 'userName = ' + this.userName + ', pay = ' + this.pay;
 378         }
 379     };
 380 
 381     var SalaryManager = function(){
 382         this.pms = [];
 383     };
 384     SalaryManager.prototype = {
 385         getPays: function(){
 386             return this.pms;
 387         },
 388         calcSalary: function(){
 389             var pm1 = new PayModel();
 390             pm1.setPay(2200);
 391             pm1.setUserName('王五');
 392 
 393             var pm2 = new PayModel();
 394             pm2.setPay(3600);
 395             pm2.setUserName('赵六');
 396 
 397             var pm3 = new PayModel();
 398             pm3.setPay(2200);
 399             pm3.setUserName('王五二号');
 400 
 401             var pm4 = new PayModel();
 402             pm4.setPay(3600);
 403             pm4.setUserName('赵六二号');
 404 
 405             var pm5 = new PayModel();
 406             pm5.setPay(2200);
 407             pm5.setUserName('王五三号');
 408 
 409             this.pms.push(pm1);
 410             this.pms.push(pm2);
 411             this.pms.push(pm3);
 412             this.pms.push(pm4);
 413             this.pms.push(pm5);
 414         },
 415         // Factory Method
 416         createIterator: function(type){
 417             if(type === 'random') {
 418                 return new RandomIterator(this);
 419             }
 420             return new Iterator(this);
 421         }
 422     };
 423 
 424     // 双向迭代器
 425     var Iterator = function(aggregate){
 426         this.pms = aggregate.getPays();
 427         this.index = 0;
 428     };
 429     Iterator.prototype = {
 430         hasNext: function(){
 431             return this.index <= (this.pms.length - 1);
 432         },
 433         hasPrevious: function(){
 434             return this.index > 0;
 435         },
 436         // 返回当前索引到num的集合
 437         next: function(num){
 438             var col = [];
 439             var count = 0;
 440             while(this.hasNext() && count++ < num) {
 441                 col.push(this.pms[this.index++]);
 442             }
 443 
 444             return col;
 445         },
 446         // 把索引退回去num个,然后再取值。
 447         // 事实上有可能有多退回去的数据
 448         previous: function(num){
 449             var col = [];
 450             var count = 0;
 451             this.index = num;
 452             while(this.hasPrevious() && count++ < num) {
 453                 col.push(this.pms[this.index++]);
 454             }
 455 
 456             return col;
 457         }
 458     };
 459 
 460     new function(){
 461         var salaryManager = new SalaryManager();
 462         salaryManager.calcSalary();
 463         var it = salaryManager.createIterator();
 464 
 465         // 获取第一页,每页显示两条
 466         var col = it.next(2);
 467         console.log('第一页数据:');
 468         print(col);
 469 
 470         var col2 = it.next(2);
 471         console.log('第二页数据:');
 472         print(col2);
 473 
 474         var col3 = it.previous(2);
 475         console.log('第三页数据:');
 476         print(col3);
 477 
 478         function print(col){
 479             for(var i =0; i < col.length; i++) {
 480                 console.log(col[i]);
 481             }
 482         }
 483     }();
 484 
 485     // 随机翻页迭代器示例
 486     
 487     var RandomIterator = function(aggregate){
 488         this.pms = aggregate.getPays();
 489         this.index = 0;
 490     };
 491     RandomIterator.prototype = {
 492         hasNext: function(){
 493             return this.index <= (this.pms.length - 1);
 494         },
 495         hasPrevious: function(){
 496             return this.index > 0;
 497         },
 498         getPage: function(pageNum, pageShow){
 499             var col = [];
 500             // 需要在这里先计算需要获取的数据的开始条数和结束条数
 501             var start = (pageNum - 1) * pageShow;
 502             var end = start + pageShow - 1;
 503 
 504             if(start < 0) start = 0;
 505 
 506             if(end > this.pms.length - 1) end = this.pms.length - 1;
 507 
 508             this.index = 0;
 509             while(this.hasNext() && this.index <= end) {
 510                 if(this.index >= start) col.push(this.pms[this.index]);
 511                 this.index++;
 512             }
 513 
 514             return col;
 515         }
 516     };
 517 
 518     new function(){
 519         var salaryManager = new SalaryManager();
 520         salaryManager.calcSalary();
 521         var it = salaryManager.createIterator('random');
 522 
 523         // 获取第一页,每页显示两条
 524         var col = it.getPage(1, 2);
 525         console.log('第一页数据:');
 526         print(col);
 527 
 528         var col2 = it.getPage(2, 2);
 529         console.log('第二页数据:');
 530         print(col2);
 531 
 532         var col3 = it.getPage(1, 2);
 533         console.log('再次获取第一页数据:');
 534         print(col3);
 535 
 536         var col4 = it.getPage(3, 2);
 537         console.log('第三页数据:');
 538         print(col4);
 539 
 540         function print(col){
 541             for(var i =0; i < col.length; i++) {
 542                 console.log(col[i]);
 543             }
 544         }
 545 
 546     }();
 547  }());
 548 
 549 (function(){
 550     /**
 551      * ECMAScript 6的Iterator--------------Generators
 552      *
 553      * 迭代器模式是很常用的设计模式,但是实现起来,很多东西是程序化的;当迭代规则比较复杂时,维护迭代器内的状态,是比较麻烦的。 于是有了generator,何为generator,这里 说的很明确: Generators: a better way to build Iterators.就是实现迭代器的更好的方式,借助 yield 关键字,可以更优雅的实现fib数列。
 554      */
 555 
 556     // 最简单的yield用法
 557     // 创建一个generatorFunction
 558     function* Hello() {
 559         yield 1;
 560         yield 2;
 561     }
 562 
 563     /**
 564      * function* Hello() { // 我习惯用大驼峰命名因为这就好比generator的构造函数 yield 1; yield 2; }
 565         arguments: null
 566         caller: null
 567         length: 0
 568         name: "Hello"
 569         prototype: GeneratorFunctionPrototype
 570             __proto__: GeneratorFunctionPrototype
 571                 constructor: function GeneratorFunctionPrototype() { [native code] }
 572                 next: function next() { [native code] }
 573                 throw: function throw() { [native code] }
 574                 __proto__: Object
 575          __proto__: function GeneratorFunctionPrototype() { [native code] }
 576         < function scope >
 577      */
 578 
 579     var hello = Hello(); // hello 是一个generator
 580     var a = hello.next(); // a: Object {value: 1, done: false}
 581     var b = hello.next(); // b: Object {value: 2, done: false}
 582     var c = hello.next(); // c: Object {value: undefined, done: true}
 583     hello.next();   // Error: Generator has already finished
 584 
 585     /*
 586     以看到hello的原型链中总是有一个next函数, 每次运行都返回yield后面的值, 只是不是单纯的yield后面的值, 而是放在一个对象的value键中, 同时我们注意到对象中还有另一个键done, Hello函数中有两个yield, 因此前两个done的值为false, 表示我还没有结束呐!, 最后一个done为true, 表示我已经结束了! 如果继续运行hello.next()则会报错Uncaught Error: Generator has already finished
 587 
 588 很明显的说yield就是相当于是另一种return, return使用时函数就结束了, 而使用yield的时候, 函数会卡在那个yield的地方, 等待下一个next
 589     */
 590 
 591     // fib示例
 592 
 593     // before
 594     function fib(){
 595         return {
 596             state :0,
 597             cur :0,
 598             prev1:-1,
 599             prev2:-1,
 600             hasNext:function(){
 601                 return true;
 602             },
 603             //fib数列,第一个是0,第二个是1,后面就是统一的迭代公式了
 604             next:function(){
 605                 if(this.state == 0){
 606                     this.cur = 0;
 607                     this.state = 1;
 608                 }else if(this.state == 1){
 609                     this.cur = 1;
 610                     this.prev2 = 0;
 611                     this.state = 2;
 612                 }else{
 613                     this.prev1 = this.prev2;
 614                     this.prev2 = this.cur;
 615                     this.cur = this.prev1 + this.prev2;
 616                 }
 617                 return this.cur;
 618             }
 619             //ignore reset funciton
 620         }
 621     }
 622     //这是无限序列,所以改造了一下,只生成8个数
 623     var fibIter = fib();
 624     for(var i = 0; i < 8; i++){
 625         console.log(fibIter.next());
 626         if(fibIter.hasNext())
 627             continue;
 628     }
 629 
 630     // after
 631      function* fib2(){
 632         yield 0;    // 状态0,第一次调用next,返回0,并改变状态
 633         yield 1;    // 状态1,第二次调用next,返回1,并改变状态
 634 
 635         var p1 = 0;
 636         var p2 = 1;
 637         var cur = p1 + p2;
 638 
 639         while(true) {
 640             yield cur;  // 状态2,后面调用next,返回相应的几个,状态不再改变
 641 
 642             p1 = p2;
 643             p2 = cur;
 644             cur = p1 + p2;
 645         }
 646     }
 647 
 648     var fibIter2 = fib2();
 649     for(var i = 0; i < 8; i++){
 650         console.log(fibIter2.next().value);
 651     }
 652     /*
 653     0  1  1  2  3  5  8  13
 654     */
 655 
 656 
 657     // http://www.html-js.com/article/1716
 658 
 659     (function(){
 660         // 对从1到100的数组,先取出其中的所有偶数,然后再取出所有其中的前10个,
 661         // 然后再计算其平方,然后转成数组。
 662         function* wrap(arr){
 663             for(var i = 0;i<arr.length;i++){
 664                 yield arr[i]; // ---(1)----
 665             }
 666         }
 667 
 668         function iter(arr){
 669             return new Iterator(arr);
 670         }
 671 
 672         function Iterator(arr){
 673             this.gen = wrap(arr);
 674         }
 675 
 676         Iterator.prototype = {
 677             where: function where(f){
 678                 var gen = whereImple(this.gen, f);
 679                 this.gen = gen;
 680                 return this;
 681             },
 682             map: function map(f){
 683                 var gen = mapImpl(this.gen, f);
 684                 this.gen = gen;
 685                 return this;
 686             },
 687             toArray: function toArray(){
 688                 var arr = [];
 689                 var _g;
 690                 var gen = this.gen;
 691                 while (true){
 692                     _g = gen.next();
 693                     if(_g.done) break;
 694                     arr.push(_g.value);
 695                 }
 696                 return arr;
 697             }
 698         };
 699 
 700         function* mapImpl(gen,f){
 701             var _g;
 702             while(true){
 703                 _g = gen.next();
 704                 if(_g.done) break;
 705                 yield f(_g.value);
 706             }
 707         }
 708 
 709         function* whereImple(gen,f){
 710             var index = 0, _g, value;
 711             while(true){
 712                 _g = gen.next();
 713                 if(_g.done) break;
 714                 value = _g.value;
 715                 if(f(value,index++)){
 716                     yield value;
 717                 }
 718             }
 719         }
 720 
 721         var _1to10 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
 722         var arr =iter(_1to10)
 723                 .where(function(v){return v % 2 == 0})
 724                 .map(function(v){return v * v})
 725                 .toArray();
 726         console.log(arr); // [4, 16, 36, 64, 100]
 727     }());
 728 
 729     // http://bg.biedalian.com/2013/12/21/harmony-generator.html
 730 
 731     // 代替回调金字塔
 732 
 733     function delay(time) {
 734         return function(fn) {
 735             setTimeout(function() {
 736                 fn(null, time); // null 表示没有错误..
 737             }, time)
 738         }
 739     }
 740 
 741     function co(GenFunc) {
 742         return function(cb) {
 743             var gen = GenFunc();
 744             void function next(err, args) {
 745                 if (err) {
 746                     cb(err);
 747                 } else {
 748                     if (gen.next) {
 749                         var ret = gen.next(args);
 750                         if (ret.done) {
 751                             cb && cb(null, args);
 752                         } else {
 753                             ret.value(next);
 754                         }
 755                     }
 756                 }
 757             }();
 758         }
 759     }
 760 
 761     co(function* () {
 762         var a;
 763         a = yield delay(200); // 200
 764         a = yield delay(a + 100); // 300
 765         a = yield delay(a + 100); // 400
 766     })(function(err, data) {
 767         if (!err) {
 768             console.log(data); // print 400
 769         }
 770     })
 771 
 772 }());
 773 
 774 (function(){
 775     // http://www.dofactory.com/javascript-iterator-pattern.aspx
 776     
 777     var Iterator = function(items) {
 778         this.index = 0;
 779         this.items = items;
 780     };
 781 
 782     Iterator.prototype = {
 783         first: function() {
 784             this.reset();
 785             return this.next();
 786         },
 787         next: function() {
 788             return this.items[this.index++];
 789         },
 790         hasNext: function() {
 791             return this.index <= this.items.length;
 792         },
 793         reset: function() {
 794             this.index = 0;
 795         },
 796         each: function(callback) {
 797             for (var item = this.first(); this.hasNext(); item = this.next()) {
 798                 callback(item);
 799             }
 800         }
 801     };
 802 
 803     // log helper
 804     var log = (function() {
 805         var log = "";
 806         return {
 807             add: function(msg) { log += msg + "\n"; },
 808             show: function() { console.log(log); log = ""; }
 809         }
 810     })();
 811 
 812 
 813     new function run() {
 814 
 815         var items = ["one", 2, "circle", true, "Applepie"];
 816         var iter = new Iterator(items);
 817 
 818         // using for loop
 819 
 820         for (var item = iter.first(); iter.hasNext(); item = iter.next()) {
 821             log.add(item);
 822         }
 823 
 824         log.add("");
 825 
 826         // using Iterator's each method
 827 
 828         iter.each(function(item) {
 829             log.add(item);
 830         });
 831 
 832         log.show();
 833     }();
 834 }());
 835 
 836 (function(){
 837     /* Title: Iterator
 838      Description: implements a specialized language
 839      */
 840     
 841     var agg = (function () {
 842 
 843         var index = 0,
 844                 data = [1, 2, 3, 4, 5],
 845                 length = data.length;
 846 
 847         return {
 848 
 849             next:function () {
 850                 var element;
 851                 if (!this.hasNext()) {
 852                     return null;
 853                 }
 854                 element = data[index];
 855                 index = index + 2;
 856                 return element;
 857             },
 858 
 859             hasNext:function () {
 860                 return index < length;
 861             },
 862 
 863             rewind:function () {
 864                 index = 0;
 865             },
 866 
 867             current:function () {
 868                 return data[index];
 869             }
 870 
 871         };
 872     }());
 873 
 874     var element;
 875     while (element - agg.next()) {
 876         // do something with the element
 877         console.log(element);
 878     }
 879 
 880     while (agg.hasNext()) {
 881         // do something with the next element...
 882         console.log(agg.next());
 883     }
 884 
 885     // this loop logs 1, then 3, then 5
 886     while (agg.hasNext()) {
 887         console.log(agg.next());
 888     }
 889 
 890     // go back
 891     agg.rewind();
 892     console.log(agg.current()); // 1
 893 
 894     // reference
 895     // http://www.addyosmani.com/resources/essentialjsdesignpatterns/book/#iteratorpatternjquery
 896     // http://shop.oreilly.com/product/9780596806767.do?sortby=publicationDate
 897 }());
 898 
 899 (function(){
 900     var CafeMenu = function(){
 901         Menu.apply(this);
 902         this.nPosition = -1;
 903         this.aMenuItems = [];
 904         this.createIterator = function(){
 905             return new CafeMenuIterator(this.aMenuItems);
 906         };
 907         this.addItem("Express", "Coffee from machine", false, 0.99);
 908         this.addItem("Long with water", "Coffee with a lot of water", false, 1.20);
 909         this.addItem("On the rocks", "Coffee with ice", false, 2.00);
 910     };
 911     CafeMenu.prototype.addItem = function(sName, sDescription, bVegetarian, nPrice){
 912         var oMenuItem = new MenuItem(sName, sDescription, bVegetarian, nPrice);
 913         this.aMenuItems.push(oMenuItem);
 914     };
 915     CafeMenu.prototype.getMenuItems = function(){
 916         return this.aMenuItems;
 917     };
 918 
 919     var CafeMenuIterator = function(aMenuItems){
 920         this.aMenuItems = aMenuItems;
 921         Iterator.apply(this);
 922         this.nPosition = -1;
 923         this.nLength = this.aMenuItems.length;
 924         this.hasNext = function(){
 925             return (this.nPosition + 1) < this.nLength;
 926         };
 927         this.next = function(){
 928             this.nPosition = this.nPosition + 1;
 929             return this.aMenuItems[this.nPosition];
 930         };
 931     };
 932 
 933     var DinnerMenu = function(){
 934         Menu.apply(this);
 935         this.oMenuItems = {};
 936         this.createIterator = function(){
 937             return new DinnerMenuIterator(this.oMenuItems);
 938         };
 939         this.addItem("Vegetarian BLT", "(Fakin') Bacon with lettuce and tomato on whole wheat", true, 2.99);
 940         this.addItem("BLT", "Bacon with lettuce and tomato on whole wheat", false, 2.99);
 941         this.addItem("Soup of the day", "Soup of the day, with a side of potato salad", false, 3.29);
 942         this.addItem("Hotdog", "A hotdog with saurkraut, relish, onions, topped with cheese", false, 3.05);
 943     };
 944     DinnerMenu.MAX_ITEMS = 6;
 945     DinnerMenu.prototype.addItem = function(sName, sDescription, bVegetarian, nPrice){
 946         if(this.length === DinnerMenu.MAX_ITEMS){
 947             throw new Error("Sorry menu is full! Can't add item to menu");
 948         }
 949         this.oMenuItems[sName] = new MenuItem(sName, sDescription, bVegetarian, nPrice);
 950         this.length = this.length + 1;
 951     };
 952     DinnerMenu.prototype.getMenuItems = function(){
 953         return this.oMenuItems;
 954     };
 955 
 956     var DinnerMenuIterator = function(oMenuItems){
 957         this.oMenuItems = oMenuItems;
 958         Iterator.apply(this);
 959         this.nPosition = -1;
 960         this.nLength = 0;
 961         this.hasNext = function(){
 962             return (this.nPosition + 1) < this.nLength;
 963         };
 964         this.next = function(){
 965             this.nPosition = this.nPosition + 1;
 966             return this.oMenuItems[this.aKeys[this.nPosition]];
 967         };
 968         this._getKeys = function(){
 969             var aKeys = [];
 970             var sKey = '';
 971             for(sKey in this.oMenuItems){
 972                 if(this.oMenuItems.hasOwnProperty(sKey)){
 973                     aKeys.push(sKey);
 974                     this.nLength = this.nLength + 1;
 975                 }
 976             }
 977             return aKeys;
 978         };
 979         this.aKeys = this._getKeys();
 980     };
 981 
 982     var Iterator = function(){
 983         this.hasNext = function(){
 984             throw new Error("This method must be overwritten!");
 985         };
 986         this.next = function(){
 987             throw new Error("This method must be overwritten!");
 988         };
 989         this.remove = function(){
 990             throw new Error("This method must be overwritten!");
 991         };
 992     };
 993 
 994     var Mattress = function(aMenus){
 995         this.aMenus = aMenus;
 996     };
 997     Mattress.prototype._printMenu = function(oIterator){
 998         var oMenuItem = null;
 999         while(oIterator.hasNext()){
1000             oMenuItem = oIterator.next();
1001             console.log(oMenuItem.getName() + ": " + oMenuItem.getDescription() + ", " + oMenuItem.getPrice() + "eur.");
1002         }
1003     };
1004     Mattress.prototype.printMenu = function(){
1005         var nMenu = 0;
1006         var nLenMenus = this.aMenus.length;
1007         var oMenu = null;
1008         var oIterator = null;
1009 
1010         for(; nMenu < nLenMenus;)
1011         {
1012             oMenu = this.aMenus[nMenu];
1013             oIterator = oMenu.createIterator();
1014             this._printMenu(oIterator);
1015             nMenu = nMenu + 1;
1016         }
1017     };
1018 
1019     var Menu = function(){
1020         this.createIterator = function(){
1021             throw new Error("This method must be overwritten!");
1022         };
1023     };
1024 
1025     var MenuItem = function(sName, sDescription, bVegetarian, nPrice){
1026         this.sName = sName;
1027         this.sDescription = sDescription;
1028         this.bVegetarian = bVegetarian;
1029         this.nPrice = nPrice;
1030     };
1031     MenuItem.prototype.getName = function(){
1032         return this.sName;
1033     };
1034     MenuItem.prototype.getDescription = function(){
1035         return this.sDescription;
1036     };
1037     MenuItem.prototype.getPrice = function(){
1038         return this.nPrice;
1039     };
1040     MenuItem.prototype.isVegetarian = function(){
1041         return this.bVegetarian;
1042     };
1043 
1044     var PancakeHouseMenu = function(){
1045         Menu.apply(this);
1046         this.nPosition = -1;
1047         this.aMenuItems = [];
1048         this.createIterator = function(){
1049             return new PancakeHouseMenuIterator(this.aMenuItems);
1050         };
1051         this.addItem("K&B's Pancake Breakfast", "Pancakes with scrambled eggs, and toast", true, 2.99);
1052         this.addItem("Regular Pancake Breakfast", "Pancakes with fried eggs, sausage", false, 2.99);
1053         this.addItem("Blueberry Pancakes", "Pancakes made with fresh blueberries", true, 3.49);
1054         this.addItem("Waffles", "Waffles, with your choice of blueberries or strawberries", true, 3.59);
1055     };
1056     PancakeHouseMenu.prototype.addItem = function(sName, sDescription, bVegetarian, nPrice){
1057         var oMenuItem = new MenuItem(sName, sDescription, bVegetarian, nPrice);
1058         this.aMenuItems.push(oMenuItem);
1059     };
1060     PancakeHouseMenu.prototype.getMenuItems = function(){
1061         return this.aMenuItems;
1062     };
1063 
1064     var PancakeHouseMenuIterator = function(aMenuItems){
1065         this.aMenuItems = aMenuItems;
1066         Iterator.apply(this);
1067         this.nPosition = -1;
1068         this.nLength = this.aMenuItems.length;
1069         this.hasNext = function(){
1070             return (this.nPosition + 1) < this.nLength;
1071         };
1072         this.next = function(){
1073             this.nPosition = this.nPosition + 1;
1074             return this.aMenuItems[this.nPosition];
1075         };
1076     };
1077 
1078     var oMattress = new Mattress([new PancakeHouseMenu(), new DinnerMenu(), new CafeMenu()]);
1079     console.log("---------------------------------------------");
1080     oMattress.printMenu();
1081     console.log("---------------------------------------------");
1082 
1083 }());
1084 
1085 </script>
1086 </body>
1087 </html>

 

posted @ 2014-01-25 10:32  LukeLin  阅读(3500)  评论(0编辑  收藏  举报