Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

javascript设计模式 - 迭代器模式 #4

Open
mhahaha opened this issue Nov 3, 2018 · 0 comments
Open

javascript设计模式 - 迭代器模式 #4

mhahaha opened this issue Nov 3, 2018 · 0 comments

Comments

@mhahaha
Copy link
Owner

mhahaha commented Nov 3, 2018

迭代器模式

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

###实现自己的迭代器
现在我们来自己实现一个 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在内的大部分语言都内置迭代器,我们只需遵循一定的规则去使用就好了。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant