JavaScript Lazy evaluation:可迭代對象與迭代器

做者:MelkorNemesis
譯者:前端小智
來源:medium
點贊再看,養成習慣

本文 GitHub https://github.com/qq44924588... 上已經收錄,更多往期高贊文章的分類,也整理了不少個人文檔,和教程資料。歡迎Star和完善,你們面試能夠參照考點複習,但願咱們一塊兒有點東西。javascript

Lazy evaluation

Lazy evaluation常被譯爲「延遲計算」或「惰性計算」,指的是僅僅在真正須要執行的時候才計算表達式的值。前端

惰性求值相反的是及早求值(eager evaluation)及早求值,也被稱爲貪婪求值(greedy evaluation)或嚴格求值,是多數傳統編程語言的求值策略。java

充分利用惰性求值的特性帶來的好處主要體如今如下兩個方面:git

  • 避免沒必要要的計算,帶來性能上的提高。
  • 節省空間,使得無限循環的數據結構成爲可能。

迭代器

ES6 中的迭代器使惰性求值和建立用戶定義的數據序列成爲可能。迭代是一種遍歷數據的機制。 迭代器是用於遍歷數據結構元素(稱爲Iterable)的指針,用於產生值序列的指針。github

迭代器是一個能夠被迭代的對象。它抽象了數據容器,使其行爲相似於可迭代對象。面試

迭代器在實例化時不計算每一個項目的值,僅在請求時才生成下一個值。 這很是有用,特別是對於大型數據集或無限個元素的序列。編程

可迭代對象

可迭代對象是但願其元素可被公衆訪問的數據結構。JS 中的不少對象都是可迭代的,它們可能不是很好的察覺,可是若是仔細檢查,就會發現迭代的特徵:微信

  • new Map([iterable])
  • new WeakMap([iterable])
  • new Set([iterable])
  • new WeakSet([iterable])
  • Promise.all([iterable])
  • Promise.race([iterable])
  • Array.from([iterable])

還有須要一個可迭代的對象,不然,它將拋出一個類型錯誤,例如:數據結構

  • for ... of
  • ... (展開操做符)

const [a, b, ..] = iterable (解構賦值)編程語言

  • yield* (生成器)

JavaScript中已有許多內置的可迭代項:

String,Array,TypedArray,Map,Set

迭代協議

迭代器和可迭對象遵循迭代協議

協議是一組接口,並規定了如何使用它們。

迭代器遵循迭代器協議,可迭代遵循可迭代協議。

可迭代的協議

要使對象變得可迭代,它必須實現一個經過Symbol.iterator的迭代器方法,這個方法是迭代器的工廠。

使用 TypeScript,可迭代協議以下所示:

interface Iterable {
  [Symbol.iterator]() : Iterator;
}

Symbol.iterator]()是無參數函數。 在可迭代對象上調用它,這意味着咱們能夠經過this來訪問可迭代對象,它能夠是常規函數或生成器函數。

迭代器協議

迭代器協議定義了產生值序列的標準方法。

爲了使對象成爲迭代器,它必須實現next()方法。 迭代器能夠實現return()方法,咱們將在本文後面討論這個問題。

使用 TypeScript,迭代器協議以下所示:

interface Iterator {
    next() : IteratorResult;
    return?(value?: any): IteratorResult;
}

IteratorResult 的定義以下:

interface IteratorResult {
    value?: any;
    done: boolean;
}
  • done通知消費者迭代器是否已經被使用,false表示仍有值須要生成,true表示迭代器已經結束。
  • value 能夠是任何 JS 值,它是向消費者展現的值。

donetrue時,能夠省略value

組合

迭代器和能夠可迭代對象能夠用下面這張圖來表示:

clipboard.png

事例

基礎知識介紹完了,接着,咱們來配合一些事例來加深咱們的映像。

範圍迭代器

咱們先從一個很是基本的迭代器開始,createRangeIterator迭代器。

咱們手動調用it.next()以得到下一個IteratorResult。 最後一次調用返回{done:true},這意味着迭代器如今已被使用,再也不產生任何值。

function createRangeIterator(from, to) {
  let i = from;

  return {
    next() {
      if (i <= to) {
        return { value: i++, done: false };
      } else {
        return { done: true };
      }
    }
  }
}

const it = createRangeIterator(1, 3);

console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());

clipboard.png

可迭代範圍迭代器

在本文的前面,我已經提到 JS 中的某些語句須要一個可迭代的對象。 所以,咱們前面的示例在與for ... of循環一塊兒使用時將不起做用。

可是建立符合迭代器可迭代協議的對象很是容易。

clipboard.png

function createRangeIterator (from, to) {
  let i = from

  return {
    [Symbol.iterator] () {
      return this
    },
    next() {
      if (i <= to) {
        return { value: i++, done: false }
      } else {
        return { done: true }
      }
    }
  }
}

const it = createRangeIterator(1, 3)

for (const i of it) {
  console.log(i)
}

無限序列迭代器

迭代器能夠表示無限制大小的序列,由於它們僅在須要時才計算值。

注意不要在無限迭代器上使用擴展運算符(...),JS 將嘗試消費迭代器,因爲迭代器是無限的,所以它將永遠不會結束。 因此你的應用程序將崩潰,由於內存已被耗盡 😱

