JavaScript设计模式笔记-迭代器模式

定义

迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表现.迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即时不关心对象的内部构造,也可以按照顺序访问其中的每个元素.

7.1 jquery中的迭代器

迭代器模式无非就是循环访问聚合对象中的各个元素.比如jquery的$.each函数 其中i为当前索引,n为当前元素

1
2
3
4
$.each([1,2,3],function(i,n){
console.log('当前下标为:'+i)
console.log('当前值为:'+n)
})

7.2 实现自己的迭代器

1
2
3
4
5
6
7
8
var each=function(arr,cb){
for(var i=0;i=arr.length;i<l;i++){
cb.call(arr[i],i,arr[i])
}
}
each([1,2,3],function(i,n){
console.log(i,n)
})

7.3 内部迭代器和外部迭代器

1.内部迭代器

刚刚编写的each属于内部迭代器,each函数内部已经定义好了迭代规则,它完全接收整个规则,外部只需要一次调用.
内部迭代器在调用的时候十分方便,外界不用关心迭代器内部的实现,跟迭代器的交互也仅仅是一次初始调用,但也刚好是内部迭代器的缺点.
比如需要比较判断两个数组元素值是否完全相等,如果不改写each函数内部,我们能够入手的地方也仅仅剩余each的回调函数了.

