Home  >  Article  >  Web Front-end  >  Detailed explanation of function rationalization in JavaScript

Detailed explanation of function rationalization in JavaScript

青灯夜游
青灯夜游forward
2020-12-18 17:53:246502browse

Detailed explanation of function rationalization in JavaScript

Related recommendations: "javascript video tutorial"

I accidentally saw Function Currie recently when I was reading technology blogs in the community Change the words , and also ask for handwritten js function currying, I thought, what advanced thing is currying? Never heard of it?

I set out with the problem, studied it specifically, and did some sorting.

What is function currying?

What is function currying? Let’s first look at how Wikipedia explains it:

In computer science, Currying (English: Currying), also translated as Currying or Currying, is to transform a function that accepts multiple parameters into one that accepts one The technique of taking a function with a single parameter (the first parameter of the original function) and returning a new function that accepts the remaining parameters and returns the result.

This technique was named by Christopher Strachey after the logician Haskell Gary, although it was invented by Moses Schönfinkel and Gottlob Frege.

Intuitively, currying states that "if you fix some parameters, you will get a function that accepts the remaining parameters." So for the function y^x with two variables, if y=2 is fixed, we get the function 2^x with one variable.

The concept of Currying is actually not complicated. In easy-to-understand terms: only pass a part of the parameters to the function to call it, and let it return a function to process the remaining parameters.

If the text explanation is still a little abstract, we will use the add function to make a simple function curry implementation.

// 普通的add函数
function add(x, y) {
    return x + y
}

// add函数柯里化后
var curryingAdd = function(x) {
  return function(y) {
    return x + y;
  };
};

// 函数复用
var increment = curryingAdd(1);
var addTen = curryingAdd(10);

increment(2);
// 3
addTen(2);
// 12

In fact, the x and y parameters of the add function are changed to first using a function to receive x and then returning a function to process the y parameter. Now the idea should be clearer, which is to call the function by passing only part of the parameters and let it return a function to process the remaining parameters.

Why do we need to curry functions?

After reading the above about the currying of the add function, the question arises, what is the use of spending so much effort to encapsulate it?

1. Parameter reuse

In fact, the first currying example of the add function has already involved the function reuse brought about by function currying. For convenience, we can quickly implement the increment function and addTen function through currying of the add function. Let’s look at an example:

// 正常正则验证字符串 reg.test(txt)

// 函数封装后
function check(reg, txt) {
    return reg.test(txt)
}

check(/\d+/g, 'test')       //false
check(/[a-z]+/g, 'test')    //true

// Currying后
function curryingCheck(reg) {
    return function(txt) {
        return reg.test(txt)
    }
}

var hasNumber = curryingCheck(/\d+/g)
var hasLetter = curryingCheck(/[a-z]+/g)

hasNumber('test1')      // true
hasNumber('testtest')   // false
hasLetter('21212')      // false

The above example is a regular verification. Normally, call check directly. The function is fine, but if I have many places where I need to check whether there are numbers, I actually need to reuse the first parameter reg, so that other places can directly call hasNumber, hasLetter and other functions so that the parameters can be reused. It is also more convenient to call.

2. Confirm in advance
var on = function(element, event, handler) {
    if (document.addEventListener) {
        if (element && event && handler) {
            element.addEventListener(event, handler, false);
        }
    } else {
        if (element && event && handler) {
            element.attachEvent('on' + event, handler);
        }
    }
}

var on = (function() {
    if (document.addEventListener) {
        return function(element, event, handler) {
            if (element && event && handler) {
                element.addEventListener(event, handler, false);
            }
        };
    } else {
        return function(element, event, handler) {
            if (element && event && handler) {
                element.attachEvent('on' + event, handler);
            }
        };
    }
})();

It may be easier to understand if it is written in another way. The above is to determine the isSupport parameter first

