JavaScript設計模式之迭代器模式

定義

迭代器模式就是提供一種方法順序訪問一個聚合對象中的各個元素,而又不須要暴露該對象的內部表示。在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

相關文章
相關標籤/搜索