Применение функции высшего порядка — каррирование и антикаррирование

внешний интерфейс JavaScript функциональное программирование регулярное выражение


читать оригинал


предисловие

В JavaScript каррирование и антикаррирование — это применение функций высшего порядка. Перед этим мы должны знать, что такое функции высшего порядка. Говоря простым языком, функции можно передавать функциям в качестве параметров. Эта функция называется функцией обратного вызова. , а функция с этим параметром является функцией высшего порядка. Функция обратного вызова вызывается в функции высшего порядка и передает соответствующие параметры. Когда функция высшего порядка выполняется, из-за другой внутренней логики функции обратного вызова , выполнение функции высшего порядка Результаты также разные, очень гибкие, также известные как функциональное программирование.


карри

В JavaScript каррирование функций является важной идеей функционального программирования и важным приложением в функциях высшего порядка.Его смысл в том, чтобы шаг за шагом передавать параметры в функцию, каждый раз передавать некоторые параметры и возвращать более конкретную функцию в получить Для остальных параметров несколько уровней таких функций, которые получают частичные параметры, могут быть вложены в середине, пока не будет возвращен окончательный результат.

1. Самый простой сплит карри

// 原函数
function add(a, b, c) {
    return a + b + c;
}

// 柯里化函数
function addCurrying(a) {
    return function (b) {
        return function (c) {
            return a + b + c;
        }
    }
}

// 调用原函数
add(1, 2, 3); // 6

// 调用柯里化函数
addCurrying(1)(2)(3) // 6

Каррированная функцияaddCurryingКаждый раз, когда возвращаемое значение является функцией, а следующий параметр используется в качестве формального параметра.После того, как все три параметра переданы, последняя возвращенная функция выполняет операцию суммирования, которая фактически полностью использует характеристики замыкания. быть реализованным.

2, Carrying General Formula

Вышеуказанные функции Carrying не связаны с функциями более высокого порядка, не обладают универсальностью преобразования в любое количество параметров или неизвестной функции, мы следующей функцией Curry Curry Common Transfer, любая функция может быть преобразована в Carrying.

// ES5 的实现
function currying(func, args) {
    // 形参个数
    var arity = func.length;
    // 上一次传入的参数
    var args = args || [];

    return function () {
        // 将参数转化为数组
        var _args = [].slice.call(arguments);

        // 将上次的参数与当前参数进行组合并修正传参顺序
        Array.prototype.unshift.apply(_args, args);

        // 如果参数不够,返回闭包函数继续收集参数
        if(_args.length < arity) {
            return currying.call(null, func, _args);
        }

        // 参数够了则直接执行被转化的函数
        return func.apply(null, _args);
    }
}

Вышеупомянутое в основном использует синтаксис ES5 для достижения и использует многоcallа такжеapply, давайте реализуем ту же функцию каррирования общей формулы преобразования способом ES6.

// ES6 的实现
function currying(func, args = []) {
    let arity = func.length;

    return function (..._args) {
        _args.unshift(...args);

        if(_args.length < arity) {
            return currying.call(null, func, _args);
        }

        return func(..._args);
    }
}

функцияcurryingЭто относительно продвинутое преобразование общего назначения, и параметры могут быть разделены по желанию.Предполагая, что преобразуемая функция имеет несколько формальных параметров, мы можем передать любое количество параметров в любой ссылке для разделения.Например, если5параметры, которые могут быть переданы в первый раз2, второй можно передать1, в третий раз можно передать остальные, и есть другие различные схемы передачи и разбиения параметров, т.к.curryingПараметры собираются внутри и корректируются в соответствии с порядком параметров преобразуемой функции.

Одним из больших преимуществ каррирования является то, что оно может помочь нам реализовать функции с разными функциями путем разделения параметров на основе преобразованной функции, как в следующем примере.

// 被转换函数,用于检测传入的字符串是否符合正则表达式
function checkFun(reg, str) {
    return reg.test(str);
}

// 转换柯里化
let check = currying(checkFun);

// 产生新的功能函数
let checkPhone = check(/^1[34578]\d{9}$/);
let checkEmail = check(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/);

Приведенный выше пример преобразуется в каррированную функцию на основе преобразованной функции и используется_checkПеременная получена, и каждый последующий вызов_checkПередача разных регулярных выражений создаст функциональную функцию, которая обнаруживает разные типы строк.

Это использование также применимо, когда преобразованная функция является функцией более высокого порядка, как в следующем примере.

// 被转换函数,按照传入的回调函数对传入的数组进行映射
function mapFun(func, array) {
    return array.map(func);
}

// 转换柯里化
let getNewArray = currying(mapFun);

// 产生新的功能函数
let createPercentArr = getNewArray(item => `${item * 100}%`);
let createDoubleArr = getNewArray(item => item * 2);

// 使用新的功能函数
let arr = [1, 2, 3, 4, 5];
let percentArr = createPercentArr(arr); // ['100%', '200%', '300%', '400%', '500%',]
let doubleArr = createDoubleArr(arr); // [2, 4, 6, 8, 10]

3. Каррирование и связывание

bindМетод - это метод, который часто используется, его роль заключается в том, чтобы помочь нам вызватьbindобъект контекста внутри функцииthisЗамените его первым параметром, который мы передаем, и вызовите другие параметры, следующие за ним.bindпараметры функции.