var on = function(isSupport, element, event, handler) {
    isSupport = isSupport || document.addEventListener;
    if (isSupport) {
        return element.addEventListener(event, handler, false);
    } else {
        return element.attachEvent('on' + event, handler);
    }
}

In the process of doing projects, it is very common to encapsulate some DOM operations. The first way of writing above is also relatively common, but let's take a look at the second way of writing. It is relatively different. The first way of writing is to self-execute and then return a The new function actually determines which method it will take in advance, avoiding making judgments every time.

3. Delayed calculation/operation
Function.prototype.bind = function (context) {
    var _this = this
    var args = Array.prototype.slice.call(arguments, 1)

    return function() {
        return _this.apply(context, args)
    }
}

Like bind that is often used in our js, the implementation mechanism is Currying.

How to implement function Ke Lihua?

General encapsulation method:

// 初步封装
var currying = function(fn) {
    // args 获取第一个方法内的全部参数
    var args = Array.prototype.slice.call(arguments, 1)
    return function() {
        // 将后面方法里的全部参数和args进行合并
        var newArgs = args.concat(Array.prototype.slice.call(arguments))
        // 把合并后的参数通过apply作为fn的参数并执行
        return fn.apply(this, newArgs)
    }
}

Here is the preliminary encapsulation first, saving the preliminary parameters through the closure, and then splicing them by getting the remaining arguments, and finally executing the currying required function.

But the above function still has some flaws. If it is returned in this way, it can only expand one more parameter. Currying(a)(b)(c) does not seem to be supported (multi-parameter calls are not supported) , generally in this case, you will think of using recursion and encapsulating it one layer further.

// 支持多参数传递
function progressCurrying(fn, args) {

    var _this = this
    var len = fn.length;
    var args = args || [];

    return function() {
        var _args = Array.prototype.slice.call(arguments);
        Array.prototype.push.apply(args, _args);

        // 如果参数个数小于最初的fn.length,则递归调用,继续收集参数
        if (_args.length < len) {
            return progressCurrying.call(_this, fn, _args);
        }

        // 参数收集完毕,则执行fn
        return fn.apply(this, _args);
    }
}

This is actually based on the preliminary, adding recursive calls. As long as the number of parameters is less than the initial fn.length, the recursion will continue to be executed.

What is the performance of function currying?

Regarding the performance of Currying, we should know the following points:

  • Accessing arguments objects is usually a little slower than accessing named parameters
  • Some older versions The implementation of arguments.length in browsers is quite slow
  • Using fn.apply(…) and fn.call(…) is usually slightly slower than calling fn(…) directly
  • Creating a large number of nested scopes and closure functions will bring costs, both in terms of memory and speed

In fact, in most applications, the main performance bottleneck is operating DOM nodes , the performance loss of this js is basically negligible, so curry can be used directly and safely.

Curried interview questions

// 实现一个add方法,使计算结果能够满足如下预期:
add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;

function add() {
    // 第一次执行时,定义一个数组专门用来存储所有的参数
    var _args = Array.prototype.slice.call(arguments);

    // 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
    var _adder = function() {
        _args.push(…arguments);
        return _adder;
    };

    // 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
    _adder.toString = function () {
        return _args.reduce(function (a, b) {
            return a + b;
        });
    }
    return _adder;
}

add(1)(2)(3)                // 6
add(1, 2, 3)(4)             // 10
add(1)(2)(3)(4)(5)          // 15
add(2, 6)(1)                // 9

Summary

By simply passing a few parameters, you can dynamically create useful new functions; and it also brings an additional benefit , that is, the mathematical function definition is retained, although there is more than one parameter.

The Currying function is very easy to use, and using it every day is simply a pleasure for me. It is an essential tool to have on hand to make functional programming less cumbersome and boring.

For more programming-related knowledge, please visit: Introduction to Programming! !

The above is the detailed content of Detailed explanation of function rationalization in JavaScript. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:segmentfault.com. If there is any infringement, please contact admin@php.cn delete