"ES6 Standard Getting Started" (Ruan Yifeng) - 17.Iterator and for ... of loop

The concept 1.Iterator (walker) of

JavaScript original means "a collection of" data structures, primarily array ( Array) and objects ( Object), has added a ES6 Mapand Set. So there is a set of four data, users can also use them in combination, define your own data structures, such as members of the array it is Map, Mapmembers object. This requires a unified interface mechanism to deal with all the different data structures.

Iterator (Iterator) is one such mechanism. It is an interface that provides a unified mechanism to access a variety of different data structures. Any data structure as long as the deployment Iterator interface, to complete traversal operation (i.e., sequentially processed all members of the data structure).

Iterator role has three: one for the various data structures, to provide a unified, easy-access interface; second is to make members of the data structure can be arranged in a certain order; the third is ES6 create a new traverse command for...ofcycle , Iterator interface is mainly for for...ofconsumption.

Iterator traversal is this.

(1) Create a pointer to the object , points to the start position of the current data structure. That is, on the object nature walker, is a pointer to the object.

(2) the first call object pointer nextmethod, the first pointer to a data structure member.

(3) a second pointer to the object call nextmethod, a pointer points to a data structure of the second member.

(4) continues to call the object pointer nextmethod, the end position until it points to the data structure.

Each call nextmethod, will return information about the current members of the data structure. Specifically, it is a return contains valueand donethe object two properties. Where valueproperty is the value of the current member, doneis a Boolean value that indicates whether the end of the traverse.

The following is a simulation nextexample of a method return value.

var it = makeIterator(['a', 'b']);

it.next() // { value: "a", done: false }
it.next() // { value: "b", done: false }
it.next() // { value: undefined, done: true }

function makeIterator(array) {
  var nextIndex = 0;
  return {
    next: function() {
      return nextIndex < array.length ?
        {value: array[nextIndex++], done: false} :
        {value: undefined, done: true};
    }
  };
}

The above code defines a makeIteratorfunction, it is generated a traversal function, simply sends a visitor object. Array ['a', 'b']implementation of this function will return the object to traverse the array (i.e., the object pointer) it.

Pointer to the object nexta method for moving the pointer. Initially, the pointer to the beginning of the array. Then, each call to nextthe method, the pointer will point to the next member of the array. The first call, point a; the second call, pointing b.

nextMethod returns an object that represents information about the current data members. This object has valueand donetwo properties, valueproperty returns a member of the current position, doneis a Boolean value that indicates whether the end of the traverse, whether that is still necessary to call again nextmethod.

In summary, the pointer to the object call nextmethod, can be given in advance to traverse the data structure.

For walker objects, done: falseand value: undefinedattributes are omitted, so that the above makeIteratorfunction can be simplified into the following form.

function makeIterator(array) {
  var nextIndex = 0;
  return {
    next: function() {
      return nextIndex < array.length ?
        {value: array[nextIndex++]} :
        {done: true};
    }
  };
}

Since only the Iterator interface standard is added to the data structure above, therefore, that traverse the data structure with which it is traversed, physically separate, you can write the corresponding data does not traverse the object structure, with a walker or simulation of a data structure of the object. The following is an infinite object traversing running example.

var it = idMaker();

it.next().value // 0
it.next().value // 1
it.next().value // 2
// ...

function idMaker() {
  var index = 0;

  return {
    next: function() {
      return {value: index++, done: false};
    }
  };
}

In the above example, it generates traversal function idMaker, returns a visitor object (i.e. the object pointer). However, no corresponding data structures, or that, traversing the object itself describes a data structure out.

If TypeScript wording, traversal interface (the Iterable), the pointer object (the Iterator) and the nextmethod returns the values of the specifications may be described as follows.

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

interface Iterator {
  next(value?: any) : IterationResult,
}

interface IterationResult {
  value: any,
  done: boolean,
}

2. Default Iterator interface

Iterator purpose interface, all data structures is to provide a unified access mechanism, i.e., for...ofthe cycle (see below). When for...ofthe loop a certain data structure traversal, the cycle will automatically look for Iterator interface.

A data structure as long as the deployment of the Iterator interface, we call this data structure is a "traversable" (iterable).

ES6 specified, the default Iterator interface deployed in the data structure of the Symbol.iteratorproperty, or that has a data structure as long as the Symbol.iteratorproperty can be considered to be "traversed" (iterable). Symbol.iteratorAttribute is itself a function of the current data structure is traversed to generate the default function. Implementation of this function will return an iterator. As for the attribute name Symbol.iterator, it is an expression that returns Symbolan object of iteratorproperty, which is a pre-defined type of special value Symbol, it should be placed in square brackets (see "Symbol" chapter).

