iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 4
0
Modern Web

JS Design Pattern 系列 第 4

JS Design Pattern Day04-迭代器模式 Iterator

嗨大家好,今天是第四天,差點來不及發文

今天來寫迭代器模式!

迭代器模式是指提供一個方法可以依照順序的存取一個聚合物(像是陣列)中的各個元素,且迭代過程會與業務邏輯分開。其實現在很多語言都已經有內建迭代器了,像JavaScript裡的forEach以及jQuery裡的each都是。為了瞭解整個模型,我們來作一個自己的迭代器吧!

首先先說明功用,就是要針對陣列裡的每一個元素個別去做一些事,例如傳入[1,2,3]然後就要print出'第0個是1'這樣的內容:

var each = function (ary, callback, _this) {
	for (var i = 0; i < ary.length; i++) {
		callback.call(_this, i, ary[i]);
	}
};

each([1, 2, 3], function (index, number) {
	console.log('第' + index + '個是' + number);
}, this);

這樣的寫法叫做內部迭代器,其實效果跟Array.forEach()一樣,內部迭代器的好處是呼叫的時候非常方便,但由於內部規則已先被規定,使用上就較難有變化。例如我們現在要比較兩個陣列是否相等,想要丟入兩個陣列的話就做不到,除非我們來修改callback function:

var compare = function (ary1, ary2) {
	if (ary1.length !== ary2.length) {
		throw new Error('不相等');
	}
	//一樣使用each,但是修改callback裡的內容可以達到我們的需求
	each(ary1, function (i, n) {
		if (n !== ary2[i]) {
			throw new Error('不相等');
		}
	});
	console.log('相等');
};
compare([1, 2, 3], [1, 2, 4]);

像遇到上述這種狀況我們還可以有更好的選擇:使用外部迭代器
外部迭代器增加了一些呼叫的複雜度,但相對多了一些靈活性,來實做一下:

首先,我們先定義迭代器的一些狀態(isDone),並提供介面可以控制順序(next)以及獲取元素的方法(getCurrentItem)

var Iterator = function(obj) {
	var current = 0;
	var next = function() { current++; };
	var isDone = function() { return current >= obj.length };
	var getCurrentItem = function() { return obj[current] };
	return {
		next: next,
		isDone: isDone,
		getCurrentItem: getCurrentItem
	}
};

再來我們來實作compare功能:

var compare = function(iterator1, iterator2) {
	while (!iterator1.isDone() && !iterator2.isDone()) {
		if (iterator1.getCurrentItem() !== iterator2.getCurrentItem()) {
			throw new Error('不相等');
		}
		iterator1.next();
		iterator2.next();
	}
	console.log('相等');
};
//實際使用
var a = [1, 2, 3];
var b = [1, 2, 4];
compare(Iterator(a), Iterator(b));

整個過程Iterator只負責獲取物件以及提供物件的狀態,真正比較是在compare裡。

外部迭代器呼叫方式相對複雜但適用面較廣,實際上內部外部迭代器沒有優劣之分,可以依據需求來使用。另外迭代器模式不僅可以迭代陣列,還可以迭代物件,但物件必須要有長度屬性,並且可以用下標存取。jQuery的each函數就提供了物件的迭代功能,大家可以打$.each看一下裡面的code。

另外,迭代器可以利用for的break功能,提供一個中止迭代器的方式,我們再改寫一下一開始的each:

var each = function(ary, callback, _this) {
	for (var i = 0; i < ary.length; i++) {
		if (callback.call(_this, i, ary[i]) === false) {
			break;
		};
	}
};

each([1, 2, 3], function(index, number) {
	if (number > 2) {
		return false;
	}
	console.log(index, number);
}, this);

最後再舉一個迭代實際上應用的例子,'JavaScript設計模式與開發實踐'的作者在這裡舉了以前專案實際的範例,
我把一些實作內容拿掉專注在模式上,以下是我簡化之後的範例:

var getUploadObj = function() {
	try {
		return new IEUploadObj();
	} catch (e) {
		if (supportFlash()) {
			//flash上傳
			return flashUploadObj();
		} else {
			//表單上傳
			return formUploadObj();
		}
	}
};

這段程式碼配合的需求是在不同瀏覽器環境中選用不同的上傳檔案方式,例如支援IE的上傳控制項的話就用IEUploadObj,如果沒有就用需要Flash支援的flashUploadObj,再沒有支援Flash就用原生表單formUploadObj上傳。那上面這樣寫的缺點就有分支過多、違反開放-封閉原則,若還有要新增方法的話就要深入getUploadObj裡頭增加條件分支,因此我們可以用迭代器模式改造一下:

一樣先來做個迭代器,讓每一種上傳方法照順序來執行檢測,若回傳false就表示要進到下一個方法

var iteratorUploadObj = function() {
	for (var i = 0, fn; fn = arguments[i++];) {
		var uploadObj = fn();
		if (uploadObj !== false) {
			return uploadObj;
		}
	}
};

這時候無論有多少個方法要加入,就只要將方法實作的內容定義好,然後依照順序丟入就可以用啦

var getUploadObjA = function() {
	//A方法實作內容
};

var getUploadObjB = function() {
	//B方法實作內容
};

var uploadObj = iteratorUploadObj(getUploadObjA, getUploadObjB);

OK,以上就是迭代器模式


上一篇
JS Design Pattern Day03-代理模式 Proxy
下一篇
JS Design Pattern Day05-發佈/訂閱模式 Publish/Subscribe(上)
系列文
JS Design Pattern 30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言