This article is from a learning sharing in the name of joy by @, a team member. I hope to share and discuss with you.

Seek to accumulate silicon step so that thousands of miles, dare to explore the beauty of life.

Let’s start with two pieces of code:

// Programming without using functions
const upperArr = ['HTML'.'CSS'.'JavaScript'];
const lowerArr = [];
for (let i = 0; i < arr.length; i++) {
    lowerArr.push(arr[i].toLowerCase());
}

// Programming with functions
function toLowerArr(arr) {
    const lowerArr = [];
    for (let i = 0; i < arr.length; i++) {
        lowerArr.push(arr[i].toLowerCase());
    }
    return lowerArr;
}
Copy the code

Compared with the two pieces of code, the second method is undoubtedly recommended, that is, encapsulating business logic with functions, which is convenient for reuse and maintenance, and can isolate the influence of outer scopes and avoid contaminating global variables. It’s a bit like going to the supermarket on the weekend and buying a lot of stuff and having to scramble to grab it with both hands if you don’t have a bag. Functions are like plastic bags that cost extra. They can carry everything and are easy to carry, which is pretty much how we first came to know and use them.

Contrast object orientation

Object-oriented programming is all about it, but for most FE, “functions are the real love.” With regard to the difference between “functional programming” and “object orientation,” let’s consider a small question to explore the difference.

“Question: How do you put an elephant in a refrigerator?”

Process-oriented: steps

  1. Open the refrigerator;
  2. Pick up the elephant;
  3. Stuffed in the fridge;
  4. Turn off the refrigerator;
  • Perspective: To look at problems from the perspective of implementation, focusing on concrete implementation;
  • Advantages: Relatively simple, no need to deal with object instantiation;
  • Disadvantages: Functional coupling, difficult to maintain, easy to produce powerful functions that are difficult to maintain;

Object oriented: object/interface/class/inheritance

  1. Create the refrigerator object, giving attributes: open size | | | storage;
  2. Create an elephant object with attributes: size;
  3. Call refrigerator method -> open door;
  4. Call refrigerator method -> Store;
  5. Call refrigerator method -> close the door;
  • 3. To see a problem from the point of view of an object;
  • Advantages: easy to maintain and expand, suitable for multi-person development of large projects, inheritance/encapsulation/polymorphic, derivable;
  • Disadvantages: there is a certain performance loss, object instantiation memory footprint, instance object properties are not needed every time;

By comparing these two approaches, you can see that the kind of functional programming we use every day is procedural programming. Each function is the encapsulation of each step, focusing on the concrete implementation rather than the abstraction of the attributes of objective things.

With a brief understanding of the differences, we move on to today’s topic: functional programming.

Functional programming

Functional Programming: [FP: Functional Programming] describes the mapping between data and functions, or the abstraction of operations

Basis function

  1. Functions can be stored in variables;
  2. Functions can be passed as arguments;
  3. A function can be returned as a return value;
  4. Functionable objects have properties;
  5. Function declarations take precedence over assignment (contrast variable declarations/function expressions);
  6. Parameters are passed by value;
  7. Functions in JavaScript do not support overloading;

Function execution mechanism

JavaScript functions are executed using stack data structures. The characteristics of the stack is that there is only one entrance and exit, only from the top of the stack, following the principle of “advanced after out, last in first out”.

  1. When the code is executed, the global environment is entered and the global context is pushed.
  2. When calling a function, enter the function environment and push the function context to the stack.
  3. At the end of the function call, the stack operation is performed;
  4. The top of the stack stores the currently executing context;

The normal execution of a function is a stack access operation like this:

function foo() {
    function bar() {
        return 'I am bar';
    }
}
foo();
Copy the code

This is just the ideal situation in JavaScript“Closure”The existence of will interrupt the function out of the stack operation, so that the function that has finished the call still exists in the stack, occupying the memory overhead.

Closure: a function whose internal variable is referenced by another function after completion

Closure generation: Closures are generated in situations where a function is returned as a return value, or as an argument, and the function uses variables in an external scope. A normal function call would end by destroying the variable object, freeing up memory, but the existence of a closure makes references to variables inside the parent function exist, so they are kept in memory.