const obj = {
  [Symbol.iterator] : function () {
    return {
      next: function () {
        return {
          value: 1,
          done: true
        };
      }
    };
  }
};

The above code, the object objis traversable (Iterable), because of having Symbol.iteratorproperties. Implementation of this property, it will return a visitor object. The fundamental characteristic of the object is to have a nextmethod. Each call nextmethod returns an information object represents the current members, with valueand donetwo properties.

Some of the data structure includes a native ES6 Iterator interface (such as arrays), i.e. without any treatment, it can be for...ofloop through. The reason is that these data structures deployed native Symbol.iteratorproperties (see below), some other data structures are not (such as object). Those who deployed Symbol.iteratorattribute data structure, called the deployment a traversal interface. Call this interface, it will return a visitor object.

Native data structure includes the following Iterator interface.

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • arguments object function
  • NodeList object

The following example is an array of Symbol.iteratorproperties.

let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();

iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }

The above code, the variable arris an array, having traversed the original interface gifted deployed in arrthe Symbol.iteratorproperties above. So, call this property, you get a visitor object.

For native deployment data structure Iterator interface, do not write your own walker generating function, for...ofthe cycle will automatically traverse them. In addition, other data structures (mainly object) Iterator interface, you need to own the Symbol.iteratorproperty above the deployment, this will be for...oflooping through.

Object (Object) is no reason why the default deployment Iterator interface, which is because the properties of the object first traversal, after which traverse the property is uncertain, developers need to manually specify. Essentially, the walker is a linear process, for any non-linear data structure, traversing the deployment interface, it is equivalent to conversion of a linear deployment. However, strictly speaking, the object traversing the deployment interface is not very necessary, because when an object is actually using the structure as a Map, ES5 Map no structure, and ES6 native offers.

If an object can be provided to for...ofthe Iterator interface called loop must be in Symbol.iteratorthe deployment method of traversing generated property (prototype object The method may also have chain).

class RangeIterator {
  constructor(start, stop) {
    this.value = start;
    this.stop = stop;
  }

  [Symbol.iterator]() { return this; }

  next() {
    var value = this.value;
    if (value < this.stop) {
      this.value++;
      return {done: false, value: value};
    }
    return {done: true, value: undefined};
  }
}

function range(start, stop) {
  return new RangeIterator(start, stop);
}

for (var value of range(0, 3)) {
  console.log(value); // 0, 1, 2
}

The above codes are a class written deployment Iterator interface. Symbol.iteratorAttribute corresponds to a function that returns the current object traversing objects after execution.

The following are examples realized by a pointer to a structure walker.

function Obj(value) {
  this.value = value;
  this.next = null;
}

Obj.prototype[Symbol.iterator] = function() {
  var iterator = { next: next };

  var current = this;

  function next() {
    if (current) {
      var value = current.value;
      current = current.next;
      return { done: false, value: value };
    } else {
      return { done: true };
    }
  }
  return iterator;
}

var one = new Obj(1);
var two = new Obj(2);
var three = new Obj(3);

one.next = two;
two.next = three;

for (var i of one){
  console.log(i); // 1, 2, 3
}

The above code is first deployed in the prototype chain constructor Symbol.iteratormethod call which returns a visitor object iterator, call the object's nextmethods, while a return value, the automatic internal pointer to the next instance.

Here is another object to add to the Iterator interface examples.

let obj = {
  data: [ 'hello', 'world' ],
  [Symbol.iterator]() {
    const self = this;
    let index = 0;
    return {
      next() {
        if (index < self.data.length) {
          return {
            value: self.data[index++],
            done: false
          };
        } else {
          return { value: undefined, done: true };
        }
      }
    };
  }
};

For array-like objects (keys and values exist lengthattributes), deployed Iterator interface, there is a simple method, it is Symbol.iteratorthe method of direct reference to an array of interfaces Iterator.

NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
// 或者
NodeList.prototype[Symbol.iterator] = [][Symbol.iterator];

[...document.querySelectorAll('div')] // 可以执行了

NodeList object is an array of similar objects, having already traversed interface directly traverse. The above code, we will traverse the interface into its array of Symbol.iteratorattributes, we can see no effect.

Here is another call array-like object array Symbol.iteratorexample of the method.

