Coding With Fun
Home Docker Django Node.js Articles Python pip guide FAQ Policy

ES6 Iterator with for... Of loop


May 08, 2021 ES6


Table of contents


1. The concept of Iterator (traverser).

JavaScript's original data structure, 数组 which represents “集合” is dominated by 对象 and objects, and ES6 Map and Set T here are four sets of data that users can use in combination to define their own data structures, such as the array's members are Map, and Map's members are objects. This requires a unified interface mechanism to handle all the different data structures.

遍历器 (Iterator) is one such mechanism. I t is an interface that provides a unified access mechanism for a variety of different data structures. Any data structure can be traversed (that is, all members of the data structure are processed in turn) by deploying the Iterator interface.

Iterator has three roles: one is to provide a unified, simple access interface for various data structures, the other is to enable members of the data structure to be arranged in a certain order, and the third is that ES6 has created a new traversal command for... O f loop, Iterator interface is mainly for... Of consumption.

Iterator's traversal process is like this.

(1) Create a pointer object that points to the beginning of the current data structure. That is, the traverser object is essentially a pointer object.

(2) The next method of the pointer object is called for the first time, and the pointer can be pointed to the first member of the data structure.

(3) The second time the next method of the pointer object is called, the pointer points to the second member of the data structure.

(4) The next method of the pointer object is called continuously until it points to the end of the data structure.

Each time the next method is called, information about the current member of the data structure is returned. S pecifically, an object that contains both value and done properties is returned. Where the value property is the value of the current member, the done property is a Boolean value that indicates whether the traversal is over.

The following is an example of simulating the return value of the next method.

  1. var it = makeIterator(['a', 'b']);
  2. it.next() // { value: "a", done: false }
  3. it.next() // { value: "b", done: false }
  4. it.next() // { value: undefined, done: true }
  5. function makeIterator(array) {
  6. var nextIndex = 0;
  7. return {
  8. next: function() {
  9. return nextIndex < array.length ?
  10. {value: array[nextIndex++], done: false} :
  11. {value: undefined, done: true};
  12. }
  13. };
  14. }

The code above defines a makeIterator function, which is a traverser generator that returns a traverser object. By executing this function on the array , the traverser object (i.e. the pointer object) it is returned to the array.

The next method of the pointer object, which is used to move the pointer. A t the beginning, the pointer points to the beginning of the array. T hen, each time the next method is called, the pointer points to the next member of the array. The first call, pointing to a;

The next method returns an object that represents information about the current data member. This object has two properties, value and done, the value property returns a member of the current position, and the done property is a Boolean value that indicates whether the traversal ends, that is, whether it is necessary to call the next method again.

In summary, by calling the next method of the pointer object, you can traverse the previously given data structure.

For traverser objects, both the done: false and value: undefined properties can be omitted, so the makeIterator function above can be simplified in the following form.

  1. function makeIterator(array) {
  2. var nextIndex = 0;
  3. return {
  4. next: function() {
  5. return nextIndex < array.length ?
  6. {value: array[nextIndex++]} :
  7. {done: true};
  8. }
  9. };
  10. }

Since Iterator simply adds interface specifications to the data structure, the traverser is actually separate from the data structure it traverses, and can write out traverser objects that do not correspond to the data structure, or simulate the data structure with traverser objects. The following is an example of an infinitely running traverser object.

  1. var it = idMaker();
  2. it.next().value // 0
  3. it.next().value // 1
  4. it.next().value // 2
  5. // ...
  6. function idMaker() {
  7. var index = 0;
  8. return {
  9. next: function() {
  10. return {value: index++, done: false};
  11. }
  12. };
  13. }

In the example above, the traverser generator idMaker returns a traverser object (that is, a pointer object). But there is no corresponding data structure, or that the traverser object itself describes a data structure.

If you use TypeScript, the specifications for the return values of the traverser interface (Iterable), pointer objects (Iterator), and next methods can be described below.

  1. interface Iterable {
  2. [Symbol.iterator]() : Iterator,
  3. }
  4. interface Iterator {
  5. next(value?: any) : IterationResult,
  6. }
  7. interface IterationResult {
  8. value: any,
  9. done: boolean,
  10. }

