Article directory
-
- 1 Generator function
-
- 1.1 Overview
- 1.2 Execution mechanism
- 1.3 yield expression
-
- 1.3.1 Overview
- 1.3.2 Similarities and differences between `yield` expression and `return` statement
- 1.4 next() method parameters
1 Generator function
1.1 Overview
ES6 newly introduces the Generator function, which can suspend the execution flow of the function through the yield keyword, providing the possibility to change the execution flow, thus providing a solution for asynchronous programming.
Collection of native JavaScipt cases
JavaScript + DOM basics
JavaScript basic to advanced
Canvas game development
Formally, the Generator function is an ordinary function, but it has two characteristics.
- There is an asterisk between the function keyword and the function name
- Yield expressions are used inside the function body to define different internal states (yield` means “output” in English).
function* func(){ console.log("one"); yield '1'; console.log("two"); yield '2'; console.log("three"); return '3'; }
Executing the Generator function will return a traverser object. That is to say, the Generator function is not only a state machine, but also a traverser object generation function. The returned traverser object can traverse each state inside the Generator function in sequence.
There are two yield
expressions (1 and 2) inside the above Generator function, that is, the function has three states: 1, 2 and return statement (end execution).
1.2 Execution mechanism
Calling the Generator function is the same as calling an ordinary function. Just add () after the function name. However, the Generator function will not be executed immediately like an ordinary function. Instead, it will return a pointer to the internal state object, so the iterator object Iterator must be called. Next method, the pointer will start execution from the head of the function or where it stopped last time.
f.next(); // one // {value: "1", done: false} f.next(); // two // {value: "2", done: false} f.next(); // three // {value: "3", done: true} f.next(); // {value: undefined, done: true}
When the next method is called for the first time, execution starts from the head of the Generator function. First, one is printed, and execution stops when yield is reached, and the value ‘1’ of the expression after yield is used as the value attribute value of the returned object. This is When the function has not finished executing, the value of the done attribute of the returned object is false.
When calling next method for the second time, the same as above.
When the next method is called for the third time, three is printed first, and then the return operation of the function is performed, and the value of the expression after return is used as the value attribute value of the returned object. At this time, the function has ended, and the done attribute value is usually used. is true.
When the next method is called for the fourth time, the function has finished executing, so the returned value attribute value is undefined and the done attribute value is true. If there is no return statement when executing the third step, {value: undefined, done: true} will be returned directly.
ES6 does not specify where the asterisk between the function
keyword and the function name should be written. This causes the following writing methods to pass.
function * foo(x, y) { ··· } function *foo(x, y) { ··· } function* foo(x, y) { ··· } function*foo(x, y) { ··· }
Since the Generator function is still an ordinary function, the general writing method is the third way above, that is, the asterisk follows the function
keyword.
1.3 yield expression
1.3.1 Overview
Since the traverser object returned by the Generator function will traverse the next internal state only by calling the next
method, it actually provides a function that can suspend execution. The yield
expression is the pause flag.
The operation logic of the next
method of the traverser object is as follows:
(1) When encountering a yield
expression, the execution of subsequent operations will be suspended, and the value of the expression immediately following yield
will be used as the value of the returned object. code>valueAttribute value.
(2) The next time the next
method is called, execution will continue until the next yield
expression is encountered.
(3) If no new yield
expression is encountered, it will run until the end of the function until the return
statement, and return
The value of the expression following the statement is used as the value of the value
attribute of the returned object.
(4) If the function does not have a return
statement, the value of the value
attribute of the returned object is undefined
.
It should be noted that the expression following the yield
expression will only be executed when the next
method is called and the internal pointer points to the statement. Therefore, it is equivalent to providing a manual method for JavaScript. The syntactic function of “Lazy Evaluation”.
function* gen() { yield 123 + 456; }
In the above code, the expression 123 + 456
after yield
will not be evaluated immediately. The pointer will only be moved here in the next
method. The value will not be evaluated until a sentence is passed.
1.3.2 Similarities and differences between yield
expression and return
statement
The yield
expression and the return
statement have both similarities and differences. The similarity is that both return the value of the expression immediately following the statement. The difference is that every time yield
is encountered, the function pauses execution and continues backward execution from that position next time, while the return
statement does not have the function of position memory. In a function, you can only execute one (or one) return
statement, but you can execute multiple (or multiple) yield
expressions. Normal functions can only return one value, because return
can only be executed once; Generator functions can return a series of values, because there can be any number of yield
. From another perspective, it can also be said that Generator generates a series of values, which is where its name comes from (in English, the word generator means “generator”).
The Generator function does not need a yield
expression, and then it becomes a simple suspended execution function.
function* f() { console.log('Executed!') } var generator = f(); setTimeout(function () { generator.next() }, 2000);
In the above code, if the function
f
is an ordinary function, it will be executed when assigning a value to the variablegenerator
. However, functionf
is a Generator function, so functionf
will only be executed when thenext
method is called.
1.4 next() method parameters
The yield
expression itself has no return value, or it always returns undefined
. The next
method can take one parameter, which will be treated as the return value of the previous yield
expression.
function* f() { for(var i = 0; true; i + + ) { var reset = yield i; if(reset) { i = -1; } } } var g = f(); g.next() // { value: 0, done: false } g.next() // { value: 1, done: false } g.next(true) // { value: 0, done: false }
The above code first defines a Generator function
f
that can run infinitely. If thenext
method has no parameters, it will run to theyield
expression and variable each time. The value ofreset
is alwaysundefined
. When thenext
method takes a parametertrue
, the variablereset
is reset to this parameter (i.e.true
) , soi
will be equal to-1
, and the next cycle will increase from-1
.
This function has very important grammatical significance. When the Generator function resumes operation from the paused state, its context remains unchanged. Through the parameters of the next
method, there is a way to continue injecting values into the function body after the Generator function starts running. That is to say, different values can be injected from the outside into the inside of the Generator function at different stages to adjust the function behavior.
function* foo(x) { var y = 2 * (yield (x + 1)); var z = yield (y / 3); return (x + y + z); } var a = foo(5); a.next() // Object{value:6, done:false} a.next() // Object{value:NaN, done:false} a.next() // Object{value:NaN, done:true} var b = foo(5); b.next() // { value:6, done:false } b.next(12) // { value:8, done:false } b.next(13) // { value:42, done:true }
In the above code, the next
method is run for the second time without parameters, causing the value of y to be equal to 2 * undefined
(i.e. NaN
). After dividing by 3, it is still NaN
, so the value
attribute of the returned object is also equal to NaN
. The Next
method is run for the third time without parameters, so z
is equal to undefined
, and the value
attribute of the object is returned. Equal to 5 + NaN + undefined
, that is, NaN
.
If you provide parameters to the next
method, the returned results are completely different. When the above code calls the next
method of b
for the first time, it returns the value 6
of x + 1
; the second time Call the next
method this time to set the value of the last yield
expression to 12
, so y
is equal to 24
, returns the value 8
of y / 3
; the third time the next
method is called, the last yield The value of the
expression is set to 13
, so z
is equal to 13
, then x
is equal to 5
, y
is equal to 24
, so the value of the return
statement is equal to 42
.
Note that since the parameters of the
next
method represent the return value of the previousyield
expression, the parameters are passed when thenext
method is used for the first time. it is invalid. The V8 engine directly ignores the parameters when thenext
method is used for the first time. The parameters are only valid from the second time thenext
method is used. Semantically speaking, the firstnext
method is used to start the traverser object, so it does not require parameters.