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>