-
Notifications
You must be signed in to change notification settings - Fork 0
Description
迭代器模式
迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺序访问其中的每个元素。
###实现自己的迭代器
现在我们来自己实现一个 each 函数,each 函数接受 2 个参数,第一个为被循环的数组,第二个为循环中的每一步后将被触发的回调函数:
const each = (ary, callback) => {
for (let i = 0, l = ary.length; i < l; i++) {
// 把下标和元素当作参数传给 callback 函数
callback.call(ary[i], i, ary[i])
}
}
each([1, 2, 3], (i, n) => {
alert([i, n])
})
内部迭代器和外部迭代器
迭代器可以分为内部迭代器和外部迭代器,它们有各自的适用场景。这一节我们将分别讨论这两种迭代器。
1.内部迭代器
上面刚写的each就是内部迭代器,它内部已经定义好了迭代规则,它完全接手整个迭代过程,外部只需要一次初始调用。这也暴露出内部迭代器的缺点,即只能按照迭代器内部的规则进行迭代,例如each不能同时迭代两个数组
2.外部迭代器
- 外部迭代器必须显式地请求迭代下一个元素。
- 外部迭代器增加了一些调用的复杂度,但相对也增强了迭代器的灵活性,我们可以手工控制迭代的过程或者顺序。
以“判断两数组是否相等”这个需求为例,加深对内部迭代器与外部迭代器的理解。
【内部迭代器实现】
const compare = (ary1, ary2) => {
if (ary1.length !== ary2.length) {
throw new Error('ary1 和 ary2 不相等')
}
each(ary1, (i, n) => {
if (n !== ary2[i]) {
throw new Error('ary1 和 ary2 不相等')
}
})
console.log('ary1 和 ary2 相等')
}
compare([1, 2, 3], [1, 2, 4]) // throw new Error ( 'ary1 和 ary2 不相等' )
【外部迭代器实现】
const Iterator = obj => {
let current = 0
const next = () => {
current++
}
const isDone = () => current >= obj.length
const getCurrItem = () => obj[current]
return {
next: next,
isDone: isDone,
getCurrItem: getCurrItem
}
}
const compare = (iterator1, iterator2) => {
while (!iterator1.isDone() && !iterator2.isDone()) {
if (iterator1.getCurrItem() !== iterator2.getCurrItem()) {
throw new Error('iterator1 和 iterator2 不相等')
}
iterator1.next()
iterator2.next()
}
console.log('iterator1 和 iterator2 相等')
}
const iterator1 = Iterator([1, 2, 3])
const iterator2 = Iterator([1, 2, 3])
compare(iterator1, iterator2) // 输出:iterator1 和 iterator2 相等
外部迭代器虽然调用方式相对复杂,但它的适用面更广,也能满足更多变的需求。内部迭代
器和外部迭代器在实际生产中没有优劣之分,究竟使用哪个要根据需求场景而定。
###迭代类数组对象和字面量对象
迭代器模式不仅可以迭代数组,还可以迭代一些类数组的对象。比如 arguments、{"0":'a',"1":'b'}等。通过上面的代码可以观察到,无论是内部迭代器还是外部迭代器,只要被迭代的聚合对象拥有 length 属性而且可以用下标访问,那它就可以被迭代。在 JavaScript 中,for in 语句可以用来迭代普通字面量对象的属性。jQuery 中提供了$.each`函数来封装各种迭代行为:
$.each = (obj, callback) => {
let value,
i = 0
const length = obj.length
const 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
}
###倒序迭代器
迭代器模式提供了循环访问一个聚合对象中每个元素的方法,但它没有规定我们以顺序、倒序还是中序来循环遍历聚合对象。下面我们实现一个倒序访问的迭代器:
const reverseEach = (ary, callback) => {
for (const l = ary.length - 1; l >= 0; l--) {
callback(l, ary[l])
}
}
reverseEach([0, 1, 2], (i, n) => {
console.log(n) // 分别输出:2, 1 ,0
})
中止迭代器
迭代器可以像普通 for 循环中的 break 一样,提供一种跳出循环的方法。
if (value === false) {
break
}
约定如果回调函数的执行结果返回 false,则提前终止循环,终止迭代器示例如下:
const each = (ary, callback) => {
for (let i = 0, l = ary.length; i < l; i++) {
if (callback(i, ary[i]) === false) { // callback 的执行结果返回 false,提前终止迭代
break
}
}
}
each([1, 2, 3, 4, 5], (i, n) => {
if (n > 3) { // n 大于 3 的时候终止循环
return false
}
console.log(n) // 分别输出:1, 2, 3
})
###迭代器模式的应用举例
【根据不同的浏览器获取相应的上传组件对象】
在不同的浏览器环境下,选择的上传方式是不一样的。因为使用浏览器的上传控件进行上传速度快,可以暂停和续传,所以我们首先会优先使用控件上传。如果浏览器没有安装上传控件,则使用 Flash 上传, 如果连 Flash 也没安装,那就只好使用浏览器原生的表单上传了。
const getUploadObj = () => {
try {
return new ActiveXObject("TXFTNActiveX.FTNUpload"); // IE 上传控件
} catch (e) {
if (supportFlash()) { // supportFlash 函数未提供
const str = '<object type="application/x-shockwave-flash"></object>'
return $(str).appendTo($('body'))
} else {
const str = '<input name="file" type="file"/>' // 表单上传
return $(str).appendTo($('body'))
}
}
}
以上代码虽然可以实现该需求,但是存在很明显的缺陷:
- 可读性差
- 违反开放封闭原则
后续还有其他上传方案的话只有继续添加if-else分支,比较难看。
下面我们使用迭代器模式来实现上面需求,代码如下:
// 定义各类上传方案的初始化类
const getActiveUploadObj = () => {
try {
return new ActiveXObject("TXFTNActiveX.FTNUpload") // IE 上传控件
} catch (e) {
return false
}
}
const getFlashUploadObj = () => {
if (supportFlash()) { // supportFlash 函数未提供
const str = '<object type="application/x-shockwave-flash"></object>'
return $(str).appendTo($('body'))
}
return false
}
const getFormUpladObj = () => {
const str = '<input name="file" type="file" class="ui-file"/>' // 表单上传
return $(str).appendTo($('body'))
}
// 迭代器使得定义的上传组件可以按照优先级进行循环迭代
// 迭代器内部循环过程假如返回一对象,则执行对应的方法,反之则返回false继续执行
const iteratorUploadObj = () => {
for (let i = 0, fn; fn = arguments[i++];) {
const uploadObj = fn()
if (uploadObj !== false) {
return uploadObj
}
}
}
const uploadObj = iteratorUploadObj(getActiveUploadObj, getFlashUploadObj, getFormUpladObj)
很显然,迭代器模式中,功能职责的分配比较清晰,也比较容易理解,可以很方便地的维护和扩展。
总结
可以看出,合理运用迭代器模式可以使我们在开发过程很清楚地规划代码逻辑,使我们编写的代码拥有很好的维护性和拓展性,实现成本也比较低,还是比较简单,很容易理解的,更何况现在包括JavaScript在内的大部分语言都内置迭代器,我们只需遵循一定的规则去使用就好了。