1
2
3
4
5
6
7
8
9
10
11
12
var compare=function(arr1,arr2){
if(arr1.length!==arr2.length){
throw new Error("arr1与arr2不相等")
}
each(arr1,function(i,n){
if(n !== arr2[i]){
throw new Error("arr1与arr2不相等)
}
})
alert("arr1与arr2相等")
}
compare=([1,2,3],[1,2,4]);//error 不相等

这个函数之所以能够如此顺利的完成,还依靠了JavaScript中函数可以作为参数传递这个特性,但是在其他语言中就未必有这么幸运了

2.外部迭代器

外部迭代器必须显式的请求迭代下一个元素.
外部迭代器增加了一些调用的复杂度,但相对也增强了迭代器的灵活性,我们可以手工控制迭代的过程或者顺序。
下面实现一个外部迭代器 来自RUBY语言 出自《松本行弘的程序世界》

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var Iterator=function(obj){
var current=0;
var next=function(){
current+=1;
};
var isDone=function(){
return current>=obj.length;
};
var getCurrItem=function(){
return obj[current]
};
return{
next:next,
isDone:isDone,
getCurrItem:getCurrItem
}
}

再看看如何改编compare函数

1
2
3
4
5
6
7
8
9
10
11
12
13
var compare=function(iterator1,iterator2){
while(!iterator1.isDone()&&!iterator2.isDone()){
if(iterator1.getCurrItem()!==iterator2.getCurrItem()){
throw new Error("arr1与arr2不相等")
}
iterator1.next()
iterator2.next()
}
alert("arr1与arr2相等)
}
var iterator1=Iterator([1,2,3]);
var iterator2=Iterator([1,2,3]);
compare(iterator1,iterator2);//相等

前几天正好有个问题.问题是如何异步回调的控制函数迭代..看到今天这章突然发现外部迭代器可以很好的实现这个需求.先来用递归实现的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
var funcFor=function(arr,cb){
var len=arr.length;
var i=0;
var Iterator=function(fn){
fn(function(next,data) {
if (next) {
i++;
if (i >=len) {
return cb("没有了")
}
Iterator(arr[i])
} else {
cb(data)
}
})
}
return function(){
Iterator(arr[i])
}()
}
funcFor([function(cb){
setTimeout(function(){
cb(true)
},1000)
},function(cb){
setTimeout(function(){
cb(false,1)
},1000)
}],function(data){console.log(data)})

这里值得注意的就是 用递归实现也就相当于手动控制了迭代的进行.这段代码有个地方要注意下 为什么是i >=len而不是i > len 因为len与i是不一样的 i是从0开始算的. 那么用外部迭代器来改写 有稍许需要改动的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
var Iterator=function(obj){
var current=0;
var next=function(){
current+=1;
};
var isDone=function(){
return current>=obj.length;
};
var getCurrItem=function(){
return obj[current]
};
return{
next:next,
isDone:isDone,
getCurrItem:getCurrItem
}
}
var funcIterator=Iterator([function(cb){
setTimeout(function(){
cb(true)
},1000)
},function(cb){
setTimeout(function(){
cb(false,1)
},1000)
}])
var compare=function(iterator,cb){
if(!iterator.isDone()){
var fn=iterator.getCurrItem();
fn(function(next,data){
if(next){
if(!iterator.isDone()){
iterator.next()
var fn=iterator.getCurrItem();
fn(arguments.callee)
}
}else{
cb(data)
}
})
}
}
compare(funcIterator,function(data){console.log(data)})

实际上这里也用到了递归.. 这里就利用了手动控制迭代器来完成了一个异步处理迭代的问题.

7.4 迭代类数组对象和字面量对象

迭代器不仅可以迭代数组,还可以迭代一些类数组对象,比如arguments,{“0”:1,”1”:”b”}等,只要迭代对象拥有length属性而且可以用下标访问,那它就可以被迭代.
在JavaScript中,for in 语句可以用来迭代普通字面量对象属性,jquery中提供了$.each来封装各种迭代行为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$.each=function(obj,callback){
var value,
i=0,
length=obj.length,
isArray=isArraylike(obj);
if(isArray){
for(;i<length;i++){
value=callback.call(obj[i],i,obj[i]);
if(value===false){
break;
}
}
}else{
for(i in obj){
value=callback.call(obj[i],i,obj[i]);
if(value===false){
break
}
}
}
return obj;
}

7.5 倒序迭代器

由于GoF中对迭代器模式的定义非常松散,所以我们可以有多种多样的迭代器实现 比如实现一个倒序迭代器

1
2
3
4
5
6
7
8
var reverseEach=function(ary,callback){
for(var l=ary.length-1;l>=0;i--){
callback(l,ary[l])
}
}
reverseEach([0,1,2],function(i,n){
console.log(n);//分别输出2,1,0
})

7.6 中止迭代器

迭代器可以像普通for循环中的break一样,提供一种跳出循环的方法,比如jquery中的each

1
2
3
if(value===false){
break
}

参考上述的jquery迭代器比如需要n大于3的时候跳出循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var each = function( ary, callback ){
for ( var i = 0, l = ary.length; i < l; i++ ){
if ( callback( i, ary[ i ] ) === false ){ // callback 的执行结果返回false,提前终止迭代
break;
}
}
};
each( [ 1, 2, 3, 4, 5 ], function( i, n ){
if ( n > 3 ){ // n 大于3 的时候终止循环
return false;
}
console.log( n ); // 分别输出:1, 2, 3
});

7.7 迭代器模式的应用举例

..原文比较长 这里简单说一下 就是利用迭代器来判断选择哪个上传功能组件
先看看没有用迭代器实现的判断

1
2
3
4
5
6
7
8
9
10
11
12
13
var getUploadObj=function(){
try{
return new ActiveXObject("TXFTNActiveX.FTNUpload");//IE上传控件
}catch(e){
if(supportFlash()){
var str="<object type="application/x-shockwave-false"></object>"
return $(str).appendTo($("body"));
}else{
var str="<input name="file" type="file" />";//表单上传
return $(str).appendTo($("body"));
}
}
}

这里多了很多的if else判断 那么可以用策略模式来改写 策略模式就是专门未来处理这种问题的.下例的迭代器也比较像策略模式 只需要加一层context就行了

1
2
3
4
5
6
7
8
9
var getActiveUploadObj=function(){
try{
return new ActiveXObject("TXFTNActiveX.FTNUpload");//IE上传控件
}catch(e){
return false
}
}
var getFlashUploadObj=function(){...略}
var getFormUploadObj=function(){...略}

那么我们的迭代器只需要进行下面这几步工作

  • 提供一个可以被迭代的方法 可以根据传递顺序的先后按照优先级进行迭代
  • 如果正在迭代的函数返回一个object则表示找到了正确的upload对象 反之如果函数返回false则让迭代器继续工作

迭代器代码如下

1
2
3
4
5
6
7
8
9
var iteratorUploadObj=function(){
for(var i=0;fn;fn=arguments[i++]){
var uploadObj=fn()
if(uploadObj!==false){
return uploadObj
}
}
}
var uploadObj=iteratorUploadObj(getActiveUploadObj,getFlashUploadObj,getFormUploadObj)

这个迭代器只需要封装一层就可以当做策略模式用了

7.8 小结

迭代器是一种相对简单的模式.简单到很多时候都不认为这是一种设计模式…….(简单嘛?)