One, closure 1. The concept of closures
Closures are closely related to the execution context, environment, scope
Execution context
The execution context is a canonical device used to track run-time code evaluation, and logically, the execution context is maintained with the execution context stack (stack, call stack).
There are several types of code: Global Code, function code, eval code, and module code, each of which is evaluated in the context of its execution.
When the function is called, a new execution context is created and pressed into the stack-at which point it becomes an active execution context. When the function returns, this context is ejected from the stack
function recursive (flag) { //Exit condition. if (flag = = = 2) { return; } Call recursively. Recursive (++flag);} Go.recursive (0);
The context in which another context is called is called the Caller (caller). The invoked context is appropriately referred to as the callee (callee), in which recursive is both the caller and the callee
The corresponding execution context stack
Typically, the code for a context runs until the end. However, in the generator of asynchronous processing, it is special.
A generator function may suspend its executing context and remove it from the stack before it is finished. Once the generator is activated again, its context is restored and pressed into the stack again
function *g () { yield 1; Yield 2; } var f = g (); F.next (); F.next ();
yield
The statement returns the value to the caller and pops the context. When you call next, the same context is pressed back into the stack, and the
Environment
There is an associated lexical environment for each execution context
You can define a lexical environment as a repository for variables, functions, and classes in scope, and each environment has a reference to an optional parent environment
For example, the global context in this code corresponds to the context of the Foo function.
Let x = 10;let y = 20; function foo (z) {let x = +; return x + y + z;} Foo (30); 150
Scope
When an execution context is created, it is associated with a specific scope (the code domain realm). This scope provides a global environment for the context (this "global" is not global in the general sense, but a means of providing context stack calls)
Static scope
If a language can determine the context in which the binding is resolved by looking only for source code, then the language implements a static scope. Therefore, the general can also be called the lyrics law scope.
The function is referenced in the environment, and the function is referenced by the environment. Static scopes are implemented by capturing the environment in which the function is created.
, the Global environment references the Foo function, and the Foo function also refers to the global environment
Free variables
A variable that is neither a formal parameter of a function nor a local variable of a function
function Testfn () { var localVar = ten; function Innerfn (innerparam) { alert (Innerparam + LocalVar); } return INNERFN;}
For the INNERFN function, LocalVar belongs to the free variable
Closed Package
A closure is a combination of code blocks and data in the context in which the code block is created, which is the environment in which the function captures the definition (closed environment).
In JS, the function is a class-one citizen (first-class), in general the code block is the meaning of the function (temporarily do not consider the special circumstances of ES6)
So, a closure is not just a function, it is an environment in which some relevant data and pointers are stored.
In theory, all functions are closures.
Because they are all created, the data in the upper context is saved. This is true even for simple global variables, because accessing a global variable in a function is equivalent to accessing a free variable, at which point the outermost scope is used.
From the point of view of implementation, not fully follow the theory, but also two points, according to one can be called closure
A free variable is referenced in the code
The context in which it was created has been destroyed, and it still exists (for example, an intrinsic function is returned from the parent function)
More relevant concepts to view this series
2. Characteristics of closures
- function nesting functions
- External parameters and variables can be referenced inside the function
- Parameters and variables are not recycled by the garbage collection mechanism
In general, closures have nested functions that refer to external parameters and variables (free variables) and persist after their context is destroyed (not recycled by the garbage collection mechanism)
3. Advantages of closures
- Keep a variable in memory for a long time
- Avoid contamination of global variables
- Existence as a private member
According to the characteristics, closures have the corresponding advantages
For example, to create a counter, as a general rule we can use the class
function Couter () { this.num = 0;} Couter.prototype = { Constructor:couter, //Increment up:function () { this.num++; }, //Minus down : function () { this.num--; }, //Get getnum:function () { console.log (this.num); }}; var C1 = new Couter (); C1.up (); C1.up (); C1.getnum (); 2var C2 = new Couter (); C2.down (); C2.down (); C2.getnum (); -2
This is good, we can also use the closure of the way to achieve
function Couter () { var num = 0; return { //increment up:function () { num++; }, //minus down:function () { num--; }, //Get getnum:function () { console.log (num);}} ;} var C1 = Couter (); C1.up (); C1.up (); C1.getnum (); 2var C2 = Couter (); C2.down (); C2.down (); C2.getnum (); -2
As you can see, num is still in memory while the context of the Couter function is destroyed
In many design patterns, closures act as important roles,
4. Disadvantages of closures
The downside of closures is more in terms of memory performance.
Due to the long-term presence of variables in memory, there may be insufficient memory in complex programs, but this is not very serious, we need to make a choice in memory usage and development methods. Clean out variables when you don't need them.
At some point (the object is referenced to the DOM, the GC uses reference counting) to cause a memory leak, remember to clean up the variable before exiting the function
Window.onload = function () { var elem = document.queryselector ('. txt '); Elem's onclick points to the anonymous function, and the closure of the anonymous function also refers to the elem Elem.onclick = function () { console.log (this.innerhtml); }; Clean elem = null;};
Memory leaks related things, there is not much to say, and then to organize an article
In addition, since the variables in the closure can be modified outside the function (through the exposed interface method), all inadvertently also internal variables will be modified, so also pay attention to
5. Application of closures
Closures have a wide range of usage scenarios
A common question is, what does this piece of code output
var func = [];for (var i = 0; i < 5; ++i) { Func[i] = function () { console.log (i); }} FUNC[3] (); 5
Since the scope of the relationship, the final output of 5
With a slight modification, you can use anonymous functions to execute and close the packet immediately to output the correct result
for (var i = 0; i < 5; ++i) { (function (i) { Func[i] = function () { console.log (i); } }) (i); } FUNC[3] (); 3for (var i = 0; i < 5; ++i) { (function () { var n = i; Func[i] = function () { console.log (n); } }) (); } FUNC[3] (); 3for (var i = 0; i < 5; ++i) { Func[i] = (function (i) { return function () { console.log (i); } }) (i);} FUNC[3] (); 3
Second, higher order function
High-order functions (High-order function Abbreviation: HOF), I sound so advanced, meet the following two points can be called higher-order functions
- function can be passed as a parameter
- function can be output as a return value
The definition in Wiki is
- Accept one or more functions as input
- Output a function
Higher-order functions can be understood as functions above functions , which are commonly used, such as common
var getData = function (URL, callback) { $.get (URL, function (data) { callback (data); });}
Or you can use it in many closed scenarios.
such as the anti-shake function (debounce) and throttling function (throttle)
Debounce
Anti-shake, which means that no matter how many times an action is triggered, it will not be executed until the continuous action is stopped.
For example, an input box accepts the user input, and the input is completed before the search begins.
With page scrolling as an example, you can define a shake-out function that accepts a custom delay value as the time identifier for the stop.
function stabilization, which is not processed in frequent operation until the operation is completed (again delay time) only once processing function debounce (FN, delay) { delay = Delay | | var timer = null; return function () { var arg = arguments; The last timer cleartimeout (timer) is cleared during each operation; timer = null; Define a new timer, operate after a period of time timer = setTimeout (function () { fn.apply (this, arg); }, delay);} }; var count = 0;window.onscroll = Debounce (function (e) { console.log (E.type, ++count);//scroll}, 500);
Scrolling the page, you can see that only after scrolling is done
Throttle
Throttling, which means that no matter how many times an action is triggered, it can only trigger one time within a defined period.
For example, resize and scroll time frequently triggered operations, if all accepted processing, may affect performance, need throttling control
With page scrolling as an example, you can define a throttling function that accepts a custom delay value as the time indicator for the stop.
Two points to be aware of
To set an initial identity, prevent it from being executed at the beginning of the process, and reset after the last processing
Also set timer processing, prevent two action not to delay value, the last set of actions can not trigger
function throttling, the time of the interval delay in the frequent operation is only processed once function throttle (FN, delay) { delay = Delay | | var timer = null; Each scrolling initial identification var timestamp = 0; return function () { var arg = arguments; var now = Date.now (); Set start time if (timestamp = = = 0) { timestamp = now; } Cleartimeout (timer); timer = null; has been to the delay for a period of time, to be processed if (now-timestamp >= delay) { fn.apply (this, arg); timestamp = now; } Add timers to ensure that the last operation can also handle else { timer = setTimeout (function () { fn.apply (this, arg); Recover identity timestamp = 0; }, delay);}} ; var count = 0;window.onscroll = Throttle (function (e) { console.log (E.type, ++count);//scroll}, 500);
Third, currying
The currying, also known as partial evaluation, is a function that transforms a function that accepts multiple parameters into one that accepts a single parameter (the first parameter of the initial function), and returns a new function, which takes the remaining parameters and returns the result of the operation.
A more classic example is
Implementation cumulative Add (1) (2) (3) (4)
The first approach is to use callback nesting
function Add (a) { //Mad Callback return function (b) { return function (c) { return function (d) { //return a + B + C + D; return [A, B, C, d].reduce (function (v1, v2) { return v1 + v2; }); } } } Console.log (Add (1) (2) (3) (4)); 10
Neither graceful nor well-extended
Modify two, let IT support variable number of parameters
function Add () { var args = [].slice.call (arguments); Used to store update parameter array function adder () { var arg = [].slice.call (arguments); args = Args.concat (arg); Each call, return itself, the value can be obtained by the internal ToString value return adder; } Specifies the value of toString for the implicit value calculation adder.tostring = function () { return args.reduce (function (v1, v2) { return v1 + V 2; }); return adder;} Console.log (Add (1, 2), add (1, 2) (3), add (1) (2) (3) (4)); 3 6 10
The above code will enable this "curry"
Two points to be aware of are
- Arguments is not a real array, so you cannot use an array's native method (such as slice)
- When the value is taken, the implicit evaluation is performed first by the internal ToString (), then by ValueOf (), the valueOf priority is higher, we can override the initial method
Of course, not all types of ToString and Tovalue are the same, number, String, Date, Function types are not exactly the same, this article does not expand
It uses the call method, which is mainly to change the context of execution, similar to Apply,bind, etc.
We can try to customize the bind method of a function, such as
var obj = { num:10, getnum:function (num) { console.log (num | | this.num);} }; var o = { num:20};obj.getnum ();//10obj.getnum.call (o, n);//1000obj.getnum.bind (O) (20);//20//custom bind bind fun Ction.prototype.binder = function (context) { var fn = this; var args = [].slice.call (arguments, 1); return function () { return fn.apply (context, args); };};o Bj.getNum.binder (o, 100) (); 100
The above currying is not perfect, if you want to define a multiplication function, you have to write again long code
A generic currying function needs to be defined as a wrapper
currying function Curry (FN) { var args = [].slice.call (arguments, 1); function inner () { var arg = [].slice.call (arguments); args = Args.concat (arg); return inner; } inner.tostring = function () { return fn.apply (this, args); }; return inner;} function Add () { return [].slice.call (arguments). reduce (function (v1, v2) { return v1 + v2; });} function Mul () { return [].slice.call (arguments). reduce (function (v1, v2) { return v1 * v2; });} var curryadd = Curry (add), Console.log (Curryadd (1) (2) (3) (4) (5)); 15var Currymul = Curry (mul, 1); Console.log (Currymul (2, 3) (4) (5)); 120
It looks a lot better, it's easy to expand
In practice, however, in the application of curry, the number of parameter scenarios is less and the parameters are fixed in more cases (common two or three)
currying function Curry (FN) { var args = [].slice.call (arguments, 1), //functions fn parameter length fnlen = fn.length; Stores the parameter array until the parameter is sufficient, calling function inner () { var arg = [].slice.call (arguments); args = Args.concat (arg); if (args.length >= fnlen) { return fn.apply (this, args); } else { return inner; } } return inner;} function Add (A, B, C, D) { return a + B + C + D;} function Mul (A, B, C, D) { return a * b * c * D;} var curryadd = Curry (add), Console.log (Curryadd (1) (2) (3) (4)); 10var Currymul = Curry (mul, 1); Console.log (Currymul (2, 3) (4)); 24
In the Add method defined above, 4 parameters are accepted
In our currying function, accept the Add method and remember how many parameters this method needs to accept, store the incoming parameters, and call processing when the quantity requirement is met.
Anti-currying
Anti-Curry, which reverses the function of the curry after it is converted from several calls that accept a single parameter to a single call that takes multiple arguments
One simple way to do this is to pass multiple parameters to the function of the curry one at a time, because our curry function natively supports the incoming processing of multiple parameters, and only "one call" is used when the anti-Curry call is invoked.
Combining the above curry code, the anti-curry code is as follows
Anti-currying function Uncurry (FN) { var args = [].slice.call (arguments, 1); return function () { var arg = [].slice.call (arguments); args = Args.concat (arg); Return fn.apply (this, args); }} var uncurryadd = Uncurry (Curryadd); Console.log (Uncurryadd (1, 2, 3, 4)); 10var Uncurrymul = Uncurry (Currymul, 2); Console.log (Uncurrymul (3, 4)); 24
Resources:
Javascript. The core:2nd Edition
JavaScript: Core-second edition (translated)
Ecma-262-3 in detail. Chapter 6. Closures.
Understanding the closure, higher order function and currying using JS