迭代器模式就是提供一種方法順序訪問一個聚合對象中的各個元素,而又不須要暴露該對象的內部表示。在JavaScript中,例如forEach的實現就是一種迭代器模式,它能夠遍歷數組。jQuery中的each方法既能夠遍歷數組也能夠遍歷對象,它也是迭代器模式的一種實現。javascript
內部迭代器的內部已經定義好了迭代規則,它徹底接手整個迭代過程,外部只須要一次初始調用。下面實現一個簡單的內部迭代器:java
const each = function (array, callback) {
for (let i = 0, len = array.length; i < len; i++) {
callback(array[i], i, array[i])
}
}
each([1, 2, 3], function (i, n) {
console.log(i, n)
})
複製代碼
在JavaScript中,forEach和map函數都是內部迭代器的實現。數組
外部迭代器必須顯示地請求迭代下一個元素。外部迭代器雖然增長了相對的複雜度,可是也加強了迭代器的靈活性,咱們能夠本身控制迭代器的過程或順序。在ES6中,Generate函數也是外部迭代器的一種實現。下面簡單實現一種外部迭代器:瀏覽器
const iterator = function (obj) {
let current = 0
const next = function () {
current += 1
}
const isDone = function () {
return current >= obj.length
}
const getCurrentItem = function () {
return obj[current]
}
return {
next,
isDone,
getCurrentItem,
length: obj.length
}
}
複製代碼
迭代器模式不只能夠迭代數組,還能夠迭代一些類數組的對象,例如函數中的arguments和經過DOM API查詢的NodeList。例如jQuery中的each方法就能夠根據對象的類型使用不一樣的迭代策略:app
$.each = function (obj, callback) {
let value,
i = 0,
length = obj.length,
isArray = isArraylike(obj)
if (isArray) {
for (let i = 0, len = obj.length; i < len; i++) {
value = callback(obj[i], i, obj[i])
if (value === false) {
break
}
}
} else {
for (i in obj) {
value = callback(obj[i], i, obj[i])
if (value === false) {
break
}
}
}
return obj
}
複製代碼
假設咱們須要實現一個需求,根據不一樣的瀏覽器獲取相應的上傳組件對象,咱們能夠經過以下代碼來實現:函數
const getUploadObj = function () {
try {
return new ActiveXObject('TXFINActiveX.FTMUpload')
} catch (e) {
if (supportFlash()) {
const obj = '<object type="application/x-shockwave-flash"></object>'
return $(obj).appendTo($('body'))
} else {
const input = '<input name="file" type="file"/>'
return $(input).appendTo($('body'))
}
}
}
複製代碼
上面的代碼,會根據不一樣的瀏覽器環境選擇不一樣的上傳方式。優先使用控件上傳,控件不支持,則選擇Flash上傳方式,若是Flash也沒安裝,那就只好使用瀏覽器原生的表單上傳。
雖然上面的代碼也能實現需求,可是代碼裏混雜着try catch塊和if else語句,首先閱讀困難,其次違反了開閉原則。若是咱們想增長一種上傳方式,好比HTML5上傳,這時候惟一的辦法就是繼續往函數裏增長條件分支。
梳理下問題,咱們能夠看到,無論有多少種上傳方式,咱們都要不斷去嘗試每一種方式,直到找到合適的方式。因而咱們能夠把每種upload對象的方法封裝在各自的函數裏,經過迭代器獲取這些upload對象,直到獲取到一個可用的爲止。下面經過代碼來實現:ui
const getActiveUpload = function () {
try {
return new ActiveXObject('TXFINActiveX.FTMUpload')
} catch (e) {
return false
}
}
const getFlashUpload = function () {
if (supportFlash()) {
const obj = '<object type="application/x-shockwave-flash"></object>'
return $(obj).appendTo($('body'))
}
return false
}
const getFormUpload = function () {
const input = '<input name="file" type="file"/>'
return $(input).appendTo($('body'))
}
複製代碼
上面封裝的三種upload對象,若是被瀏覽器支持,則返回對應的upload對象,不然返回false,提示迭代器繼續進行。下面來實現upload iterator:spa
const iteratorUpload = function () {
for (let i = 0, len = arguments.length; i < len; i++) {
const uploadObj = arguments[i]()
if (uploadObj !== false) {
return uploadObj
}
}
}
const uploadObj = iteratorUpload(getActiveUpload, getFlashUpload, getFormUpload)
複製代碼
使用迭代器重構以後,咱們能夠看到不一樣上傳對象的方法被隔離在不一樣的函數中,互不干擾,使代碼更容易維護,提升了代碼的擴展性。若是咱們後續還要增長其餘的上傳方式,只須要定義新的函數,而後再按優先級把不一樣的上傳函數傳遞給iteratorUpload迭代器。code