2. Default Iterator interface

The purpose of the Iterator interface is to provide a unified access mechanism for all data structures, i.e. for... O f loop (see below). W hen using for... When a loop traverses a data structure, the loop automatically looks for an Iterator interface.

As long as an Iterator interface is deployed, we call this data structure "traversable".

ES6 states that the default Iterator interface is deployed in the Symbol.iterator property of the data structure, or that a data structure can be considered "traversable" as long as it has the Symbol.iterator property. T he Symbol.iterator property itself is a function, the default traverser generator for the current data structure. B y executing this function, a traverser is returned. As for the property name Symbol.iterator, it is an expression that returns the Iterator property of the Symbol object, which is a predefined special value of symbol, so put it in square brackets (see chapter Symbol).

  1. const obj = {
  2. [Symbol.iterator] : function () {
  3. return {
  4. next: function () {
  5. return {
  6. value: 1,
  7. done: true
  8. };
  9. }
  10. };
  11. }
  12. };

In the above code, the object obj is traversable because it has the Symbol.iterator property. E xecuting this property returns a traverser object. T he fundamental characteristic of the object is having the next method. Each time the next method is called, an information object representing the current member is returned, with value and done properties.

Some of ES6's data structures are native to the Iterator interface (such as arrays), i.e. they can be for... without any processing. O f loop traversal. T he reason is that the Symbol.iterator property is natively deployed for these data structures (see below), and other data structures are not (such as objects). A ny data structure that deploys the Symbol.iterator property is called a traverser interface. Calling this interface returns a traverser object.

The data structure that natively has an Iterator interface is as follows.

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • The arguments object of the function
  • NodeList object

The following example is the Symbol.iterator property of the array.

  1. let arr = ['a', 'b', 'c'];
  2. let iter = arr[Symbol.iterator]();
  3. iter.next() // { value: 'a', done: false }
  4. iter.next() // { value: 'b', done: false }
  5. iter.next() // { value: 'c', done: false }
  6. iter.next() // { value: undefined, done: true }

In the code above, the variable arr is an array that natively has a traverser interface and is deployed above the Symbol.iterator property of arr. So, by calling this property, you get the traverser object.

For the data structure of the native deployment Iterator interface, you don't have to write your own traverser generator, for... O f loops automatically traverse them. I n addition, the Iterator interface of other data structures (primarily objects) needs to be deployed on top of the Symbol.iterator property in order to be for... Of loop traversal.

Object does not deploy the Iterator interface by default because it is uncertain which property of the object traverses first and which traversal later, and needs to be manually specified by the developer. I n essence, the traverser is a linear process, and for any nonlinear data structure, deploying the traverser interface is equivalent to deploying a linear transformation. Strictly speaking, however, it is not necessary for an object to deploy a traverser interface because the object is actually used as a Map structure, ES5 does not have a Map structure, and ES6 is natively provided.

If an object is to have can be for... The Iterator interface called by the of-loop must deploy the traverser generation method on symbol.iterator's properties (objects on the prototype chain have this method as well).

  1. class RangeIterator {
  2. constructor(start, stop) {
  3. this.value = start;
  4. this.stop = stop;
  5. }
  6. [Symbol.iterator]() { return this; }
  7. next() {
  8. var value = this.value;
  9. if (value < this.stop) {
  10. this.value++;
  11. return {done: false, value: value};
  12. }
  13. return {done: true, value: undefined};
  14. }
  15. }
  16. function range(start, stop) {
  17. return new RangeIterator(start, stop);
  18. }
  19. for (var value of range(0, 3)) {
  20. console.log(value); // 0, 1, 2
  21. }

The code above is a way for a class to deploy the Iterator interface. The Symbol.iterator property corresponds to a function that returns the traverser object of the current object after execution.

