javascript 設計模式之迭代器模式

文章系列

javascript 設計模式之單例模式javascript

javascript 設計模式之適配器模式java

javascript 設計模式之裝飾者模式node

javascript設計模式之代理模式web

javascript 適配、代理、裝飾者模式的比較設計模式

javascript 設計模式之狀態模式數組

javascript 設計模式之迭代器模式markdown

javascript 設計模式之策略模式數據結構

javascript 設計模式之觀察者模式app

javascript 設計模式之發佈訂閱者模式函數

概念

迭代器模式提供一種方法順序訪問一個聚合對象中的各個元素,而又不暴露該對象的內部表示。

特色:

  1. 爲遍歷不一樣數據結構的 「集合」 提供統一的接口;
  2. 能遍歷訪問 「集合」 數據中的項,不關心項的數據結構

從 Array.prototype.forEach 提及

let arr = [1, 2, 3]
arr.forEach((item) => {
	console.info(item) // 1 2 3
})
複製代碼

在數組上遍歷是沒有問題的,但將它做爲在 NodeList 就會發現報錯

<div id="div1">
    <a href="#">a1</a>
    <a href="#">a2</a>
    <a href="#">a3</a>
    <a href="#">a4</a>
    <a href="#">a5</a>
</div>
複製代碼

遍歷 id 爲 div1 下的全部 a 標籤

let nodeList = document.getElementById('div1').getElementsByTagName('a')
nodeList.forEach((item) => {
	console.info(item)
})
複製代碼

運行會報Uncaught TypeError: nodeList.forEach is not a function 錯誤。 這是因爲 nodeList 只是類數組,並無實現 forEach 方法。

使用 for 循環遍歷

let nodeList = document.getElementById('div1').getElementsByTagName('a')
let i, length = nodeList.length
for (i = 0; i < length; i++) {
	console.info(nodeList[i].innerHTML)
}
複製代碼

能夠正常打印

jQuery 的 each 方法

上述的數組跟 nodeList 採用不一樣的遍歷方式,爲了處理這種狀況,經過藉助 jQuery 的 each 方法,咱們能夠用同一套遍歷規則遍歷不一樣的集合對象:

let arr = [1, 2, 3]
let nodeList = document.getElementById('div1').getElementsByTagName('a')
let $a = $('a')
$.each(arr, function (index, item) {
    console.log(`數組的第${index}個元素是${item}`)
})
$.each(nodeList, function (index, aNode) {
    console.log(`DOM類數組的第${index}個元素是${aNode.innerText}`)
})
$.each($a, function (index, aNode) {
    console.log(`jQuery集合的第${index}個元素是${aNode.innerText}`)
})
複製代碼

能夠正常輸出內容

自定義個迭代器

// 主體
class Container {
    constructor(list) {
        this.list = list
    }
    // 生成遍歷器
    getIterator() {
        return new Iterator(this)
    }
}

// 迭代器
class Iterator {
    constructor(container) {
        this.list = container.list
        this.index = 0
    }
    next() {
        if (this.hasNext()) {
            return this.list[this.index++]
        }
        return null
    }
    hasNext() {
        if (this.index < this.list.length) {
            return true
        }
        return false
    }
}

const arr = [1, 2, 3, 4, 5, 6] // 是有序的集合,好比數組,nodeList
const container = new Container(arr)
const iterator = container.getIterator()
while (iterator.hasNext()) {
    console.info(iterator.next())
}

複製代碼

ES6 以後的迭代器

ES6 新增了 Set 和 Map ,致使目前有序集合有 Array Map Set String TypedArray arguments NodeList 這麼多種,須要一個統一的遍歷接口訪問這些有序集合中的數據,因此 ES6 引入了 Iterator。

ES6 默認的 Iterator 接口部署在數據結構的 [Symbol.iterator] 屬性上,該屬性自己是一個函數,執行這個函數會返回個遍歷器對象。

遍歷器對象的特徵:

  • 擁有 next 方法
  • 執行 next(),會返回個包含 value 和 done 屬性的對象
    • value: 當前數據結構成員的值
    • done: 布爾值,表示遍歷是否結束

在數組上測試下

let arr = [1, 2, 3]
let iterator = arr[Symbol.iterator]();
console.info(iterator.next()) // { value: '1', done: false }
console.info(iterator.next()) // { value: '2', done: false }
console.info(iterator.next()) // { value: '3', done: false }
console.info(iterator.next()) // { value: undefined, done: true }
複製代碼

代碼優化並封裝個 each 方法:

function each(data) {
    const iterator = data[Symbol.iterator]() // 生成迭代器
    let item = { done: false }
    while (!item.done) {
        item = iterator.next()
        if (!item.done) {
            console.info(item)
            console.info(item.value)
        }
    }
}
let arr = [1, 2, 3]
let nodeList = document.getElementsByTagName('P')
let map = new Map()
map.set('name', 'zhangSan')
map.set('age', 12)
each(arr)
each(nodeList)
each(map)
複製代碼

