preface

Functional programming has become a hot topic on the front end. In the last few years, we’ve seen a lot of application code bases making heavy use of functional programming ideas.

This article will skip the arcane concepts and focus on what is functional code in JavaScript, the difference between declarative and imperative code, and what are the common functional models? For more quality articles, please click on the GitHub blog

What is functional programming

Functional programming is a programming paradigm that uses functions to encapsulate the operation process and calculates the result by combining various functions. Functional programming means you can write code with fewer bugs in less time. As a simple example, suppose we wanted to capitalize the string functional programming is Great with the first letter of each word:

var string = 'functional programming is great';
var result = string
  .split(' ')
  .map(v => v.slice(0, 1).toUpperCase() + v.slice(1))
  .join(' ');
Copy the code

The above example uses split to convert a string to an array, then maps to capitalize each element, and finally joins to convert an array to a string. The whole process is called join(Map (split(STR))), which embodies the core idea of functional programming: data transformation through functions.

From this we can conclude that functional programming has two basic characteristics:

  • Data is converted by functions
  • Find the result by concatenating multiple functions

2. Contrast declarative and imperative

  • Imperative: We write instruction after instruction to make the computer perform some action, which usually involves a lot of details. Imperative code frequently uses statements to accomplish an action. For, if, switch, throw, etc.

  • Declarative: We declare what we want to do by writing expressions, not by giving step-by-step instructions. An expression is usually a composition of some function call, values, and operators used to compute the resulting value.

// directives var CEOs = [];for(var i = 0; i < companies.length; I ++){CEOs who contribute to (companies[I].ceo)} // statements var CEOs who contribute to = companies.Copy the code

From the above example, we can see that declarative writing is an expression that doesn’t care how the counter is iterated, how the returned array is collected, or what to do, not how to do it. One of the obvious benefits of functional programming is declarative code. For pure functions with no side effects, we can write business code without thinking about how the function is implemented internally.

Three, common features

No side effect

A function is called without modifying its external state, that is, a function is called n times and still returns the same result.

var a = 1; // Contains side effects, which modify the external variable A // the result is different multiple timesfunction test1() {
  a++
  returna; } // No side effects, no changes to the external state // the result of multiple calls is the samefunction test2(a) {
  return a + 1;
}
Copy the code

Transparent reference

A function that uses only the variables passed to it and internally created variables, but no other variables.

var a = 1; var b = 2; // Use variables inside a function that are not in its scopefunction test1() {
  returna + b; } // The variables used inside the function are passed in explicitlyfunction test2(a, b) {
  return a + b;
}
Copy the code

Immutable variable

Once a variable is created, it cannot be modified. Any modification will create a new variable. The biggest benefit of using immutable variables is thread-safety. Multiple threads can access the same immutable variable at the same time, making parallelism easier. Since JavaScript natively does not support immutable variables, this is done through third-party libraries. (e.g. Immutable. Js, Mori, etc.)

var obj = Immutable({ a: 1 });
var obj2 = obj.set('a', 2);
console.log(obj);  // Immutable({ a: 1 })
console.log(obj2); // Immutable({ a: 2 })
Copy the code

The function is a first-class citizen

Functions are often said to be JavaScript’s “first class citizens,” meaning that functions are equal to other data types and can be assigned to other variables, passed in as arguments to another function, or returned as a value from another function. Closures, higher-order functions, function corrification, and function composition are all around the application of this property

Common functional programming models

1. Closure

A function is a closure if it refers to a free variable. What is a free variable? A free variable is a variable that is outside the scope of the function. (All global variables are free variables, and functions that reference global variables are technically closures, but such closures are not useful. In general, closures are functions inside functions.)

Closure formation conditions:

  • There are inner and outer functions
  • The inner function refers to local variables of the outer function

What closures do: You can define scoped persistence variables that can be used for caching, intermediate calculations, etc.

// The anonymous function creates a closure const cache = (function() {
  const store = {};
  
  return {
    get(key) {
      return store[key];
    },
    set(key, val) { store[key] = val; }}} ()); The console. The log (cache) / / {get: ƒ.setƒ} cache. Set ('a', 1);
cache.get('a'); / / 1Copy the code

The example above is an implementation of a simple caching tool. Anonymous functions create a closure so that store objects can always be referenced without being recycled.

The downside of closures is that persistent variables are not released properly, which can easily lead to memory waste, so some additional manual cleanup is often required.

2. Higher-order functions

Functional programming tends to reuse a common set of functions to process data by using higher-order functions. A higher-order function is a function that takes a function as an argument, a function as a return value, or both.

Higher-order functions are often used:

  • Abstract or isolate actions, functions, asynchronous control flow as callback functions, Promises, Monads, etc
  • Create functionality that can be used generically for a variety of data types
  • Partially applied to function parameters (partial function application) or to create a Currified function for reuse or function composition.
  • Takes a list of functions and returns some composite functions made up of the functions in the list.

The JavaScript language natively supports higher-order functions, such as array.prototype. map, array.prototype. filter and array.prototype. reduce are some of the higher-order functions built into JavaScript. Using higher-order functions makes our code cleaner and cleaner.

map

The map() method creates a new array, and the result is the result returned when each element in the array calls one of the provided functions. Map does not change the original array.

Suppose we have an array of objects with name and type attributes, and we want all the name attributes in the array to be in a new array. How do we do that?

