JavaScript functional programming has been a topic for a long time, but it seems that it has become increasingly popular since 2016. This may be because the ES6 syntax is more user-friendly for functional programming, or because, for example, RxJS (Re...
JavaScript functional programming has been a topic for a long time, but it seems that it has become increasingly popular since 2016. This may be because ES6 syntax is more user-friendly for functional programming, or because of the popularity of functional frameworks such as RxJS (ReactiveX.
I have read a lot of functional programming explanations, but most of them stay at the theoretical level, and some of them are only for pure functional programming languages such as Haskell. The purpose of this article is to talk about the specific practices of functional programming in JavaScript in my eyes. The reason is that "in my eyes" is that what I say only represents my opinion, it may conflict with some strict concepts.
This article will describe a lot of formal concepts, it focuses on what functional code is in JavaScript, what is the difference between functional code and general writing, what benefits does functional Code bring to us, and some common functional models. which of the following are supported.
Functional Programming I understand
I think functional programming can be understood,Programming with functions as the main carrier, Using functions to disassemble and abstract general expressions
What are the benefits of doing so compared with imperative statements? There are mainly the following points:
Basic functional programming
The following example shows a specific function expression.
// Each word in the array has an upper-case letter. // The general syntax is const arr = ['apple', 'pen ', 'apple-pen']; for (const I in arr) {const c = arr [I] [0]; arr [I] = c. toUpperCase () + arr [I]. slice (1);} console. log (arr); // write function upperFirst (word) {return word [0]. toUpperCase () + word. slice (1);} function wordToUpperCase (arr) {return arr. map (upperFirst);} console. log (wordToUpperCase (['apple', 'pen ', 'apple-pen']); // function method 2 console. log (arr. map (['apple', 'pen ', 'apple-pen'], word => word [0]. toUpperCase () + word. slice (1 )));
When the situation becomes more complex, the expression writing method may encounter several problems:
The definition is not obvious and gradually becomes difficult to maintain
Poor reusability leads to more code
Many intermediate variables are generated.
Function programming solves the above problems. SeeFunction Method 1It uses function encapsulation to disassemble functions (the granularity is not unique) and encapsulate them as different functions, and then use combined calls for the purpose. This makes the definition clear, easy to maintain, reuse, and expand. Second, useHigh-order functions, Array. map replaces... Of performs array traversal to reduce intermediate variables and operations.
WhileFunction Method 1AndFunction Method 2The main difference between them is that you can consider whether the function may be reused in the future. If not, the latter is better.
Chain Optimization
From aboveFunction Method 2We can see that functional code can easily causeHorizontal scalingThat is, multi-layer Nesting is generated. Here is an example of extreme points.
// Calculate the sum of numbers. // the general method is console. log (1 + 2 + 3-4) // function sum (a, B) {return a + B;} function sub (a, B) {return a-B;} console. log (sub (sum (1, 2), 3), 4 );
This example is for demonstration only.Horizontal scalingAs the number of nested layers of the function increases, the readability of the Code is greatly reduced and errors are easily generated.
In this case, we can consider multiple optimization methods, such as the followingChain Optimization.
// Optimize the writing method (well, you are not mistaken, this is the lodash chain Writing Method) const utils = {chain (a) {this. _ temp = a; return this;}, sum (B) {this. _ temp + = B; return this ;}, sub (B) {this. _ temp-= B; return this ;}, value () {const _ temp = this. _ temp; this. _ temp = undefined; return _ temp ;}}; console. log (utils. chain (1 ). sum (2 ). sum (3 ). sub (4 ). value ());
In this way, the structure will become clearer as a whole, and each part of the chain can be easily displayed. Another good example is the comparison of function nesting and chain.Callback FunctionAndPromise Mode.
// Request two interfaces in sequence // The callback function import $ from 'jquery '; $. post ('A/url/to/target', (rs) =>{ if (rs) {$. post ('A/url/to/another/target', (rs2) =>{ if (rs2) {$. post ('A/url/to/third/target') ;}}); // Promiseimport request from 'catta '; // catta is a lightweight request tool that supports fetch, jsonp, and ajax and does not depend on request ('A/url/to/target '). then (rs => rs? $. Post ('A/url/to/another/target'): Promise. reject (). then (rs2 => rs2? $. Post ('A/url/to/third/target'): Promise. reject ());
As the nested hierarchy and single-layer complexity of the callback function increase, it will become bloated and difficult to maintain, and the chain structure of Promise can still be scaled vertically in high complexity, and the hierarchy isolation is clear.
Common functional programming model Closure (Closure)
A block of code that can retain local variables from being released. It is called a closure.
The concept of closure is abstract. I believe everyone knows and uses this feature more or less.
So what are the benefits of closures?
First, let's take a look at how to create a closure:
// Create a closure function makeCounter () {let k = 0; return function () {return ++ k ;};} const counter = makeCounter (); console. log (counter (); // 1console. log (counter (); // 2
The code block of the makeCounter function references the local variable k in the returned function. As a result, the local variable cannot be recycled by the system after the function is executed, resulting in a closure. The function of this closure is to "retain" The local variable so that the variable can be used repeatedly when the inner function is called. Unlike global variables, this variable can only be referenced within the function.
In other words, closures actually create some private "persistence variables" for functions.
From this example, we can conclude that the condition for creating a closure is:
Internal and external functions exist
The internal function references the local variables of the external function.
Use of closures
Closure is mainly used to define persistence variables with limited scopes. These variables can be used for caching or computing.
// Simple cache tool // an anonymous function creates a closure const cache = (function () {const store ={}; return {get (key) {return store [key] ;}, set (key, val) {store [key] = val ;}}} (); cache. set ('A', 1); cache. get ('A'); // 1
The preceding example shows the implementation of a simple cache tool. An anonymous function creates a closure so that the store object can be referenced and not recycled.
Disadvantages of closures
Persistent variables will not be released normally and will continue to occupy the memory space, which can easily lead to memory waste. Therefore, some additional manual cleaning mechanisms are generally required.
High-order functions
A function that accepts or returns a function is called a high-order function.
It sounds very cool, but we often use it, but we didn't know their names. The JavaScript language is native and supports higher-order functions. Because JavaScript Functions are first-class citizens, they can be used as parameters and return values of another function.
We often see many native high-level functions in JavaScript, such as Array. map, Array. reduce, Array. filter.
The following uses map as an example to illustrate how it is used.
Map)
Ing is for the set, that is, to make the same Transformation for each item of the set and generate a new set
As a high-level function, map accepts a function parameter as the logic of ing.
// Add one to each of the arrays to form a new array. // The general syntax is const arr = [1, 2, 3]; const rs = []; for (const n of arr) {rs. push (++ n);} console. log (rs) // map rewrite const arr = [1, 2, 3]; const rs = arr. map (n => ++ n );
In the above general statement, traversing the array through the for... of loop will produce additional operations, and there is a risk of changing the original array.
The map function encapsulates the necessary operations, so that we only need to care about the implementation of the ing logic function, which reduces the amount of code and the risk of side effects.
Currying)
Some parameters of a function are given to generate a new function that accepts other parameters.
This term may not be often heard, but it has been seen by people who have used undescore or lodash.
There is a magic _. partial function, which is the implementation of colialization.
// Obtain the relative path of the target file to the basic path. // The general syntax is const BASE = '/path/to/base'; const relativePath = path. relative (BASE, '/some/path ');//_. parical rewrite const BASE = '/path/to/base'; const relativeFromBase = _. partial (path. relative, BASE); const relativePath = relativeFromBase ('/some/path ');
Passed _. partial, we get the new function relativeFromBase, which is equivalent to calling path when called. by default, the first parameter is passed into the BASE.
In this example, the operation we really want to complete is to obtain the path relative to the BASE each time, rather than relative to any path. Keri makes it easier to call functions with only some parameters concerned.
Composing)
Combine the capabilities of multiple functions to create a new function
Similarly, the first time you saw him, he may still be in lodash. The compose method (now called flow)
// In the array, each word is capitalized and Base64 is used. // The General Writing Method (one of them) const arr = ['pen ', 'applypen', 'applypen ']; const rs = []; for (const w of arr) {rs. push (btoa (w. toUpperCase ();} console. log (rs );//_. flow rewrite const arr = ['pen ', 'applypen']; const upperAndBase64 = _. partialRight (_. map ,_. flow (_. upperCase, btoa); console. log (upperAndBase64 (arr ));
_. Flow combines the capabilities of functions that are converted to uppercase and Base64 to generate a new function. It can be used as a parameter function or later.
Your opinion
The JavaScript functional programming I understand may be different from many traditional concepts. I do not only think of high-order function compute function programming. Other functions, such as common functions combined with calls and chain structures, all belong to the category of function programming, as long as they use functions as the main carrier.
In my opinion, functional programming is not necessary and should not be a mandatory rule or requirement. Like object-oriented or other ideas, it is also one of the methods. In more cases, we should be a combination of several people, rather than a concept.
The above describes how to understand JavaScript functional programming. For more information, see other related articles in the first PHP community!