一樣,for ... of 循環也是同樣的狀況,因此要確保能退出循環:

function createEvenNumbersIterator () {
  let value = 0

  return {
    [Symbol.iterator] () {
      return this
    },
    next () {
      value += 2
      return { value, done: false}
    }
  }
}

const it = createEvenNumbersIterator()

const [a, b, c] = it
console.log({a, b, c})

const [x, y, z] = it
console.log({ x, y, z })

for (const even of it) {
  console.log(even)
  if (even > 20) {
    break
  }
}

clipboard.png

關閉迭代器

前面咱們提到過,迭代器能夠有選擇地使用return()方法。 當迭代器直到最後都沒有迭代時使用此方法,並讓迭代器進行清理。

for ... of循環能夠經過如下方式更早地終止迭代:

  • break
  • continue
  • throw
  • return
function createCloseableIterator () {
  let idx = 0
  const data = ['a', 'b', 'c', 'd', 'e']

  function cleanup() {
    console.log('Performing cleanup')
  }
  return {
    [Symbol.iterator]() { return this },
    next () {
      if (idx <= data.length - 1) {
        return { value: data[idx++], done: false }
      } else {
        cleanup()
        return { done: true }
      }
    },
    return () {
      cleanup()
      return { done: true }
    }
  }
}

const it = createCloseableIterator()

for (const value of it) {
  console.log(value)
  if (value === 'c') {
    break
  }
}

console.log('\n----------\n')

const _it = createCloseableIterator();
for (const value of _it) {
  console.log(value);
}

clipboard.png

  • 若是知道迭代器已經結束,則手動調用cleanup()函數。
  • 若是忽然完成,則return()起做用併爲咱們進行清理。

💥 額外的內容

若是你已經作到了這一點,咱們來看看一些額外的內容。

組合器

組合器是將現有可迭代對象組合在一塊兒以建立新可迭代對象的函數。

所以,咱們可以建立許多實用函數。那map或者filter呢?看看下面的代碼,花一分鐘時間來理解它。

function createEvenNumbersIterator() {
  let value = 0;

  return {
    [Symbol.iterator]() {
      return this;
    },
    next() {
      value += 2;
      return { value, done: false };
    }
  }
}

function map(fn, iterable) {
  const iter = iterable[Symbol.iterator]();

  return {
    [Symbol.iterator]() {
      return this;
    },
    next() {
      const n = iter.next();
      if (!n.done) {
        return { value: fn(n.value), done: false };
      } else {
        return { done: true };
      }
    }
  }
}

function filter(fn, iterable) {
  const iter = iterable[Symbol.iterator]();

  return {
    [Symbol.iterator]() {
      return this;
    },
    next() {
      const n = iter.next();
      if (!n.done) {
        if (fn(n.value)) {
          return { value: n.value, done: false };
        } else {
          return this.next();
        }
      } else {
        return { done: true };
      }
    }
  }
}

function take(n, iterable) {
  const iter = iterable[Symbol.iterator]();

  return {
    [Symbol.iterator]() {
      return this;
    },
    next() {
      if (n > 0) {
        n--;
        return iter.next();
      } else {
        return { done: true };
      }
    }
  }
}

function cycle(iterable) {
  const iter = iterable[Symbol.iterator]();
  const saved = [];
  let idx = 0;
  
  return {
    [Symbol.iterator]() {
      return this;
    },
    next() {
      const n = iter.next();
      if (!n.done) {
        saved[idx++] = n.value;
        return { value: n.value, done: false };
      } else {
        return { value: saved[idx++ % saved.length], done: false };
      }
    }
  }
}

function collect(iterable) {
  // consumes the iterator
  return Array.from(iterable);
}

const evenNumbersIterator = createEvenNumbersIterator();
const result = collect(                 // 7. and collect the result
  filter(                               // ⬆️ 6. keep only values higher than 1
    val => val > 1, map(                // ⬆️ 5. divide obtained values by 2
      val => val / 2, take(             // ⬆️ 4. take only six of them
        6, cycle(                       // ⬆️ 3. make an infinite cycling sequence of them
          take(                         // ⬆️ 2. take just three of them
            3, evenNumbersIterator      // ⬆️ 1. infinite sequence of even numbers
          )
        )
      )
    )
  )
);

console.log(result);

這是一大堆代碼,很快咱們將看到如何使用生成器和函數式編程概念來重構全部這些內容。保持關注,並注意個人後續文章,咱們仍然有不少內容要講。


代碼部署後可能存在的BUG無法實時知道,過後爲了解決這些BUG,花了大量的時間進行log 調試,這邊順便給你們推薦一個好用的BUG監控工具 Fundebug

原文:https://medium.com/@MelrNemes...

交流

文章每週持續更新,能夠微信搜索「 大遷世界 」第一時間閱讀和催更(比博客早一到兩篇喲),本文 GitHub https://github.com/qq449245884/xiaozhi 已經收錄,整理了不少個人文檔,歡迎Star和完善,你們面試能夠參照考點複習,另外關注公衆號,後臺回覆福利,便可看到福利,你懂的。

相關文章
相關標籤/搜索