What closures do:

  1. Extend the scope of a variable object;
  2. Prolong the life cycle of variable objects;
function foo() {
    const fooVal = 'VanTopper';
    const bar = function() {
      console.log(fooVal);	// bar uses variables from foo's environment
    }
    return bar;			// The function is returned as a return value
}

const getValue = foo();
getValue();			// -> VanTopper
Copy the code

Function life cycle

Create a stage Execution phase End stage
1. Create a variable object

Initialize Arguments objects (and assign values)

— Function declaration (and assignment)

— Variable declaration/function expression declaration (no assignment)

— Make sure this points to

2. Define scope
1. Variable object assignment

— Variable assignment

— Function expression assignment

2. Call the function

3. Execute other code

Variable object destruction (no closure scenario)

Higher-order functions

The function is passed as an argument or returned as a return value

Commonly used higher-order functions: forEach/map/filter/every/some/find

“What higher-order functions do: Abstract away implementation details and focus only on achieving the goal.”

Take filter as an example. To implement the filter function, you need to traverse the number groups and compare each item. Traversal is the fixed part, but the alignment logic is variable. We can solidify the invariant part. “The package of the program begins with the invariant part.” Abstracting the traversal process and providing a configuration entry for the alignment logic allows the filter method caller to focus only on the alignment logic without having to write the traversal process over and over again.

// High order function usage: easy to implement filter
function filter(arr, fn) {
  const newArr = [];
  for (let i = 0; i < arr.length; i++) {
    const result = fn(arr[i], i);
    if(result) { newArr.push(result); }}return newArr;
}

filter([-2, -1.0.1.2].(val) = > val > 0);  / / - > [1, 2]
Copy the code

Pure functions

Functions have no side effects (data contamination), fixed input, fixed output

Source of side effects

All interactions with the outside world can have side effects, including:

  1. External configuration files;
  2. Data stored in the database;
  3. User input data;
  4. Function internal source data modification;
/* ----- pure function start ----- */
function sum(a, b) {
    return a + b;
}
sum(1.2);	/ / 3
sum(1.2);	/ / 3
sum(1.2);	/ / 3


/* ----- impure function start ----- */
// Do not adhere to fixed input, fixed output
function getRandom() {
    // Return a different value each time
    return Math.random();
}

// Modify the data directly inside the function
function formatData(arr) {
    for (let item of arr) {
        // Directly modify the original array object
        item.visible = item.visible ? 1 : 0}}Copy the code

Advantages/benefits :(first, I’m pure)

  1. Stable output, reliable auxiliary (fixed input, fixed output);
  2. Don’t rob soldiers don’t rob heads (no side effects, no pollution of data);
  3. It can be used as cache to improve performance (parameter is used as key value to cache calculation results to avoid repeated operation, and the same parameter is only calculated once);
  4. Can be tested (multiple calls, data does not repeatedly cross jump);
/* Realize the function function with cache calculation 1. Use pure function to fix the input and output, to ensure that the same parameters at the same time call the same result 2. Maintain cache objects with closures, extending their scope and life cycle */

// Cache function
function memoize(fn) {
    const cache = {};
    return function() {
  	const key = JSON.stringify(arguments);
        cache[key] = cache[key] || fn.apply(fn, arguments);
        returncache[key]; }}// Function function
function getArea(r) {
    console.log('Computed... ');
    return Math.PI * r * r;
}

const getAreaWithMemory = memoize(getArea);
getAreaWithMemory(4);	// execute log only once with the same 'Computed' parameter
getAreaWithMemory(4);   // 
getAreaWithMemory(4);   // 
Copy the code

Currie,

In general, when a function is called, it is evaluated once. If the number of arguments does not match, and no default value is specified (no internal default processing for parameters), it cannot be called normally. For example :(a, b) => a + b)(10);

And “function currification” is a kind of function programming method that supports pre-parameter passing and partial calculation.

For example: “deposit to buy a house”, suppose we have one goal is to buy a house, then before buying a house, when the fund is not enough, we can take the method of saving for accumulation. Wait until the money to save enough, and then use savings, buy a house operation. Corrified, like we go to the bank to deposit money, the bank gives you a bank card, can let you continue to deposit money inside (pre-pass), each deposit action will increase the balance of the card (partial calculation), such as the amount of money enough to pay, can be withdrawn payment.