let iterable = {
  0: 'a',
  1: 'b',
  2: 'c',
  length: 3,
  [Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of iterable) {
  console.log(item); // 'a', 'b', 'c'
}

Note that ordinary objects to deploy an array of Symbol.iteratormethods, there is no effect.

let iterable = {
  a: 'a',
  b: 'b',
  c: 'c',
  length: 3,
  [Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of iterable) {
  console.log(item); // undefined, undefined, undefined
}

If the Symbol.iteratorcorresponding method is not generated traversal function (i.e., returns an iterator object), explanation will be given engine.

var obj = {};

obj[Symbol.iterator] = () => 1;

[...obj] // TypeError: [] is not a function

The above code, the variables objof Symbol.iteratorthe corresponding method is not generated traversal function, an error is reported.

With walker interfaces, data structures can be used for...ofto loop through (see below), it may also be used whileto loop through.

var $iterator = ITERABLE[Symbol.iterator]();
var $result = $iterator.next();
while (!$result.done) {
  var x = $result.value;
  // ...
  $result = $iterator.next();
}

In the above code, ITERABLEit represents a certain data structure traversal, $iteratorit traversal object. Each traversing movement of the pointer object ( nextmethods), are returned check value doneattribute, if not traverse end, the pointer moves to the next visitor object ( nextmethod), continuous cycle.

3. Call Iterator interface occasion

Some occasions call Iterator interface by default (ie Symbol.iteratormethod), the following will be introduced in addition to the for...ofcycle, there are several other occasions.

(1) deconstruction assignment

When arrays and structures deconstruction Set assignment, call the default Symbol.iteratormethod.

let set = new Set().add('a').add('b').add('c');

let [x,y] = set;
// x='a'; y='b'

let [first, ...rest] = set;
// first='a'; rest=['b','c'];

(2) Extended operator

Extended operator (...) will call the default Iterator interface.

// 例一
var str = 'hello';
[...str] //  ['h','e','l','l','o']

// 例二
let arr = ['b', 'c'];
['a', ...arr, 'd']
// ['a', 'b', 'c', 'd']

Internal expansion operator code above is called Iterator interface.

In fact, this provides a simple mechanism, it can be deployed in any data structure Iterator interface, into an array. That is, whenever a data structure deployed Iterator interface, it can be extended using the operator, which is converted to an array.

let arr = [...iterable];

(3)yield*

yield*Is back with a traversable structure, it calls the walker of the structure of the interface.

let generator = function* () {
  yield 1;
  yield* [2,3,4];
  yield 5;
};

var iterator = generator();

iterator.next() // { value: 1, done: false }
iterator.next() // { value: 2, done: false }
iterator.next() // { value: 3, done: false }
iterator.next() // { value: 4, done: false }
iterator.next() // { value: 5, done: false }
iterator.next() // { value: undefined, done: true }

(4) Other occasions

Due to iterate calls traverse the interface, so any occasions to accept arrays as parameters, in fact, are called to traverse interface. Here are some examples.

  • for...of
  • Array.from()
  • Map (), Set (), WeakMap (), WeakSet () ( for example new Map([['a',1],['b',2]]))
  • Promise.all()
  • Promise.race()

4. string Iterator interface

String is an array-like objects, also having a native Iterator interface.

var someString = "hi";
typeof someString[Symbol.iterator]
// "function"

var iterator = someString[Symbol.iterator]();

iterator.next()  // { value: "h", done: false }
iterator.next()  // { value: "i", done: false }
iterator.next()  // { value: undefined, done: true }

The above code, the call Symbol.iteratorreturns an object traversing, the traverse this next method can be invoked, for the traversal of the string.

It may cover native Symbol.iteratormethod, the purpose of modifying the behavior of the traverse.

var str = new String("hi");

[...str] // ["h", "i"]

str[Symbol.iterator] = function() {
  return {
    next: function() {
      if (this._first) {
        this._first = false;
        return { value: "bye", done: false };
      } else {
        return { done: true };
      }
    },
    _first: true
  };
};

[...str] // ["bye"]
str // "hi"

In the above code, the string str Symbol.iteratormethod is modified, so extended operator ( ...) Returns the value became bye, and the string itself or hi.

Generator function interface 5.Iterator

Symbol.iteratorThe simplest implementation of the method, or the use of Generator function to introduce the next chapter.

let myIterable = {
  [Symbol.iterator]: function* () {
    yield 1;
    yield 2;
    yield 3;
  }
}
[...myIterable] // [1, 2, 3]

// 或者采用下面的简洁写法

let obj = {
  * [Symbol.iterator]() {
    yield 'hello';
    yield 'world';
  }
};

for (let x of obj) {
  console.log(x);
}
// "hello"
// "world"

In the above code, Symbol.iteratorthe method almost without deploying any code, as long as the return value of the command given by the yield to each step.

6. return visitor object (), throw ()

In addition to traversing the object nextmethod, it may also have returnmethods and throwmethods. If you write your own visitor object generating function, it nextis to be deployed, returnmethods, and throwwhether the method of deployment is optional.

returnThe method of using the occasion is, if the for...ofloop exits in advance (usually because of a mistake, or have breakstatements), it will call returnthe method. If an object before completing the traverse, need to be cleaned or release resources, you can deploy returnmethod.

function readLinesSync(file) {
  return {
    [Symbol.iterator]() {
      return {
        next() {
          return { done: false };
        },
        return() {
          file.close();
          return { done: true };
        }
      };
    },
  };
}

In the above code, the function readLinesSyncaccepts a file object as an argument and returns a visitor object, which in addition to nextthe method, also deployed returnmethod. The following two cases will trigger the execution returnmethod.

// 情况一
for (let line of readLinesSync(fileName)) {
  console.log(line);
  break;
}

// 情况二
for (let line of readLinesSync(fileName)) {
  console.log(line);
  throw new Error();
}

In the above code, after the first line of a case where the output file, will execute returnthe method, close the file; Case 2 will be performed returnafter the method to close the file, then an error is thrown.

Note that the returnmethod must return an object, which is determined by the specifications Generator.

throwGenerator function method is mainly used with the general object traversing less than this method. See "Generator Function" chapter.

7.for ... of circulation

A data structure as long as the deployment of the Symbol.iteratorproperty, it is considered to have iterator interface, you can for...ofloop through its members. In other words, for...ofthe inner loop is a data structure called the Symbol.iteratormethod.

for...ofRange of circulating arrays may be used include, Set, and Map structure, array-like some objects (such as argumentsobjects, DOM NodeList objects), hereinafter the Generator objects, and character strings.

Array

Native array includes iteratoran interface (i.e., the default deployment Symbol.iteratorproperty), for...ofis generated by that interface on to traverse the recursive nature, may be proved by the following code.

const arr = ['red', 'green', 'blue'];

for(let v of arr) {
  console.log(v); // red green blue
}

const obj = {};
obj[Symbol.iterator] = arr[Symbol.iterator].bind(arr);

for(let v of obj) {
  console.log(v); // red green blue
}

In the above code, empty object objto deploy an array arrof Symbol.iteratorproperties, the results of objthe for...ofcycle, and produces arrexactly the same results.

for...ofExamples of loop array can replace forEachmethod.

const arr = ['red', 'green', 'blue'];

arr.forEach(function (element, index) {
  console.log(element); // red green blue
  console.log(index);   // 0 1 2
});

JavaScript original for...incycle, can only obtain key subjects you can not get the key directly. ES6 provided for...ofloop, allowing traversal key is obtained.

var arr = ['a', 'b', 'c', 'd'];

for (let a in arr) {
  console.log(a); // 0 1 2 3
}

for (let a of arr) {
  console.log(a); // a b c d
}

The above codes indicate, for...inloop reads the key name, for...ofloop reading key. If you want to through for...ofcirculation, access to the index of the array, can make use of the array instance entriesmethod and the keysmethod (see "Extended arrays" chapter).

for...ofInterface call loop traversal, the traversal interface returns an array property with only numerical index. With this for...incycle is not the same.

let arr = [3, 5, 7];
arr.foo = 'hello';

for (let i in arr) {
  console.log(i); // "0", "1", "2", "foo"
}

for (let i of arr) {
  console.log(i); //  "3", "5", "7"
}

In the above code, for...ofthe loop does not return an array arrof fooproperties.

Set structure and Map

Set Map native structure having Iterator interface, may be used as for...ofloop.

var engines = new Set(["Gecko", "Trident", "Webkit", "Webkit"]);
for (var e of engines) {
  console.log(e);
}
// Gecko
// Trident
// Webkit

var es6 = new Map();
es6.set("edition", 6);
es6.set("committee", "TC39");
es6.set("standard", "ECMA-262");
for (var [name, value] of es6) {
  console.log(name + ": " + value);
}
// edition: 6
// committee: TC39
// standard: ECMA-262

The above code demonstrates how to iterate Set structure and Map structure. There are two noteworthy, first traversal order is in accordance with the respective members are added sequentially into the data structure. Secondly, Set structure traversal, returns a value, while Map structure traversal, returns an array of two members of the array are key names and values ​​of the current members of the Map.

let map = new Map().set('a', 1).set('b', 2);
for (let pair of map) {
  console.log(pair);
}
// ['a', 1]
// ['b', 2]

for (let [key, value] of map) {
  console.log(key + ' : ' + value);
}
// a : 1
// b : 2

Calculating the generated data structure

Some of the data structure is based on existing data structure, generated computationally. For example, ES6 array, Set, Map deploy the following three methods return visitor object after the call.

  • entries()It returns an object traversing for traversing [键名, 键值]an array of. An array of index values for the key name is; for Set, key name with the same key. Map Iterator interface structure, the default is to call the entriesmethod.
  • keys() Returns an iterator object that is used to traverse all the keys.
  • values() It returns an object traversing for traversing all keys.

After three generation traverse object method call, traversed by the generated data structure are calculated.

let arr = ['a', 'b', 'c'];
for (let pair of arr.entries()) {
  console.log(pair);
}
// [0, 'a']
// [1, 'b']
// [2, 'c']

Similar arrays of objects

Array-like object comprises a number of categories. The following is for...ofrecycled for use in a string, DOM NodeList objects, argumentsan example of the object.

// 字符串
let str = "hello";

for (let s of str) {
  console.log(s); // h e l l o
}

// DOM NodeList对象
let paras = document.querySelectorAll("p");

for (let p of paras) {
  p.classList.add("test");
}

// arguments对象
function printArgs() {
  for (let x of arguments) {
    console.log(x);
  }
}
printArgs('a', 'b');
// 'a'
// 'b'

For strings, for...ofthe cycle there is a feature that will correctly identify the 32-bit UTF-16 character.

for (let x of 'a\uD83D\uDC0A') {
  console.log(x);
}
// 'a'
// '\uD83D\uDC0A'

Not all objects have a similar array Iterator interface, a simple solution is to use the Array.frommethod to be converted to an array.

let arrayLike = { length: 2, 0: 'a', 1: 'b' };

// 报错
for (let x of arrayLike) {
  console.log(x);
}

// 正确
for (let x of Array.from(arrayLike)) {
  console.log(x);
}

Objects

For ordinary objects, for...ofthe structure can not be used directly, will complain, must be deployed in order to use the Iterator interface. However, in this case, the for...incycle can still be used to traverse the keys.

let es6 = {
  edition: 6,
  committee: "TC39",
  standard: "ECMA-262"
};

for (let e in es6) {
  console.log(e);
}
// edition
// committee
// standard

for (let e of es6) {
  console.log(e);
}
// TypeError: es6[Symbol.iterator] is not a function

The above codes indicate ordinary objects for...incycle may traverse keys, for...ofthe cycle error.

One solution is to use Object.keysthe method to generate the object of an array keys, and then traverse the array.

for (var key of Object.keys(someObject)) {
  console.log(key + ': ' + someObject[key]);
}

Another method is to use the Object Generator function will repackage.

function* entries(obj) {
  for (let key of Object.keys(obj)) {
    yield [key, obj[key]];
  }
}

for (let [key, value] of entries(obj)) {
  console.log(key, '->', value);
}
// a -> 1
// b -> 2
// c -> 3

Comparison with other traversal syntax

An array, for example, JavaScript offers a variety of traversal syntax. The most original wording is forcirculating.

for (var index = 0; index < myArray.length; index++) {
  console.log(myArray[index]);
}

The wording is too much trouble, and therefore provides an array of built-in forEachmethod.

myArray.forEach(function (value) {
  console.log(value);
});

The wording of the question that can not be half-way out of the forEachloop, breakthe command or returncommands are not effective.

for...inKeys can traverse the loop array.

for (var index in myArray) {
  console.log(myArray[index]);
}

for...inCirculation has several drawbacks.

  • The digital array keys, but the for...incycle is a character string as keys "0", "1", "2" and so on.
  • for...inNot only traversal cycle numeric keys, other keys will be added manually traverse, and even key on the prototype chain.
  • In some cases, for...inthe cycle will traverse the keys in any order.

In short, for...inthe cycle is mainly designed to traverse the object, it does not apply to traverse the array .

for...ofCycle compared to the above several approaches, there are some significant advantages.

for (let value of myArray) {
  console.log(value);
}
  • It has with for...inthe same simple syntax, but without for...inthose drawbacks.
  • Unlike forEachthe method, which may be associated with break, continueand returnused in conjunction.
  • Through all the data structure provides a unified user interface.

The following is a break statement, out of for...ofExamples of the cycle.

for (var n of fibonacci) {
  if (n > 1000)
    break;
  console.log(n);
}

The above example will output the Fibonacci sequence of 1,000 or less entries. If the current item is greater than 1000, will use the breakstatement to jump out of for...ofthe loop.

Guess you like

Origin www.cnblogs.com/dxy9527/p/12515947.html
Recommended