只要具備 [Symbol.iterator] 屬性的集合,就可使用 each 方法進行遍歷

for of

Symbol.iterator並非人人都知道的,也不是每一個人都須要封裝一個 each 方法,所以,ES6提供了一個新的方法 for...of 來遍歷:

// for...of 自動遍歷擁有 Iterator 接口的數據結構
let arr = [1, 2, 3];
for (let item of arr) {
  console.log(item);
}

// 輸出:1 2 3
複製代碼

說明 for...of 只是個語法糖,運行原理:

  1. 首先調用遍歷對象 Symobo.iterator 方法,拿到遍歷器對象;
  2. 每次循環,調用遍歷器對象 next() 方法,獲得 {value: ..., done: ... } 對象

等價於下面:

// 經過調用iterator,拿到迭代器對象
const iterator = arr[Symbol.iterator]()

// 初始化一個迭代結果
let now = { done: false }

// 循環往外迭代成員
while(!now.done) {
    now = iterator.next()
    if(!now.done) {
        console.log(`如今遍歷到了${now.value}`)
    }
}
複製代碼

ES6 Iterator 和 Generator

Iterator 的價值不限於上述幾個類型的遍歷,還有 Generator 函數的使用。Generator 返回的數據符合 Iterator 接口遍歷的要求,因此 Generator 函數也可使用 Iterator 語法。

function* helloWorldGenerator() {
    yield 'hello'
    yield 'world'
    return 'ending'
}
// var hw = helloWorldGenerator()
// console.info(hw)
// hw.next() //{value: "hello", done: false}
// hw.next() //{value: "world", done: false}
// hw.next() //{value: "ending", done: true}
// hw.next() //{value: undefined, done: true}
// console.info(hw[Symbol.iterator])

for (var item of helloWorldGenerator()) {
    console.info(item) 
    // hello 
    // world
}

複製代碼

根據這個特色能夠用 for...of 遍歷普通對象

for...of 遍歷普通對象的解決方法:

  1. 使用 Objet.keys 將對象鍵名生成一個數組,而後遍歷該數組;
  2. Generator 函數從新包裝對象
let person = {
  name: 'Ken',
  sex: 'Male'
}

// Generator 包裝對象
function* entries(obj) {
  for (let key of Object.keys(obj)) {
    yield [key, obj[key]];
  }
}
for (let [key, value] of entries(person)) {
  console.log(`${key}: ${value}`);
}

// 輸出:
// name: Ken 
// sex: Male
複製代碼

迭代器分類

內部迭代器

內部已經定義好了迭代規則,它徹底接手整個迭代過程,外部只須要一次初始調用

實現:

function each(arr, fn) {
  for (let i = 0; i < arr.length; i++) {
    fn(i, arr[i])
  }
}

each([1, 2, 3], function(i, n) {
  console.log(i) // 0 1 2
  console.log(n) // 1 2 3
})
複製代碼

優缺點:

  • 優勢:內部迭代器在調用的時候很是方便,外界不用關心迭代器內部的實現,跟迭代器的交互也僅僅是一次初始調用
  • 缺點:因爲內部迭代器的迭代規則已經被提早規定,上面的 each 函數就沒法同時迭代2個數組,好比要比較兩數組是否相等, 只能在其回調函數中做文章了, 代碼以下:
var compare = function( ary1, ary2 ){
  if ( ary1.length !== ary2.length ){
    throw new Error ( 'ary1 和ary2 不相等' );
  }
  each( ary1, function( i, n ){
    if ( n !== ary2[ i ] ){
      throw new Error ( 'ary1 和ary2 不相等' );
    }
  });
  alert ( 'ary1 和ary2 相等' );
};
compare( [ 1, 2, 3 ], [ 1, 2, 4 ] ); // throw new Error ( 'ary1 和ary2 不相等' );
複製代碼

jQuery 的 $.each 以及 for...of 也是內部迭代器

外部迭代器

外部迭代器必須顯式地請求迭代下一個元素

上面的"自定義個迭代器" 以及 Generator yield 就是外部迭代器。 解決下兩個數組比較問題:

const arr1 = [1, 2, 3], arr2 = [1, 2, 3]
const container1 = new Container(arr1), container2 = new Container(arr2)
const iterator1 = container1.getIterator(), iterator2 = container2.getIterator()
function compare(iterator1, iterator2) {
  while (iterator1.hasNext() || iterator2.hasNext()) {
    if (iterator1.next() !== iterator2.next()) {
      return false
    }
  }
  return true
}
console.info(compare(iterator1, iterator2))
複製代碼

優缺點:

  • 優勢:外部迭代器將遍歷的權利轉移到外部, 所以在調用的時候擁有了更多的自由性,
  • 缺點:調用方式較複雜

參考連接

JavaScript設計模式與開發實踐

結語

你的點贊是對我最大的確定,若是以爲有幫助,請留下你的讚揚,謝謝!!!

相關文章
相關標籤/搜索