Here are two formulas to compare how ordinary functions are called:

// Formula type 1: normal function | right: Currization
fn(a, b, c, d) => fn(a)(b)(c)(d);
fn(a, b, c, d) => fn(a, b)(c)(d);
fn(a, b, b, d) => fn(a)(b, c, d);

// Type 2fn(a, b, c, d) => fn(a)(b)(c)(d)(); fn(a, b, c, d) => fn(a); fn(b); fn(c); fn(d); fn();Copy the code

Through the transformation of the above formula, we can get several characteristics of Corrification:

  • Part of the mass to participate
  • Return early
  • Delay the
// The currie method is implemented
function curry(fn) {
    return function curriedFn(. args) {
        if (args.length < fn.length) {
            return function() {
                returncurriedFn(... args.concat(Array.from(arguments)))}}returnfn(... args) } }Copy the code

Coriolization applications

  • Change the normal function full parameter call way;
  • The method of prepassing parameters, caching parameters;
  • Derive more semantic, more convenient to call functions;
// Validate the function
function checkValid(regex, value) {
    return regex.test(value);
}

// Verify mobile phone number
checkValid(/^1[3456789]\d{9}$/, value);
// Verify phone number
checkValid(/ ^ ([0-9] {3, 4} -)? [0-9] {7, 8} $/, value);
Copy the code

While checkValid basically does what it does, it is called in a very unfriendly way, requiring users to enter “unmemorable and complex” regular expressions every time they call it, which is a bit un-human. For this kind of scene, we can carry out the following transformation with the help of Corrification:

// After currying (with the help of lodash's _.curry method, as with the self-implemented curry function)
const curryCheckValid = _.curry(checkValid);
const checkMobile = curryCheckValid(/^1[3456789]\d{9}$/);
const checkPhone = curryCheckValid(/ ^ ([0-9] {3, 4} -)? [0-9] {7, 8} $/);

checkMobile(value);
checkPhone(value);
Copy the code

Compared with checkValid, The checkMobile derived from the currization process is obviously more friendly to callers, more in line with the principle of minimum knowledge, and reduces the use cost.

Corrified summary

Advantages:

  1. Allow partial parameter transfer, fixed variable factors (calculated in advance, can be used as cache);
  2. Return a function that can handle the rest of the argument (save for successor);
  3. Can transform multivariate function into one or less element function (combine function combination to play its strength);
  4. Spawning smaller, single-responsibility functions (lower learning costs for users);

Disadvantages:

  1. The existence of closures brings memory overhead;
  2. The cost of nested scopes and the speed of scope chain lookup;

Combination f of g of v.

The idea of composition: “Pipe assembly”

As shown in the figure, a large pipe contains the required functions, but such a large pipe will cause a lot of waste material when it needs to be cut due to size problems. If there is a problem in the middle, it can only be replaced completely. And divided into several sections of small pipes, not only convenient length customization, can save waste cost, and reduce the cost of problem identification and maintenance and replacement.

Function composition refers to the idea of small pipes, each of which is a small function and then combined into a large function. Callers do not care about the processing results of these small functions in the middle, but only pay attention to the processing results of the header input parameters and the tail.

The default is “right to left”. Here’s how it works:

// Simple combinatorial functions (the implementation here is a simple nested function)
function compose(f, g) {
    return function(value) {
        return f(g(value));
    };
}

const first = arr= > arr[0];
const reverse = arr= > arr.reverse();

// Get the last element of the array
const getLast = compose(first, reverse);
getLast('Angular'.'React'.'Vue'];	// -> Vue
Copy the code

Function combination seems to complicate simple problems, for the above business requirements, can be achieved in a function. Splitting functions adds to the workload, but their existence must be justified.

In daily development, in order to achieve business functions, many partners often write such functions one by one “very powerful”, integrating all logic to solve together. However, the reusability of this function is very low, because the function is coupled with too many functional dependencies. If some functions need to be reused, it may be safer to create a new function. And this unnecessary function increase, there will be a lot of similar and different functions of the code, invisible increase redundancy. And the parameters of complex functions are often not single, requiring users to have more parameter learning costs.

