Explication détaillée du currying de la fonction JS

La première fois que j'ai vu le mot currying, je lisais un blog sur les algorithmes qui mentionnait les fonctions de currying. A cette époque, je sentais que ce mot était très haut de gamme. En fait, quand on le comprend, on se rend compte que c'est en fait, il s'agit d'une utilisation spéciale de fonctions d'ordre supérieur.

Comme prévu, quelle que soit la fonction, elle doit avoir un nom haut de gamme pour être utile.

Tout d’abord, regardons ce qu’est exactement le curry ?

Wikipédia dit : Currying, anglais : Currying (vraiment plein de sentiment de déjà vu dans la traduction anglaise), consiste à transformer une fonction qui accepte plusieurs paramètres en une fonction qui accepte un seul paramètre (le premier paramètre de la fonction d'origine) et renvoie une fonction. nouvelle fonction qui accepte les paramètres restants et renvoie le résultat.

Voyant que cette explication est un peu abstraite, prenons la fonction add, qui a été démontrée d'innombrables fois, pour réaliser une implémentation simple.

 

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

// Currying后
function curryingAdd(x) {
    return function (y) {
        return x + y
    }
}

add(1, 2)           // 3
curryingAdd(1)(2)   // 3

En fait, les deux paramètres x et y de la fonction add sont modifiés en utilisant une fonction pour recevoir x puis en renvoyant une fonction pour traiter le paramètre y. Maintenant, l'idée devrait être plus claire, c'est-à-dire appeler la fonction en passant seulement une partie des paramètres et la laisser renvoyer une fonction pour traiter les paramètres restants.

Mais la question se pose : à quoi sert de déployer autant d’efforts pour l’encapsuler ? Il est impossible pour nous, programmeurs, de faire plus de choses sans en tirer profit, même dans cette vie.

Listons les bienfaits du Currying ?

1. Réutilisation des paramètres

 

// 正常正则验证字符串 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

L'exemple ci-dessus est une vérification régulière. Normalement, il suffit d'appeler directement la fonction check. Cependant, si je dois vérifier s'il y a des nombres à plusieurs endroits, je dois en fait réutiliser le premier paramètre reg, afin que vous puissiez appeler directement hasNumber, hasLetter et d'autres fonctions, afin que les paramètres puissent être réutilisés et que l'appel soit plus pratique.

2. Confirmez à l'avance

 

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);
            }
        };
    }
})();

//换一种写法可能比较好理解一点,上面就是把isSupport这个参数给先确定下来了
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);
    }
}

Lorsque nous travaillons sur des projets, il est très courant d'encapsuler certaines opérations DOM. La première façon d'écrire ci-dessus est également relativement courante, mais jetons un coup d'oeil à la deuxième façon d'écrire. Elle est relativement différente. La première façon d'écrire est de s'auto-exécuter puis de renvoyer une nouvelle fonction, cela détermine en fait quelle méthode sera utilisée à l'avance pour éviter de porter des jugements à chaque fois.

3. Fonctionnement retardé

 

Function.prototype.bind = function (context) {
    var _this = this
    var args = Array.prototype.slice.call(arguments, 1)
 
    return function() {
        return _this.apply(context, args)
    }
}

Comme bind, qui est souvent utilisé dans nos js, le mécanisme d'implémentation est le Currying.

Après avoir parlé de ces avantages, j'ai découvert qu'il y avait toujours un problème : dois-je modifier la fonction sous-jacente à chaque fois que j'utilise Currying ?

Existe-t-il une méthode d'emballage générale ?

 

// 初步封装
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)
    }
}

Voici d'abord l'encapsulation préliminaire, en enregistrant les paramètres préliminaires via la fermeture, puis en obtenant les arguments restants pour l'épissage et enfin en exécutant la fonction qui nécessite le curry.

Mais il semble y avoir un défaut. Si vous revenez comme ceci, vous ne pouvez développer qu'un paramètre supplémentaire. Currying(a)(b)(c) ne semble pas être pris en charge (les appels multi-paramètres ne sont pas pris en charge). En général, cela Ce genre de situation viendra à l'esprit. Utilisez la récursivité pour encapsuler une autre couche.

 

// 支持多参数传递
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);
    }
}

Il s'agit en fait d'une base préliminaire, avec l'ajout d'appels récursifs : tant que le nombre de paramètres est inférieur à la fn.length initiale, la récursion continuera à s'exécuter.

Maintenant que nous avons fini de parler des bienfaits et que les méthodes générales sont disponibles, concentrons-nous sur les performances du curry.

Il vous suffit de connaître les quatre points suivants concernant certains problèmes de performances du curry :

  • L'accès aux objets arguments est généralement plus lent que l'accès aux paramètres nommés.
  • Certains navigateurs plus anciens sont assez lents à implémenter arguments.length
  • Utiliser fn.apply(…) et fn.call(…) est généralement légèrement plus lent que d'appeler directement fn(…).
  • La création de nombreuses portées et fermetures imbriquées entraîne une dépense, à la fois en termes de mémoire et de vitesse.

En fait, dans la plupart des applications, le principal goulot d'étranglement des performances réside dans l'exploitation des nœuds DOM.La perte de performances de js est fondamentalement négligeable, donc curry peut être utilisé directement et en toute sécurité.

Enfin, développez une question d’entretien classique

 

// 实现一个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





 

Je suppose que tu aimes

Origine blog.csdn.net/ljy_1024/article/details/114011654
conseillé
Classement