3
Wechat search [Great Relocation to the World], I will share with you the front-end industry trends, learning methods, etc. as soon as possible.
This article GitHub https://github.com/qq449245884/xiaozhi has been included, there are complete test sites, materials and my series of articles for interviews with first-line manufacturers.

Lazy evaluation

Lazy evaluation often translated as "lazy evaluation" or "lazy evaluation", which refers to evaluating the value of an expression only when it is actually needed.

The opposite of ---4d4777b6e199e320b0efc05f1d55b700 惰性求值 is 及早求值(eager evaluation) Early evaluation, also known as 贪婪求值 (greedy evaluation) or strict evaluation, is the evaluation of most traditional programming languages Strategy.

The benefits of taking full advantage of the features of 惰性求值 are mainly reflected in the following two aspects:

  • Avoid unnecessary calculations and bring performance improvements.
  • Save space and make infinite loop data structures possible.

iterator

Iterators in ES6 enable lazy evaluation and creation of user-defined sequences of data. Iteration is a mechanism for iterating over data. An iterator is a pointer used to traverse the elements of a data structure (called Iterable ), yielding a pointer to a sequence of values.

An iterator is an object that can be iterated over. It abstracts the data container so that it behaves like an iterable object.

Iterators do not compute the value of each item when instantiated, and only generate the next value when requested. This is very useful, especially for large datasets or sequences with infinite elements.

iterable object

Iterables are data structures whose elements are expected to be publicly accessible. A lot of objects in JS are iterable, they may not be very noticeable, but if you examine carefully, you will find the characteristics of iteration:

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

There also needs to be an iterable object, otherwise, it will throw a type error, for example:

  • for ... of
  • ... (expand operator)
    const [a, b, ..] = iterable (destructuring assignment)
  • yield* (Generator)

There are already many built-in iterables in JavaScript:

String , Array , TypedArray , Map , Set

iterative protocol

Iterators and iterables follow 迭代协议 .

A protocol is a set of interfaces and specifies how to use them.

Iterators follow the iterator protocol, and iterables follow the iterable protocol.

iterable protocol

To make an object iterable, it must implement an iterator method via Symbol.iterator which is a factory for iterators.

With TypeScript, the iterable protocol looks like this:

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

Symbol.iterator]() is a parameterless function. Calling it on an iterable means we can access the iterable by this which can be a regular function or a generator function.

iterator protocol

The iterator protocol defines standard methods for producing sequences of values.

In order for an object to be an iterator, it must implement the next() method. Iterators can implement the return() method, which we will discuss later in this article.

With TypeScript, the iterator protocol looks like this:

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

IteratorResult is defined as follows:

 interface IteratorResult {
    value?: any;
    done: boolean;
}
  • done notifies the consumer whether the iterator has been used, false indicates that there are still values to be generated, true indicates that the iterator has ended.
  • value can be any JS value, it is the value displayed to the consumer.

When done is true , ---e333ba7329e5dbcbff36eb14be317d81 value can be omitted.

combination

Iterators and iterable objects can be represented by the following diagram:

clipboard.png

case

The basic knowledge has been introduced, and then, let's deepen our image with some examples.

range iterator

Let's start with a very basic iterator, the createRangeIterator iterator.

We manually call it.next() to get the next IteratorResult . The last call returned {done:true} , which means that the iterator is now used and no longer yields any value.

 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

iterable range iterator

Earlier in this article, I mentioned that some statements in JS require an iterable object. So our previous example will not work when used with the for ... of loop.

But creating objects that conform to 迭代器 and 可迭代协议 is very easy.

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)
}

infinite sequence iterator

Iterators can represent sequences of unlimited size because they only compute values when needed.

Be careful not to use the spread operator ( ... ) on infinite iterators, JS will try to consume the iterator and since the iterator is infinite it will never end. So your app will crash because memory is exhausted 😱

The same is true for the for ... of loop, so make sure to exit the loop:

 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

close iterator

We mentioned earlier that iterators can optionally use the return() method. Use this method when the iterator has not iterated until the end, and let the iterator clean up.

for ... of The loop can terminate the iteration earlier by:

  • 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

  • If you know that the iterator has ended, manually call the cleanup() function.
  • If done abruptly, return() works and cleans up for us.

💥 Extra Content

If you've made it this far, let's take a look at some extras.

combiner

A combinator is a function that combines existing iterables together to create a new iterable.

Therefore, we were able to create many utility functions. What about map or filter ? Take a look at the code below and take a minute to understand it.

 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);

That's a whole bunch of code, and soon we'll see how to refactor all of this using generators and functional programming concepts. Stay tuned, and keep an eye out for my follow-up article, we still have a lot to talk about.

comminicate

If you have dreams and dry goods, you can search for [Great Move to the World] on WeChat and pay attention to this Shawanzhi who is still washing dishes in the early hours of the morning.

This article GitHub https://github.com/qq449245884/xiaozhi has been included, there are complete test sites, materials and my series of articles for interviews with first-line manufacturers.

The bugs that may exist in editing cannot be known in real time. In order to solve these bugs afterwards, a lot of time is spent on log debugging. By the way, here is a useful BUG monitoring tool , Fundebug .

Author: MelkorNemesis Translator: Front-end Xiaozhi Source: medium

Original: https://medium.com/@MelrNemesis/javascript-lazy-evaluation-iterables-iterators-e0770a5de96f


王大冶
68k 声望104.9k 粉丝