// Do not use the higher-order function var animals = [{name:"Fluffykins", species: "rabbit" },
  { name: "Caro", species: "dog" },
  { name: "Hamilton", species: "dog" },
  { name: "Harold", species: "fish" },
  { name: "Ursula", species: "cat" },
  { name: "Jimmy", species: "fish"}]; var names = [];for (leti = 0; i < animals.length; i++) { names.push(animals[i].name); } console.log(names); / / /"Fluffykins"."Caro"."Hamilton"."Harold"."Ursula"."Jimmy"]
Copy the code
// Use the higher-order function var animals = [{name:"Fluffykins", species: "rabbit" },
  { name: "Caro", species: "dog" },
  { name: "Hamilton", species: "dog" },
  { name: "Harold", species: "fish" },
  { name: "Ursula", species: "cat" },
  { name: "Jimmy", species: "fish"}]; var names = animals.map(x=>x.name); console.log(names); / / /"Fluffykins"."Caro"."Hamilton"."Harold"."Ursula"."Jimmy"]
Copy the code

filter

The filter() method creates a new array containing all the elements tested by the callback function. Filter calls the callback function once for each element in the array. The callback function returns true indicating that the element passed the test and is retained, false indicating that the element is not retained. Filter does not alter the original array; it returns the filtered new array.

Suppose we have an array of objects with name and type attributes. We want to create an array containing only dogs (Species: “dog”). How do you do that?

// Do not use the higher-order function var animals = [{name:"Fluffykins", species: "rabbit" },
  { name: "Caro", species: "dog" },
  { name: "Hamilton", species: "dog" },
  { name: "Harold", species: "fish" },
  { name: "Ursula", species: "cat" },
  { name: "Jimmy", species: "fish"}]; var dogs = [];for (var i = 0; i < animals.length; i++) {
  if (animals[i].species === "dog") dogs.push(animals[i]);
}
console.log(dogs); 
Copy the code
// Use the higher-order function var animals = [{name:"Fluffykins", species: "rabbit" },
  { name: "Caro", species: "dog" },
  { name: "Hamilton", species: "dog" },
  { name: "Harold", species: "fish" },
  { name: "Ursula", species: "cat" },
  { name: "Jimmy", species: "fish"}]; var dogs = animals.filter(x => x.species ==="dog");
console.log(dogs); // {name: "Caro", species: "dog"}
// { name: "Hamilton", species: "dog" }
Copy the code

reduce

The reduce method performs a callback function on each element of the called array, eventually generating a single value and returning it. The reduce method accepts two parameters: 1) a Reducer function (callback) and 2) an optional initialValue.

Suppose we want to sum an array:

Const arr = [5, 7, 1, 8, 4];let sum = 0;
for (leti = 0; i < arr.length; i++) { sum = sum + arr[i]; } console.log(sum); / / 25Copy the code
// const arr = [5, 7, 1, 8, 4]; const sum = arr.reduce((accumulator, currentValue) => accumulator + currentValue,0); console.log(sum)//25Copy the code

We can see the difference between the three vividly in the following figure:

3. Coriolization of functions

Also known as partial evaluation, a Currization function takes some parameters and does not evaluate them immediately. Instead, it continues to return a new function, storing the parameters as a closure, and evaluating all of the parameters once they are actually evaluated.

// A normal functionfunction add(x,y){
    returnx + y; } the add (1, 2); Var add = // var add = //function(x) {
  return function(y) {
    returnx + y; }; }; var increment = add(1); increment(2); / / 3Copy the code

Here we define an add function that takes an argument and returns a new function. After you call Add, the returned function remembers the first argument to add in the form of a closure. So, how do we implement a simple Currization function?

functionCurryIt (fn) {// Parameter number of parameters of the function var n = fn.length; var args = [];return function(arg) {
    args.push(arg);
    if (args.length < n) {
      returnarguments.callee; // Return a reference to this function}else {
      returnfn.apply(this, args); }}; }function add(a, b, c) {
  return[a, b, c]; } var c = curryIt(add); var c1 = c(1); var c2 = c1(2); var c3 = c2(3); console.log(c3); / / [1, 2, 3]Copy the code

From this we can see that Currization is a “pre-loading” function method, by passing fewer parameters, get a new function that has remembered these parameters, in a sense, this is a “cache” of parameters, is a very efficient way to write a function!

4. Composition of functions

As mentioned earlier, one of the hallmarks of functional programming is to evaluate through concatenation functions. However, as the number of concatenation functions increases, the code becomes less readable. Combination of functions is the way to solve this problem. Assume that you have a compose function that can take multiple functions as arguments and then return a new one. When we pass an argument to the new function, the argument “flows” through the function and returns the result.

// The combination of two functions var compose =function(f, g) {
    return function(x) {
        returnf(g(x)); }; }; Var compose => (x => f(g(x))); var add1 = x => x + 1; var mul5 = x => x * 5; compose(mul5, add1)(2); / / = > 15Copy the code

To recommend a good BUG monitoring toolFundebug, welcome free trial!

Welcome to pay attention to the public number: front-end craftsmen, we will witness your growth together!

Refer to the article

  • Everest Architecture Course (highly recommended)
  • MDN document
  • What is Functional Programming?
  • So You Want to be a Functional Programmer
  • Understand higher-order functions in JavaScript
  • What I know about functional programming
  • JS functional programming guide
  • JavaScript Functional Programming (PART 1)
  • My view of JavaScript functional programming