The following is an example of implementing a pointer structure through a traverser.

  1. function Obj(value) {
  2. this.value = value;
  3. this.next = null;
  4. }
  5. Obj.prototype[Symbol.iterator] = function() {
  6. var iterator = { next: next };
  7. var current = this;
  8. function next() {
  9. if (current) {
  10. var value = current.value;
  11. current = current.next;
  12. return { done: false, value: value };
  13. } else {
  14. return { done: true };
  15. }
  16. }
  17. return iterator;
  18. }
  19. var one = new Obj(1);
  20. var two = new Obj(2);
  21. var three = new Obj(3);
  22. one.next = two;
  23. two.next = three;
  24. for (var i of one){
  25. console.log(i); // 1, 2, 3
  26. }

The above code first deploys the Symbol.iterator method on the prototype chain of the constructor, which returns the traverser object iterator, calls the object's next method, and automatically moves the internal pointer to the next instance while returning a value.

Here's another example of adding an Iterator interface to an object.

  1. let obj = {
  2. data: [ 'hello', 'world' ],
  3. [Symbol.iterator]() {
  4. const self = this;
  5. let index = 0;
  6. return {
  7. next() {
  8. if (index < self.data.length) {
  9. return {
  10. value: self.data[index++],
  11. done: false
  12. };
  13. } else {
  14. return { value: undefined, done: true };
  15. }
  16. }
  17. };
  18. }
  19. };

For array-like objects with numeric key names and length properties, an easy way to deploy an Iterator interface is for the Symbol.iterator method to refer directly to the Array's Iterator interface.

  1. NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
  2. // 或者
  3. NodeList.prototype[Symbol.iterator] = [][Symbol.iterator];
  4. [...document.querySelectorAll('div')] // 可以执行了

The NodeList object is an array-like object that already has a traversal interface that can be traversed directly. In the code above, we changed its traversal interface to the Symbol.iterator property of the array, and we could see that there was no effect.

The following is another example of an array-like object calling an array of Symbol.iterator methods.

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

Note that the Symbol.iterator method for deploying arrays of ordinary objects has no effect.

  1. let iterable = {
  2. a: 'a',
  3. b: 'b',
  4. c: 'c',
  5. length: 3,
  6. [Symbol.iterator]: Array.prototype[Symbol.iterator]
  7. };
  8. for (let item of iterable) {
  9. console.log(item); // undefined, undefined, undefined
  10. }

If the Symbol.iterator method does not correspond to a traverser generator (that is, a traverser object is returned), the interpretation engine will report an error.

  1. var obj = {};
  2. obj[Symbol.iterator] = () => 1;
  3. [...obj] // TypeError: [] is not a function

In the above code, the Symbol.iterator method of the variable obj does not correspond to the traverser generator, so an error is reported.

With the traverser interface, the data structure can be used for... Of loop traversal (see below), you can also use while loop traversal.

  1. var $iterator = ITERABLE[Symbol.iterator]();
  2. var $result = $iterator.next();
  3. while (!$result.done) {
  4. var x = $result.value;
  5. // ...
  6. $result = $iterator.next();
  7. }

In the code above, ITERABLE stands for some kind of traversable data structure, $iterator is its traverser object. Each time the traverser object moves the pointer (next method), it checks the done property that returns the value, and if the traversal is not over, moves the pointer to the next (next method) of the traverser object and loops continuously.

3. The occasion when the Iterator interface is called

There are occasions when the Iterator Symbol.iterator method) is called by default, except for... of loop, and there are several other occasions.

(1) Deconstruct the assignment

Symbol.iterator methods are called by default when deconstructing assignments to arrays and Set structures.

  1. let set = new Set().add('a').add('b').add('c');
  2. let [x,y] = set;
  3. // x='a'; y='b'
  4. let [first, ...rest] = set;
  5. // first='a'; rest=['b','c'];

(2) Extended operator

