How to quickly get started with VUE3.0: Enter Learning
Related Recommendations: JavaScript Learning Tutorial
I have seen many explanations about functional programming, but most of them are at the theoretical level, and some are only for pure functional programming languages such as Haskell. The purpose of this article is to talk about the specific practice of functional programming in JavaScript in my eyes. The reason why it is "in my eyes" means that what I say only represents my personal opinion, which may conflict with some strict concepts.
This article will omit a lot of formal concept introduction, and focus on showing what functional code is in JavaScript, what is the difference between functional code and general writing, what benefits functional code can bring us, and common What are some functional models?
I think functional programming can be understood as a programming method that uses functions as the main carrier. Using functions to disassemble and abstract general expressions
is compared with imperatives. What are the benefits of doing so? The main points are as follows:
clearer semantics, higher reusability, higher maintainability, better scope limitation, and fewer side effects. Basic functional programming. The following example is a specific functional expression of
Javascript code
// each word in the array, Capitalize the first letter // General writing 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); // Functional writing method - function upperFirst(word) { return word[0].toUpperCase() + word.slice(1); } function wordToUpperCase(arr) { return arr.map(upperFirst); } console.log(wordToUpperCase(['apple', 'pen', 'apple-pen'])); // Functional writing method 2 console.log(arr.map(['apple', 'pen', 'apple-pen'], word => word[0].toUpperCase() + word.slice(1))) ;
When the situation becomes more complex, the way to write expressions will encounter several problems:
the meaning is not obvious, it gradually becomes difficult to maintain, the reusability is poor, more code will be generated, and many intermediate variables will be generated. Functional programming is good solved the above problems. First, refer to functional writing method 1, which uses function encapsulation to decompose functions (the granularity is not unique), encapsulates them into different functions, and then uses combined calls to achieve the purpose. This makes the expression clear and easy to maintain, reuse and extend. Secondly, using higher-order functions, Array.map replaces for...of for array traversal, reducing intermediate variables and operations.
The main difference between functional writing method 1 and functional writing method 2 is that you can consider whether the function may be reused later. If not, the latter is better.
From the functional writing method 2 above, we can see that in the process of writing functional code, it is easy to cause horizontal extension, that is, to produce multiple layers of nesting. Let's give an extreme example below.
Javascript code
// Calculate the sum of numbers // General writing method console.log(1 + 2 + 3 - 4) // Functional writing function sum(a, b) { return a + b; } function sub(a, b) { return a - b; } console.log(sub(sum(sum(1, 2), 3), 4); This example only shows an extreme case of horizontal extension. As the number of nested levels of functions continues to increase, the code will become less readable. The performance is greatly reduced and it is easy to make mistakes. In this case, we can consider multiple optimization methods, such as the following chain optimization. // Optimize writing (well, you read that right, this is lodash's chain writing) Javascript code 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());
After rewriting in this way, the overall structure will become clearer, and each link of the chain will be What to do can also be shown easily. Another good example of the comparison between function nesting and chaining is the callback function and Promise pattern.
Javascript code
// Request two interfaces sequentially // 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'); } }); } }); // Promise import request from 'catta'; // catta is a lightweight request tool that supports fetch, jsonp, ajax, and has no dependencies 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 callback function nesting level and single-level complexity increase, it will become It is bloated and difficult to maintain, but the chain structure of Promise can still expand vertically when the complexity is high, and the hierarchical isolation is very clear.
The common functional programming model
can retain local variables in a code block that is not released. It is called a closure.
The concept of closure is relatively abstract. I believe everyone knows more or less and uses this feature
. What benefits can bags bring us?
Let’s first look at how to create a closure:
Javascript code
// Create a closure function makeCounter() { let k = 0; return function() { return ++k; }; } const counter = makeCounter(); console.log(counter()); // 1 console.log(counter()); // 2
makeCounter The code block of this function references the local variable k in the returned function, causing the local variable to fail. After the function is executed, it is recycled by the system, thus generating a closure. The function of this closure is to "retain" the local variable so that the variable can be reused when the inner function is called; unlike global variables, this variable can only be referenced inside the function.
In other words, closures actually create some "persistent variables" that are private to the function.
So from this example, we can conclude that the conditions for creating a closure are:
there are inner and outer functions. The inner function refers to the local variables of the outer function. The purpose of closure. The main purpose of closure is to define some functions. Domain-limited persistent variables, these variables can be used for caching or intermediate calculations, etc.
Javascript code
// Simple caching tool // The 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'); // 1The
above example is the implementation of a simple caching tool. The anonymous function creates a closure so that the store object can always be referenced. , will not be recycled.
Disadvantages of closures: persistent variables will not be released normally and continue to occupy memory space, which can easily cause memory waste, so some additional manual cleanup mechanisms are generally required.
function that accepts or returns a function is called a higher-order function.
It sounds like a very cold word, but in fact we often use it, but we just don’t know their names. The JavaScript language natively supports higher-order functions, because JavaScript functions are first-class citizens, and they can be used both as parameters and as the return value of another function.
We can often see many native high-order functions in JavaScript, such as Array.map, Array.reduce, and Array.filter.
Let’s take map as an example. Let’s see how he uses
Mapping is a collection of In other words, each item in the set is subjected to the same transformation to generate a new set
map. As a high-order function, it accepts a function parameter as the logical
Javascript code
of the mapping// Add one to each item in the array to form A new array // General writing method const arr = [1,2,3]; const rs = []; for(const n of arr){ rs.push(++n); } console.log(rs) // map rewrites const arr = [1,2,3]; const rs = arr.map(n => ++n);
the above general writing method, using the for...of loop to traverse the array will cause additional operations, and There is a risk of changing the original array
, but the map function encapsulates the necessary operations, so that we only need to care about the function implementation of the mapping logic, which reduces the amount of code and the risk of side effects.
gives some parameters of a function and generates a new function that accepts other parameters.
You may not often hear this term, but anyone who has used undescore or lodash has seen it.
There is a magical _.partial function, which is curried
Javascript code
// Get the relative path of the target file to the base path // The general writing method 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');
Through _.partial, we get the new function relativeFromBase. When this function is called, it is equivalent to calling path.relative, and the first parameter is passed to BASE by default. Subsequent parameters passed in are appended in order.
In this case, what we really want to accomplish is to get the path relative to BASE each time, not relative to any path. Currying allows us to only care about some of the parameters of a function, making the purpose of the function clearer and calling it simpler.
combines the capabilities of multiple functions to create a new function.
You may have seen it for the first time in lodash, the compose method (now called flow)
Javascript code
// Capitalize each word in the array, do Base64 //General writing method (one of them) const arr = ['pen', 'apple', 'applypen']; const rs = []; for(const w of arr){ rs.push(btoa(w.toUpperCase())); } console.log(rs); // _.flow rewrite const arr = ['pen', 'apple', 'applypen']; const upperAndBase64 = _.partialRight(_.map, _.flow(_.upperCase, btoa)); console.log(upperAndBase64(arr));
_.flow merges the capabilities of the uppercase conversion and Base64 conversion functions to generate a new function. Convenient to use as parameter function or subsequent reuse.
From my own point of view, my understanding of JavaScript functional programming may be different from many traditional concepts. I don’t only think that high-order functions count as functional programming. Others, such as ordinary function combined calls, chain structures, etc., I think belong to the category of functional programming, as long as they use functions as the main carrier.
And I think functional programming is not necessary, nor should it be a mandatory rule or requirement. Like object-oriented or other ideas, it is also one of the ways. In most cases, we should be a combination of several, rather than limited to concepts.
Related recommendations: JavaScript tutorial.
The above is a detailed discussion of JavaScript functional programming. For more information, please pay attention to other related articles on the PHP Chinese website!