// bind 方法的模拟
Object.prototype.bind = function (context) {
    var self = this;
    var args = [].slice.call(arguments, 1);

    return function () {
        return self.apply(context, args);
    }
}

Вышеуказанный код можно увидеть, на самом деле,bindметод представляет собой каррированную функцию преобразования, которая будет вызыватьbindФункция метода преобразуется, то есть через замыкание возвращается каррированная функция, а при выполнении каррированной функции заимствуетсяapplyпозвонюbindКонтекст выполнения функции преобразуется вcontextИ выполнить, но эта функция преобразования не такая уж сложная, без разделения параметров, но все параметры передаются при вызове функции.


антикарринг

Идея антикаррирования прямо противоположна каррированию.Если процесс каррирования состоит в том, чтобы разбить функцию на функции с более специфическими функциями, то эффект антикаррирования заключается в расширении применимости функции и создании исходная функция.Функции, принадлежащие определенному объекту, могут использоваться любым объектом.

1. Общая формула защиты от каррирования

Параметр универсального антикаррирования – это метод или функция, которую вы хотите вызывать из других объектов. При вызове универсального метода возвращается функция. Первый параметр этой функции – это объект для выполнения метода, а следующие – параметры - это выполнение метода параметры, которые будут переданы.

// ES5 的实现
function uncurring(fn) {
    return function () {
        // 取出要执行 fn 方法的对象,同时从 arguments 中删除
        var obj = [].shift.call(arguments);
        return fn.apply(obj, arguments);
    }
}
// ES6 的实现
function uncurring(fn) {
    return function (...args) {
        return fn.call(...args);
    }
}

Давайте на примере прочувствуем применение антикаррирования.

// 构造函数 F
function F() {}

// 拼接属性值的方法
F.prototype.concatProps = function () {
    let args = Array.from(arguments);
    return args.reduce((prev, next) => `${this[prev]}&${this[next]}`);
}

// 使用 concatProps 的对象
let obj = {
    name: "Panda",
    age: 16
};

// 使用反柯里化进行转化
let concatProps = uncurring(F.prototype.concatProps);

concatProps(obj, "name", "age"); // Panda&16

Существует еще одно применение антикарринга, которое используется вместо прямого использованияcallа такжеapply, такие как обнаружение типов данныхObject.prototype.toStringи др. В прошлом мы использовали его для вызова непосредственно после этого метода.callИзмените контекст и параметры передачи.Если в проекте есть несколько мест, которые должны проверять разные типы данных, это очень хлопотно.Традиционное решение — инкапсулировать его в модуль, который определяет типы данных.

// 常规方案
function checkType(val) {
    return Object.prototype.toString.call(val);
}

Если вам нужно инкапсулировать много функций таким образом, это будет хлопотно, и количество кода также увеличится.На самом деле, мы также можем использовать другое решение, которое заключается в использовании общей формулы антикаррирования для преобразования эту функцию и использовать возвращаемую функцию как переменную Receive, поэтому нам нужно инкапсулировать только одинuncurringДженерик в порядке.

// 利用反柯里化创建检测数据类型的函数
let checkType = uncurring(Object.prototype.toString);

checkType(1); // [object Number]
checkType("hello"); // [object String]
checkType(true); // [object Boolean]

2. Создавайте антикарринговые функции с помощью вызовов функций

В JavaScript мы часто используем объектно-ориентированное программирование, чтобы установить связь между двумя классами или конструкторами для реализации наследования.Если нам нужно наследование только для того, чтобы надеяться, что экземпляр одного конструктора может использовать методы прототипа другого конструктора, это расточительно проводить громоздкое наследование, а простое наследование отношений между родительским и дочерним классами не так элегантно, лучше, чтобы между ними не было никакой связи.

Function.prototype.uncurring = function () {
    var self = this;
    return function () {
        return Function.prototype.call.apply(self, arguments);
    }
}

Предыдущий вопрос расширяется функцией вышеuncurringМетод полностью решен, как в примере ниже.

// 构造函数
function F() {}

F.prototype.sayHi = function () {
    return "I'm " + this.name + ", " + this.age + " years old.";
}

// 希望 sayHi 方法被任何对象使用
sayHi = F.prototype.sayHi.uncurring();

sayHi({ name: "Panda", age: 20}); // I'm Panda, 20 years old.

существуетFunctionраспространяется на объект-прототипuncurring, сложность в том, чтобы понятьFunction.prototype.call.apply, мы знаем, что вcallв логике исходного кодаthisссылается на функцию, которая ее вызывает, вcallВнутреннее заменяется первым аргументом функцииthis, а остальные выполняют функцию как формальные параметры.

пока вFunction.prototype.call.applyсерединаapplyПервый параметр замененногоcallсерединаthis, это используется для заменыthisназывается в примереuncurringМетодыF.prototype.sayHi, так что это эквивалентноF.prototype.sayHi.call,argumentsПараметры внутри будут переданы вcallв, покаargumentsПервый элемент точно используется для измененияF.prototype.sayHiсерединаthisОбъект.


Суммировать

Видя это, вы должны иметь предварительное представление о каррировании и антикаррировании, но чтобы умело использовать их в разработке, нам нужно более глубоко понять их внутренний смысл.