js迭代器模式

迭代器模式(Iterator),提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示。

  • 一个聚集对象,而且不管这些对象是什么都需要遍历的时候,你就应该考虑用迭代器模式。
  • 你需要对聚集有多种方式遍历时,可以考虑用迭代器模式。为遍历不同的聚集结构提供如开始、下一个、是否结束、当前哪一项等统一的接口。
  • 迭代器模式就是分离了集合对象的遍历行为,抽象出一个迭代器类来负责,这样既可以做到不暴露集合的内部结构,又可让外部代码透明地访问集合内部的数据。

既然,迭代器模式承担了遍历集合对象的职责,则该模式自然存在2个类,一个是聚合类,一个是迭代器类。在面向对象涉及原则中还有一条是针对接口编程,所 以,在迭代器模式中,抽象了2个接口,一个是聚合接口,另一个是迭代器接口,这样迭代器模式中就四个角色了,具体的类图如下所示:

js迭代器模式

从上图可以看出,迭代器模式由以下角色组成:

  • 迭代器角色(Iterator):迭代器角色负责定义访问和遍历元素的接口
  • 具体迭代器角色(Concrete Iteraror):具体迭代器角色实现了迭代器接口,并需要记录遍历中的当前位置。
  • 聚合角色(Aggregate):聚合角色负责定义获得迭代器角色的接口
  • 具体聚合角色(Concrete Aggregate):具体聚合角色实现聚合角色接口。

在下面的情况下可以考虑使用迭代器模式:

  • 系统需要访问一个聚合对象的内容而无需暴露它的内部表示。
  • 系统需要支持对聚合对象的多种遍历。
  • 系统需要为不同的聚合结构提供一个统一的接口。

C#迭代器模式:

namespace 迭代器模式
{
    class Program
    {
        static void Main(string[] args)
        {
            ConcreteAggregate a = new ConcreteAggregate();

            a[0] = "大鸟";
            a[1] = "小菜";
            a[2] = "行李";
            a[3] = "老外";
            a[4] = "公交内部员工";
            a[5] = "小偷";

            Iterator i = new ConcreteIterator(a);
            //Iterator i = new ConcreteIteratorDesc(a);
            object item = i.First();
            while (!i.IsDone())
            {
                Console.WriteLine("{0} 请买车票!", i.CurrentItem());
                i.Next();
            }

            Console.Read();
        }
    }

    abstract class Aggregate
    {
        public abstract Iterator CreateIterator();
    }

    class ConcreteAggregate : Aggregate
    {
        private IList<object> items = new List<object>();
        public override Iterator CreateIterator()
        {
            return new ConcreteIterator(this);
        }

        public int Count
        {
            get { return items.Count; }
        }

        public object this[int index]
        {
            get { return items[index]; }
            set { items.Insert(index, value); }
        }
    }

    abstract class Iterator
    {
        public abstract object First();
        public abstract object Next();
        public abstract bool IsDone();
        public abstract object CurrentItem();
    }

    class ConcreteIterator : Iterator
    {
        private ConcreteAggregate aggregate;
        private int current = 0;

        public ConcreteIterator(ConcreteAggregate aggregate)
        {
            this.aggregate = aggregate;
        }

        public override object First()
        {
            return aggregate[0];
        }

        public override object Next()
        {
            object ret = null;
            current++;

            if (current < aggregate.Count)
            {
                ret = aggregate[current];
            }

            return ret;
        }

        public override object CurrentItem()
        {
            return aggregate[current];
        }

        public override bool IsDone()
        {
            return current >= aggregate.Count ? true : false;
        }

    }

    class ConcreteIteratorDesc : Iterator
    {
        private ConcreteAggregate aggregate;
        private int current = 0;

        public ConcreteIteratorDesc(ConcreteAggregate aggregate)
        {
            this.aggregate = aggregate;
            current = aggregate.Count - 1;
        }

        public override object First()
        {
            return aggregate[aggregate.Count - 1];
        }

        public override object Next()
        {
            object ret = null;
            current--;
            if (current >= 0)
            {
                ret = aggregate[current];
            }

            return ret;
        }

        public override object CurrentItem()
        {
            return aggregate[current];
        }

        public override bool IsDone()
        {
            return current < 0 ? true : false;
        }

    }
}

