Iterator
Iterator concept
Iterator provides a unified interface mechanism to provide a unified access mechanism for various data structures. Defining an Iterator is to provide an object with a next() method. Each call to next() will return a result object. The result object has two properties, value indicates the current value, and done indicates whether the traversal is over.
function makeIterator(Array){
let index = 0;
return {
next: function(){
return (
Array.length > index ?
{value: Array[index++]}:
{done: true}
)
}
}
}
let iterator = makeIterator(['1','2'])
console.log(iterator.next()); // {value: '1'}
console.log(iterator.next()); // {value: '2'}
console.log(iterator.next()); // {done: true}
The role of Iterator:
- Provide a unified and convenient access interface for various data structures;
- Enables the members of the data structure to be arranged in a certain order;
- for...of consumption
Default Iterator interface
ES6 provides the for of statement to traverse the iterator object. Let's use the for of statement to traverse the iterator created above:
let iterator = makeIterator(['1','2'])
for (let value of iterator) {
console.log(value);
} // iterator is not iterable
The result is an error saying iterator is not iterable, why is this?
ES6 stipulates that the default Iterator interface is deployed in the Symbol.iterator property of the data structure. If a data structure has the Symbol.iterator property, the data structure can be traversed. We transform our custom makeIterator as follows:
const MakeIterator = (Array) => ({
[Symbol.iterator](){
let index = 0;
return {
next(){
let length = Array.length;
if(index < length){
return {value: Array[index++]}
}else{
return {done: true}
}
}
}
}
})
for(let value of MakeIterator([1,2])){
console.log(value)
}
// 1
// 2
return() of Iterator
We add a return method to MakeIterator, and if the for...of loop exits early (usually because of an error, or a break statement), the return() method is called to terminate the traversal. Based on this feature, if an object needs to clean up or release resources before completing the traversal, we can deploy the return() method to close the file when the file reading fails.
const MakeIterator = (Array) => ({
[Symbol.iterator](){
let index = 0;
return {
next(){
let length = Array.length;
if(index < length){
return {value: Array[index++]}
}else{
return {done: true}
}
},
return(){
return {done: true}
}
}
}
})
for(let value of MakeIterator([1, 2, 3])){
console.log(value) // 1
// 方式1
break;
// 方式2
// throw new Error('error');
}
Native data structure with Iterator interface
array
Set
Map
Array-like objects, such as arguments object, DOM NodeList object, typedArray object
// arguments 对象
function sum(){
for(let value of arguments){
console.log(value)
}
}
sum(1,2)
// 1
// 2
// typedArray 对象
let typeArry = new Int8Array(2);
typeArry[0] = 1;
typeArry[1] = 2;
for(let value of typeArry){
console.log(value)
}
// 1
// 2
Generator object
function* gen(){ yield 1; yield 2; } for(let value of gen()){ console.log(value) }
- String
Q: Why doesn't Object have a native Iterator?
A: The reason why the object (Object) does not deploy the Iterator interface by default is because it is uncertain which property of the object is traversed first and which property is traversed later. Essentially, a traverser is a linear process. For any nonlinear data structure, deploying a traverser interface is equivalent to deploying a linear transformation. However, strictly speaking, the object deployment traverser interface is not very necessary, because then the object is actually used as a Map structure, ES5 does not have a Map structure, and ES6 provides it natively.
When calling the Iterator interface
destructuring assignment
let set = new Set().add('a').add('b').add('c'); let [x,y] = set; // x='a'; y='b'
spread operator
var str = 'hello'; [...str] // ['h','e','l','l','o']
The extension operator is to call the Iterator interface, so Object does not deploy the Iterator interface, so why can the ... operator be used?
Reason: There are two types of spread operators
- One is used in the case of function parameters and array expansion, which requires the object to be iterable (iterable)
The other is for object expansion, that is, in the form of {…obj}, in which case the object is required to be enumerable (enumerable)
let obj1 = { name: 'qianxun' } let obj2 = { age: 3 } // 数组对象是可枚举的 let obj = {...obj1, ...obj2} console.log(obj) //{name: 'qianxun', age: 3} // 普通对象默认是不可迭代的 let obj = [...obj1, ...obj2] console.log(obj) // object is not iterable
Mock implementation for of
function forOf(obj, cb){ let iteratorValue = obj[Symbol.iterator](); let result = iteratorValue.next() while(!result.done){ cb(result.value) result = iteratorValue.next() } } forOf([1,2,3], (value)=>{ console.log(value) }) // 1 // 2 // 3
Generator
Meet the Generator
// 概念上 Generator 函数是 ES6 提供的一种异步编程解决方案。Generator 函数是一个状态机,封装了多个内部状 态;Generator 函数还是一个遍历器对象生成函数,执行后返回一个遍历器对象。 // 形式上 1.function 关键字与函数名之间有一个星号; 2.函数体内部使用 yield 表达式,定义不同的内部状态。
function* simpleGenerator(){ yield 1; yield 2; } simpleGenerator()
As above, we created a simple Generator, and we explored it with two questions:
- What happens after the Generator function runs?
What does the yield expression in a function do?
function* simpleGenerator(){ console.log('hello world'); yield 1; yield 2; } let generator = simpleGenerator(); // simpleGenerator {<suspended}} console.log(generator.next()) // hello world // {value: 1, done: false} console.log(generator.next()) // {value: 2, done: false}
Generator The generator function returns a generator object after running, and the ordinary function will directly execute the code inside the function; each time the next method of the generator object is called, the function will be executed until the next yield keyword stops execution, and returns a {value: Value, done: Boolean} object.
Parameters of the next method
The yield expression itself does not return a value, or always returns undefined. The next method can take one parameter, which will be used as the return value of the previous yield expression. Through the parameters of the next method, different values can be injected from the outside to the inside at different stages of the Generator function's operation, thereby adjusting the function's behavior.
Since the parameter of the next method represents the return value of the previous yield expression, it is invalid to pass the parameter when the next method is used for the first time.function sum(x){ return function(y){ return x + y; } } console.log(sum(1)(2)) // 利用next传参改写 function* sum(x){ let y = yield x; while(true){ y = yield x + y; } } let gen = sum(2) console.log(gen.next()) // 2 console.log(gen.next(1)) // 3 console.log(gen.next(2)) // 4
yield expression
The role of the yield expression: define internal state and pause execution
Difference between yield expression and return statement
- The yield expression indicates that the function suspends execution and continues to execute backwards from this position next time, and the return statement does not have the function of position memory
- In a function, only one return statement can be executed, but multiple yield expressions can be executed
- Any function can use the return statement, and the yield expression can only be used in the Generator function, and an error will be reported in other places
If the yield expression participates in the operation, it is placed in parentheses; when it is used as a function parameter or placed on the right side of an assignment expression, parentheses can be omitted.
function *gen () { console.log('hello' + yield) × console.log('hello' + (yield)) √ console.log('hello' + yield 1) × console.log('hello' + (yield 1)) √ foo(yield 1) √ const param = yield 2 √ }
Based on the generator function that supports multiple yields, we can implement a scenario where a function has multiple return values:
function* gen(num1, num2){ yield num1 + num2; yield num1 - num2; } let res = gen(2, 1); console.log(res.next()) // {value: 3, done: false} console.log(res.next()) // {value: 1, done: false}
The relationship between Generator and Iterator
Since the Generator function is the traverser generating function, the Generator can be assigned to the Symbol.iterator property of the object, so that the object has the Iterator interface. Generator implementation code is more concise.
let obj = { name: 'qianxun', age: 3, [Symbol.iterator]: function(){ let that = this; let keys = Object.keys(that) let index = 0; return { next: function(){ return index < keys.length ? {value: that[keys[index++]], done: false}: {value: undefined, done: true} } } } } for(let value of obj){ console.log(value) }
Generator:
let obj = { name: 'qianxun', age: 3, [Symbol.iterator]: function* (){ let keys = Object.keys(this) for(let i=0; i< keys.length; i++){ yield this[keys[i]]; } } } for(let value of obj){ console.log(value) }
Generator.prototype.return()
return()
method, which can return the given value and terminate the traversal of the Generator function.
function* gen() {
yield 1;
yield 2;
yield 3;
}
var g = gen();
g.next() // { value: 1, done: false }
// 如果 return() 方法调用时,不提供参数,则返回值的 value 属性为 undefined
g.return('foo') // { value: "foo", done: true }
g.next() // { value: undefined, done: true }
If there is a try...finally
code block inside the Generator function, and the ---87ce55e09e25e9dbf3b599ef953e4daa try
code block is being executed, then the return()
method will cause immediate entry into the finally
code block ---cb5a0d53301dd9 , after the execution, the whole function will end.
function* numbers () {
yield 1;
try {
yield 2;
yield 3;
} finally {
yield 4;
yield 5;
}
yield 6;
}
var g = numbers();
g.next() // { value: 1, done: false }
g.next() // { value: 2, done: false }
g.return(7) // { value: 4, done: false }
g.next() // { value: 5, done: false }
g.next() // { value: 7, done: true }
yield* expressions
If you want to be inside a Generator function, call another Generator function. We need to manually complete the traversal inside the function body of the former. If the function calls are nested in multiple layers, the writing method will be cumbersome and difficult to read. ES6 provides the yield* expression as a solution.
Delegate to other generators
function* g1() {
yield 2;
yield 3;
}
function* g2() {
yield 1;
yield* g1();
yield 4;
}
const iterator = g2();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: 4, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
Delegate to other iterables
function* gen(){
yield* [1,2,3]
}
console.log(gen().next()) // {value: 1, done: false}
the this of the Generator function
The Generator function returns a traverser. ES6 specifies that this traverser is an instance of the Generator function, inheriting the methods on the Generator.prototype object, but cannot get the properties on this, because this is the global object, not the instance object.
function* gen(){
this.a = 1
}
gen.prototype.say = function(){
console.log('hi')
}
let obj = gen()
console.log(obj instanceof gen) // true
obj.say() // hi
obj.next()
console.log(obj.a) //undefined
If you want to access instance properties like a constructor, you can modify this to bind to Generator.prototype.
function* gen(){
this.a = 1
}
gen.prototype.say = function(){
console.log('hi')
}
let obj = gen.call(gen.prototype)
console.log(obj instanceof gen) // true
obj.say() // hi
obj.next()
console.log(obj.a) //1
Generator implements a state machine
function* StateMachine(state){
let transition;
while(true){
if(transition === "INCREMENT"){
state++;
}else if(transition === "DECREMENT"){
state--;
}
transition = yield state;
}
}
const iterator = StateMachine(0);
console.log(iterator.next()); // 0
console.log(iterator.next('INCREMENT')); // 1
console.log(iterator.next('DECREMENT')); // 0
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。