阅读 331

javascript设计模式 之 4 迭代器模式

1 迭代器模式的定义

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

2 jquery中的迭代器

迭代器模式无非就是循环访问局和对象中的每个元素。例如jquery中的$.earch函数。其中回调函数中的参数i为当前索引,n为元素。

$.each([1, 2, 3], function(i, n) {
    console.log(`当前下标:${i}`);
    console.log(`当前值为:${n}`);
})
复制代码

3 实现自己的迭代器

我们来实现一个拓展数组的eachPlus函数,实现each函数相同的遍历功能。参数是一个回调函数

Array.prototype.eachPlus = function(callback) {
    var self = this;
    for(var i = 0; i < self.length; i++) {
        callback(self[i], i, self);
    }
}
var arr = [1, 2, 3];
arr.eachPlus(function(value, index) {
    console.log(value, index);
})
复制代码

4 内部迭代器与外部迭代器

  • 内部迭代器:函数内部已经定义好迭代规则, 它完全接手整个迭代过程,外部只需要一次初始化调用。
  • 外部迭代器:必须显示请求迭代下一个元素。 由于内部迭代器已经将迭代过程定义好了,因此我们想要在规则中修改代码是不可能了。假如我们想要比较两个数组是否相等,只能在回调函数中进行着手:
function compare(arr1, arr2) {
    arr1.eachPlus(function(value, index) {
        if (arr1.length !== arr2.length) {
            throw new Error('arr1 != arr2');
        }
        if (arr2[index] !== value) {
            throw new Error('arr1 != arr2');
        }
        console.log('arr1 == arr2');
    });
}

var arr1 = [1, 2, 3];
var arr2 = [1, 2, 3];
compare(arr1, arr2);
复制代码

外部迭代器增加了一些调用的复杂度,但是相对增强了迭代器的灵活性,我们可以手工控制迭代的过程或者顺序。下面是一个外部迭代器的例子

Array.prototype.iterator = function() {
    var self = this;
    var index = 0;
    // 获取当前元素
    var current = function() {
        return self[index];
    };
    // 获取当前元素,并移动索引到下一个位置
    var next = function() {
        var el ;
        if (!hasNext()) {
            return null;
        }
        el = current();
        index++;
        return el;
    };
    // 是否还有元素
    var hasNext = function() {
        return index < self.length ? true : false;
    };

    // 重置迭代器
    var rewind = function() {
        index = 0;
    };  

    return {
        next: next,
        hasNext: hasNext,
        current: current,
        rewind: rewind
    }
}

// 调用
function compare(arr1, arr2) {
    var iter1 = arr1.iterator();
    var iter2 =  arr2.iterator();

    while (iter1.hasNext() || iter2.hasNext()) {
        if (iter1.current() !== iter2.current()) {
            throw new Error('不相等');
        }
        iter1.next();
        iter2.next();
    }
    console.log('相等');
}
compare([1,2,3,4], [1,2,3,4])
复制代码

5 迭代器模式应用

当上传一个文件,我们有一个上传的控件。但是不同的浏览器兼容问题,应该使用不一样的控件。

  • 获取IE控件
  • 不存在则使用flash控件
  • 以上都不存在使用原生表单上传
var getUploadObj = function(){
    try{
        return new ActiveXObject("TXFTNActiveX.FTNUpload"); // IE 上传控件
    } catch(e) {
        if (supportFlash()){ // supportFlash 函数未提供
            var str = '<object type="application/x-shockwave-flash"></object>';
            return $( str ).appendTo( $('body') );
        } else {
            var str = '<input name="file" type="file"/>'; // 表单上传
            return $( str ).appendTo( $('body') );
        }
    }
};
复制代码

上面的代码中,为了得到一个upload控件,使用了很多if,else,并且使用了try catch。这个例子就像是我们有1扇门,手里有三把钥匙,不知道拿一把可以打开,那么就只有一直尝试,直到正确为止。下面我们尝试使用内部迭代器模式解决这个问题:

  • 将上传控件各自封装
  • 组装空间数组
  • 进行迭代获取正确钥匙
var IEUpload = function() {
    try{
        return new ActiveXObject("TXFTNActiveX.FTNUpload"); // IE 上传控件
    } catch(e) {
        return false;
    }
}

var flashUpload = function() {
    if (supportFlash()) { // supportFlash 函数未提供
        var str = '<object type="application/x-shockwave-flash"></object>';
        return $( str ).appendTo( $('body') );
    }
    return false;
}

var getFormUpload = function() {
    var str = '<input name="file" type="file"/>'; // 表单上传
    return $( str ).appendTo( $('body') );
}

// 编写内部迭代器模式
function getUpload() {
    for(var i = 0, fn; fn = arguments[i++];) {
        var uploadObj = fn();
        if (uploadObj) {
            return uploadObj;
        }
    }
}

// 使用内部迭代器
getUpload(IEUpload, flashUpload, getFormUpload);
复制代码

前面提到:将不变的部分变化的部分隔开是每个设计模式的主题。这里我们也可以看出来,getUpload就是分离出来的不可变的部分。当我们需要增加一个HTML5的上传控件类型时,不再需要修改整个代码,只需要添加一个上传规则html5Upload()函数在进行调用即可。

6 小结

迭代器模式比较简单,很多时候许多人都认为它不是一种设计模式。目前绝大部分语言都内置了迭代器。