C#.NET的迭代器实现:

namespace NET的迭代器实现
{
    class Program
    {
        static void Main(string[] args)
        {

            IList<string> a = new List<string>();
            a.Add("大鸟");
            a.Add("小菜");
            a.Add("行李");
            a.Add("老外");
            a.Add("公交内部员工");
            a.Add("小偷");

            foreach (string item in a)
            {
                Console.WriteLine("{0} 请买车票!", item);
            }

            IEnumerator<string> e = a.GetEnumerator();
            while (e.MoveNext())
            {
                Console.WriteLine("{0} 请买车票!", e.Current);

            }
            Console.Read();
        }
    }
}

js内部迭代器:

现在我们来实现一个each函数,each函数接受2个参数,第一个为被循环的数组,第二个为循环中的每一步后将被触发的回调函数:

var each = function(ary,callback){
    for(var i=0,l=ary.length;i<l;i++){
        callback.call(ary[i],i,ary[i]);  //把下标和元素当作参数传递给callback函数
    }
};

each([1,2,3],function(i,n){
    alert([i,n]);
});

js外部迭代器:

比如现在有个需求,要判断2个数组里的元素的值是否完全相等,先看一下不使用外部迭代器的写法:

var compare = function(ary1,ary2){
    if(ary1.length !== ary2.length){
        throw new Error('ary1和ary2不相等');
    }
    each(ary1,function(i,n){
        if(n!==ary2[i]){
            throw new Error('ary1和ary2不相等');
        }
    });
    alert('ary1和ary2相等');
};

compare([1,2,3],[1,2,4]);   //throw new Error('ary1和ary2不相等');

说实话,这个compare函数一点都算不上好看,我们目前能够顺利的完成需求,还要感谢在javascript里可以把函数当作参数传递的特性,但在其他的语言中未必就能如此幸运。
在一些没有闭包的语言中,内部迭代器本身的实现也相当复杂,比如C语言中的内部迭代器是用函数指针来实现的,循环处理需要的数据都要以参数的形式明确地从外面传递进去。

外部迭代器必须显式地请求迭代下一个元素。
外部迭代器增加了一些调用的复杂度,但相对也增强了迭代器的灵活性,我们可以手工控制迭代的过程或者顺序。
下面这个外部迭代器的实现来自《*的程序世界》第4章,原例用Ruby写成,这里我们翻译成javascript:

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函数:

var compare = function(iterator1,iterator2){
    while(!iterator1.isDone() && !iterator2.isDone()){
        if(iterator1.getCurrItem() !== iterator2.getCurrItem()){
            throw new Error('iterator1和iterator2不相等');
        }
        iterator1.next();
        iterator2.next();
    }
    alert('iterator1和iterator2相等');
};

var iterator1 = Iterator([1,2,3]);
var iterator2 = Iterator([1,2,3]);

compare(iterator1,iterator2);    //输出:iterator1和iterator2相等

外部迭代器虽然调用方式相对复杂,但它的适用面更广,也能满足更多变的需求。内部迭代器和外部迭代器在实际生产中没有优劣之分,究竟使用哪个要根据需求场景而定。

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

var isArraylike = function(obj){
    return Object.prototype.toString.call(obj) === '[object Array]';
};

var 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;
            }
        }
    }
};

each([1,2,3,4,5],function(i,n){
    if(n>3){
        return false;
    }
    alert(n); //分别输出:1,2,3
});

each({a:1,b:2,c:3,d:4,e:5},function(i,n){
    if(n>3){
        return false;
    }
    alert(n); //分别输出:1,2,3
});

js倒序迭代器:

var reverseEach = function(ary,callback){
    for(var l=ary.length-1;l>=0;l--){
        callback.call(ary[l],l,ary[l]);
    }
};

reverseEach([0,1,2],function(i,n){
    alert(n); //分别输出:2,1,0
});

js中止迭代器:

var each = function(ary,callback){
    for(var i=0,l=ary.length;i<l;i++){
        if(callback.call(ary[i],i,ary[i]) === false){   //callback的执行结果返回false,提前终止迭代
            break;
        }
    }
};

each([1,2,3,4,5],function(i,n){
    if(n>3){
        return false;
    }
    alert(n); //分别输出:1,2,3
});