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>