Function combination also has the function of “divide and conquer”, which separates function functions into simple and smaller functions with finer granularity. Take the idea of Lego bricks and assemble them into more powerful functions. Moreover, these small functions often only cost to create once and are highly reusable.

Another advantage is that open source third-party tool functions like Lodash /underscore already provide these basic functions without us having to maintain them ourselves.

// Use lodash to rewrite getLast
FlowRight is a componse function
const getLast = _.flowRight(_.first, _.reverse);
getLast(['Angular'.'React'.'Vue');	// -> Vue
Copy the code

Combination rules

  1. Combine multiple functions into new functions, ignore the middle part and only see the end result;
  2. Satisfy the associative law, the combination order is different, the result is the same (f1 and F2 can be combined, or F1 and F3 can be combined);
  3. Each function in the function combination should be unary, and the final function combined is also unary (fine granularity, small volume);
  4. Execution order: right to left (default)/left to right;

Function combination debugging

The characteristic of function combination is to pass the result of processing layer by layer, gradually to the next function. According to this principle, we can implement a log function as an intermediate function, add its own printing debugging function and need to pass the processing value transparently.

const getLast = _.flowRight(_.first, log, _.reverse);
getLast(['Angular'.'React'.'Vue']);	// We can print the result of the _.reverse processing

// Debug the function
function log(value) {
    console.log(value);
    return value;
}
Copy the code

lodash/fp

The LoDash/FP module is a friendly practice provided by LoDash to support functional programming

Let’s look at a small question, there is a similar interview question, the test point is map parameters and parseInt conversion.

// lodash
_.map(['10'.'10'.'10'].parseInt);
// -> [10, NaN, 2]

// lodash/fp
fp.map(parseInt['10'.'10'.'10']);
// -> [10, 10, 10]
Copy the code

The default loDash method is data first, which means data first, so the above _.map execution is as follows:

_.map(['10'.'10'.'10'].(val, index) = > parseInt(val, index));

parseInt('10'.0);  / / - > 10
parseInt('10'.1);  // -> NaN
parseInt('10'.2);  / / - > 2
Copy the code

MSDN -> parseInt Conversion rule

  • Lodash default method (data first, functions later)
  • Lodash/FP module method (function first, data later, automatic currification)

Comparison of three implementations

const str = 'NEVER SAY GOODBYE';

/* ---- native implementation ---- */
const transByDefault = (str) = > str.toLowerCase().split(' ').join(The '-');

/* ---- lodash ---- */
const join = _.curry((seq, str) = > _.join(str, seq));
const split = _.curry((seq, str) = > _.split(str, seq));

const transByLodash = _.flowRight(join(The '-'), split(' '), _.lowerCase);

/* ---- lodash/fp ---- */
const fp = _.noConflict();
const transByFp = fp.flowRight(fp.join(The '-'), fp.slit(' '), fp.lowerCase);


/ / call
transByDefault(str);	// -> never-say-goodbye
transByLodash(str);	// -> never-say-goodbye
transByFp(str);		// -> never-say-goodbye
Copy the code
A freestanding implementation Lodash implementation Lodash/fp
Function coupling, low function reuse
– Support function combination

– Data first, function later

– Functions of several variables need to be currified by themselves


– Automatic kerritization of multivariate functions

– Function priority, data after, no additional processing

Summary of function combination

Advantages:

  1. Merge budgeting processes into powerful functional functions (flexible combination);
  2. Ignore the intermediate process and focus only on the final result (as long as the result);
  3. Enhance function reuse, reduce strong function function;

Disadvantages:

  1. Some basic functions are needed (common utility functions provided by third-party libraries can be used).
  2. Need to define insert log function for intermediate result debugging;

This article is only a glimpse of functional programming, and there are functions such as partial functions, functors and other functional programming knowledge is not involved, interested partners can continue to further study.

That is all the content of this sharing. I hope it will help you

Like the words don’t forget to move your fingers, like, collect, pay attention to three even a wave away.


About us

We are the front end team, left hand component library, right hand tool library, all kinds of technology savage growth.

A man can’t run as fast as a crowd can run. Welcome to join our team, the year of the Ox is booming forward.