Extension operator (... The default Iterator interface is also called.

  1. // 例一
  2. var str = 'hello';
  3. [...str] // ['h','e','l','l','o']
  4. // 例二
  5. let arr = ['b', 'c'];
  6. ['a', ...arr, 'd']
  7. // ['a', 'b', 'c', 'd']

The Iterator interface is called inside the extension operator of the code above.

In fact, this provides an easy mechanism to convert any data structure that deploys the Iterator interface into an array. That is, as long as a data structure has an Iterator interface deployed, you can use an extension operator on it to convert it into an array.

  1. let arr = [...iterable];

(3)yield*

Yield is followed by a traversable structure that calls the traverser interface of the structure.

  1. let generator = function* () {
  2. yield 1;
  3. yield* [2,3,4];
  4. yield 5;
  5. };
  6. var iterator = generator();
  7. iterator.next() // { value: 1, done: false }
  8. iterator.next() // { value: 2, done: false }
  9. iterator.next() // { value: 3, done: false }
  10. iterator.next() // { value: 4, done: false }
  11. iterator.next() // { value: 5, done: false }
  12. iterator.next() // { value: undefined, done: true }

(4) Other occasions

Because the traversal of the array calls the traverser interface, any situation where the array is accepted as an argument is actually called the traverser interface. Here are some examples.

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

4. The String's Iterator interface

字符串 is an array-like object that also natively has an Iterator interface.

  1. var someString = "hi";
  2. typeof someString[Symbol.iterator]
  3. // "function"
  4. var iterator = someString[Symbol.iterator]();
  5. iterator.next() // { value: "h", done: false }
  6. iterator.next() // { value: "i", done: false }
  7. iterator.next() // { value: undefined, done: true }

In the above code, the Call Symbol.iterator method returns a traverser object on which the next method can be called to implement traversal of strings.

The native Symbol.iterator method can be overwritten for the purpose of modifying the traverser behavior.

  1. var str = new String("hi");
  2. [...str] // ["h", "i"]
  3. str[Symbol.iterator] = function() {
  4. return {
  5. next: function() {
  6. if (this._first) {
  7. this._first = false;
  8. return { value: "bye", done: false };
  9. } else {
  10. return { done: true };
  11. }
  12. },
  13. _first: true
  14. };
  15. };
  16. [...str] // ["bye"]
  17. str // "hi"

In the above code, the Symbol.iterator method of the string str has been modified, so the extension operator (... The returned value becomes bye, and the string itself is hi.

5. Iterator interface with Generator function

Symbol.iterator method is to use the Generator function to be covered in the next chapter.

  1. let myIterable = {
  2. [Symbol.iterator]: function* () {
  3. yield 1;
  4. yield 2;
  5. yield 3;
  6. }
  7. }
  8. [...myIterable] // [1, 2, 3]
  9. // 或者采用下面的简洁写法
  10. let obj = {
  11. * [Symbol.iterator]() {
  12. yield 'hello';
  13. yield 'world';
  14. }
  15. };
  16. for (let x of obj) {
  17. console.log(x);
  18. }
  19. // "hello"
  20. // "world"

In the above code, the Symbol.iterator method virtually does not deploy any code, as long as the yield command gives the return value for each step.

6. Return(), throw() of the traverser object

In addition to having the next method, next object can return method and the throw method. If you write the traverser object generator yourself, the next method must be deployed, and whether the return method and the throw method are deployed is optional.

The return method is used when, if for... T he return method is called when the loop exits early (usually because of an error, or because there is a break statement). If an object needs to clean up or free resources before it can complete the traversal, you can deploy the return method.

  1. function readLinesSync(file) {
  2. return {
  3. [Symbol.iterator]() {
  4. return {
  5. next() {
  6. return { done: false };
  7. },
  8. return() {
  9. file.close();
  10. return { done: true };
  11. }
  12. };
  13. },
  14. };
  15. }

In the above code, the function readLinesSync accepts a file object as an argument and returns a traverser object, where the return method is deployed in addition to the next method. In both cases, the return method is triggered.

  1. // 情况一
  2. for (let line of readLinesSync(fileName)) {
  3. console.log(line);
  4. break;
  5. }
  6. // 情况二
  7. for (let line of readLinesSync(fileName)) {
  8. console.log(line);
  9. throw new Error();
  10. }

In the above code, once the first line of the case outputs the file, the return method is executed, the file is closed, and case two throws an error after the return method closes the file.

Note that the return method must return an object, which is determined by the Generator specification.

The throw method is primarily used in conjunction with the Generator function, which is not used by general traverser objects. See the chapter "Generator Functions."

7. for... Of loop

ES6 for...of loops, as a unified way to traverse all data structures.

As long as a data structure is deployed Symbol.iterator it is considered to have an iterator interface, and can be used for... T he of loop traverses its members. T hat is, for... Inside the loop is the Symbol.iterator method of the data structure.

for... The ranges that the of loop can use include arrays, Set and Map structures, some array-like objects (such as arguments objects, DOM NodeList objects), Generator objects later, and strings.

Array

The array natively has an iterator (i.e. the Symbol.iterator property is deployed by default), for... The of loop is essentially a traverser generated by calling this interface, as evidenced by the following code.

  1. const arr = ['red', 'green', 'blue'];
  2. for(let v of arr) {
  3. console.log(v); // red green blue
  4. }
  5. const obj = {};
  6. obj[Symbol.iterator] = arr[Symbol.iterator].bind(arr);
  7. for(let v of obj) {
  8. console.log(v); // red green blue
  9. }

In the above code, the empty object obj deploys the Symbol.iterator property of the array arr, resulting in obj's for... Of loop, produces exactly the same result as arr.

for... The of loop can replace the forEach method for array instances.

  1. const arr = ['red', 'green', 'blue'];
  2. arr.forEach(function (element, index) {
  3. console.log(element); // red green blue
  4. console.log(index); // 0 1 2
  5. });

JavaScript's original for... I n loop, you can only get the key name of the object, you can't get the key value directly. E S6 offers for... of loop, allowing traversal to get key values.

  1. var arr = ['a', 'b', 'c', 'd'];
  2. for (let a in arr) {
  3. console.log(a); // 0 1 2 3
  4. }
  5. for (let a of arr) {
  6. console.log(a); // a b c d
  7. }

The above code indicates that for... i n loop read key name, for... T he of loop reads the key value. I f you want to pass for... The of loop, which gets the index of the array, can take advantage of the entries method and the keys method of the array instance (see chapter Extension of the Array).

for... T he of loop calls the traverser interface, which returns only properties with a numeric index. T his is the same as for... The in loop is different.

  1. let arr = [3, 5, 7];
  2. arr.foo = 'hello';
  3. for (let i in arr) {
  4. console.log(i); // "0", "1", "2", "foo"
  5. }
  6. for (let i of arr) {
  7. console.log(i); // "3", "5", "7"
  8. }

In the code above, for... The of loop does not return the foo property of array arr.

Set and Map structures

Set and Map structures are also native Iterator interface and can be used directly for... Of loop.

  1. var engines = new Set(["Gecko", "Trident", "Webkit", "Webkit"]);
  2. for (var e of engines) {
  3. console.log(e);
  4. }
  5. // Gecko
  6. // Trident
  7. // Webkit
  8. var es6 = new Map();
  9. es6.set("edition", 6);
  10. es6.set("committee", "TC39");
  11. es6.set("standard", "ECMA-262");
  12. for (var [name, value] of es6) {
  13. console.log(name + ": " + value);
  14. }
  15. // edition: 6
  16. // committee: TC39
  17. // standard: ECMA-262

The above code shows how to traverse the Set structure and the Map structure. T here are two notable places, first of all, the order in which each member is added to the data structure. Second, the Set structure traverses, returning a value, while the Map structure traversal returns an array of two members of the current Map member, the key name and the key value.

  1. let map = new Map().set('a', 1).set('b', 2);
  2. for (let pair of map) {
  3. console.log(pair);
  4. }
  5. // ['a', 1]
  6. // ['b', 2]
  7. for (let [key, value] of map) {
  8. console.log(key + ' : ' + value);
  9. }
  10. // a : 1
  11. // b : 2

Calculate the resulting data structure

Some data structures are calculated and generated on the basis of existing data structures. For example, arrays of ES6, Set, and Map all deploy the following three methods, all of which return traverser objects after being called.

  • Entries() returns a traverser object that is used to traverse an array of key names, key values. F or arrays, the key name is the index value, and for Set, the key name is the same as the key value. The Iterator interface of the Map structure, the default is to call the entries method.
  • Keys() returns a traverser object that traverses all key names.
  • values() returns a traverser object that traverses all key values.

The traverser objects generated after the three method calls are all traversed by computationally generated data structures.

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

An array-like object

Array-like objects include several categories. H ere's for... The of loop is used as an example of strings, DOM NodeList objects, arguments objects.

  1. // 字符串
  2. let str = "hello";
  3. for (let s of str) {
  4. console.log(s); // h e l l o
  5. }
  6. // DOM NodeList对象
  7. let paras = document.querySelectorAll("p");
  8. for (let p of paras) {
  9. p.classList.add("test");
  10. }
  11. // arguments对象
  12. function printArgs() {
  13. for (let x of arguments) {
  14. console.log(x);
  15. }
  16. }
  17. printArgs('a', 'b');
  18. // 'a'
  19. // 'b'

For strings, for... Another feature of the loop is that the 32-bit UTF-16 character is correctly recognized.

  1. for (let x of 'a\uD83D\uDC0A') {
  2. console.log(x);
  3. }
  4. // 'a'
  5. // '\uD83D\uDC0A'

Not all array-like objects have an Iterator interface, and an easy solution is to use the Array.from method to convert it into an array.

  1. let arrayLike = { length: 2, 0: 'a', 1: 'b' };
  2. // 报错
  3. for (let x of arrayLike) {
  4. console.log(x);
  5. }
  6. // 正确
  7. for (let x of Array.from(arrayLike)) {
  8. console.log(x);
  9. }

Object

For normal objects, for... T he of structure cannot be used directly, errors are reported, and the Iterator interface must be deployed before it can be used. H owever, in this case, for... The in loop can still be used to traverse key names.

  1. let es6 = {
  2. edition: 6,
  3. committee: "TC39",
  4. standard: "ECMA-262"
  5. };
  6. for (let e in es6) {
  7. console.log(e);
  8. }
  9. // edition
  10. // committee
  11. // standard
  12. for (let e of es6) {
  13. console.log(e);
  14. }
  15. // TypeError: es6[Symbol.iterator] is not a function

The above code indicates that for normal objects, for... T he in loop can traverse the key name, for... The of loop will report an error.

One solution is to use the Object.keys method to generate an array of key names for an object and then traverse the array.

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

Another approach is to repack the object using the Generator function.

  1. function* entries(obj) {
  2. for (let key of Object.keys(obj)) {
  3. yield [key, obj[key]];
  4. }
  5. }
  6. for (let [key, value] of entries(obj)) {
  7. console.log(key, '->', value);
  8. }
  9. // a -> 1
  10. // b -> 2
  11. // c -> 3

Comparison with other traversal syntax

In the case of arrays, JavaScript provides a variety of traversal syntaxes. The original writing is the for loop.

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

This writing is cumbersome, so arrays provide a built-in forEach method.

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

The problem with this writing is that neither the break command nor the return command works without jumping out of the forEach loop in the middle.

for... The in loop can traverse the key name of the array.

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

for... The in loop has several drawbacks.

  • The key name of the array is a number, but for... The in loop is a string with key names "0," "1," "2," and so on.
  • for... The in loop traverses not only the number key name, but also other keys added manually, even those on the prototype chain.
  • In some cases, for... The in loop traverses the key names in any order.

Anyway, for... The in loop is primarily designed to traverse objects and is not suitable for traversing arrays.

for... The of loop has some significant advantages over the above practices.

  1. for (let value of myArray) {
  2. console.log(value);
  3. }

  • With the same for... i n the same concise syntax, but no for... in Those shortcomings.
  • Unlike the forEach method, it works with break, continue, and return.
  • provides a unified operating interface for traversing all data structures.

Here's a break statement that jumps out of the for... An example of a of a loop.

  1. for (var n of fibonacci) {
  2. if (n > 1000)
  3. break;
  4. console.log(n);
  5. }

In the example above, items with a Fibonachi number column less than or equal to 1000 are output. I f the current item is greater than 1000, the break statement is used to jump out of for... Of loop.