设计模式之迭代器模式

参考资料

  • 曾探《JavaScript设计模式与开发实践》;

定义

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

迭代、循环有什么区别:

  • 循环,循环就是在满足一定条件时,重复执行同一段代码,典型的例子:do...while

  • 迭代,迭代是指按顺序逐个访问对象中的每一项,典型的例子:forEach

那么什么样的对象可以被迭代呢?

  • 目前的内置可迭代对象有:String、Array、TypedArray、Map、Set,他们的原型对象都实现了@@iterator方法。

  • 当这个对象是可迭代对象时,我们可以通过调用[Symbol.iterator]方法来按顺序遍历对象中的每一项:

const arr = [1, 2, 3, 4];
// 迭代器
const iterator = arr[Symbol.iterator]()
iterator.next(); // { value: 1, done: false }
iterator.next(); // { value: 2, done: false }
  • 如果我们需要将一个任意的对象变成可迭代对象时,只要提供自己的@@iterator方法即可,比如MDN中的例子

使用场景:迭代器模式在平时的开发中其实是比较常见的,但由于js语言中已经内置了迭代器,我们通常不需要刻意进行封装。但有几个场景下也可以考虑自行封装迭代器:

  • 当我们需要遍历不同类型的数据时,类似jQuery中的each方法,可以考虑封装一个统一的方法,在方法内部处理遍历的逻辑,实现一个内部迭代器。这样的优势在于,可以将遍历逻辑与业务解耦,这样也满足开闭原则单一职责原则

  • 当我们需要手动控制迭代的过程和顺序时,也可以考虑ES6的迭代器或自行封装可迭代对象,通过next方法手动的控制迭代时机,在一些有流程化的需求中,应该会有不错效果。

  • 当我们需要通过不同的规则去使用不同方法时,也可以考虑使用迭代器模式,按顺序判断是否满足条件。

jQuery中的迭代器

迭代器模式就是循环访问聚合对象中的各个元素,比如jQuery中的$.each:

	$.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 ) { // 迭代object 对象
                value = callback.call( obj[ i ], i, obj[ i ] );
                if ( value === false ) {
                    break;
                }
            }
		}
		return obj;
	};
		
    $.each( [1, 2, 3], function( i, n ){
		console.log( '当前下标为: '+ i );
		console.log( '当前值为:' + n );
	});

axios中的迭代器模式

在axios源码中,也使用到很多each方法,比如同时给axios上添加['post', 'put', 'patch']等方法,那么axios中是如何实现的呢?

// lib/utils.js

function forEach(obj, fn) {
  // Don't bother if no value provided
  if (obj === null || typeof obj === 'undefined') {
    return;
  }

  // Force an array if not already something iterable
  if (typeof obj !== 'object') {
    /*eslint no-param-reassign:0*/
    obj = [obj];
  }

  if (isArray(obj)) {
    // Iterate over array values
    for (var i = 0, l = obj.length; i < l; i++) {
      fn.call(null, obj[i], i, obj);
    }
  } else {
    // Iterate over object keys
    for (var key in obj) {
      if (Object.prototype.hasOwnProperty.call(obj, key)) {
        fn.call(null, obj[key], key, obj);
      }
    }
  }
}

通过源码发现其实与jQuery中的类似:如果是数组则使用下标方式遍历,是对象则使用for...in方式遍历,但有不同的点在于:Axios中对象遍历加了一层判断hasOwnProperty,这个方法是判断对象自身属性中是否具有指定的属性,可以忽略掉那些从原型链上继承来的属性

实现自己的迭代器

参数1:被循环的数组;参数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 ] );
	});

迭代器分类

迭代器可以分为内部迭代器外部迭代器两大类。

  • 内部迭代器完全接手整个迭代过程,外部只需一次初始调用。

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

	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 不相等' );
  • 外部迭代器必须显示地请求迭代下一个元素。外部迭代器增加了一些调用的复杂度,但相对也增强了迭代器的灵活性,我们可以手工的控制迭代的过程或顺序。

	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 相等

迭代器模式应用-根据不同浏览器选择相应的上传组件

我们会优先选择控件上传,如果没有安装上传控件则使用Flash上传,如果Flash也没有安装,那就只好使用浏览器原生的表单上传了。但是我们目前的代码里充斥着try...cache、if等,很难阅读而且违反了开闭原则

原始的代码,可能是这样的:

var getUploadObj = function() {
    try {
        return new ActiveXObject('TXFTNActiveX.FTNUpload'); // IE 上传控件
    } cache(e) {
        if (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'));
        }
    }
}

我们考虑用迭代器模式来优化:

  • 提供一个可以被迭代的方法,使得getActiveUploadObj、getFlashUploadObj、getFormUploadObj依照优先级被迭代

  • 如果正在被迭代的函数返回一个对象,则表示找到了正确的upload对象,反之,如果返回false,则让迭代器继续工作

// 定义各个上传方法
var getActiveUploadObj = function() {
    try {
        return new ActiveXObject('TXFTNActiveX.FTNUpload');
    } cache(e) {
        return false
    }
}

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

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

// 按优先级迭代函数
var iteratorUploadObj = function() {
    for (var i = 0, fn; fn = arguments[i++];) {
        var uploadObj = fn();
        if (uploadObj !== false) {
            return uploadObj
        }
    }
}

// 获取可上传upload对象
var uploadObj = iteratorUploadObj(getActiveUploadObj, getFlashUploadObj, getFormUploadObj)

这样重构后可以看到各个上传对象的方法互不干扰,可以很好的维护和扩展代码。如果我们后面再添加一个WebKit控件上传HTML5上传,我们仅仅要做下面的工作:

// 定义上传函数
var getWebKitUploadObj = function() {}
var getHtml5UploadObj = function() {}

// 按照优先级添加到迭代器
var uploadObj = iteratorUploadObj(getActiveUploadObj, getFlashUploadObj, getFormUploadObj, getWebKitUploadObj, getHtml5UploadObj)
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值