web-dev-qa-db-fra.com

JavaScript: cloner une fonction

Quel est le moyen le plus rapide de cloner une fonction en JavaScript (avec ou sans ses propriétés)?

eval(func.toString()) et function() { return func.apply(..) }. Mais je suis inquiet à propos de la performance de eval et l'empaquetage aggravera la pile et risque de dégrader les performances s'il est appliqué beaucoup ou appliqué à déjà emballé.

new Function(args, body) a l'air sympa, mais comment puis-je diviser de manière fiable la fonction existante en arguments et corps sans un analyseur JS dans JS?

Merci d'avance.

Mise à jour: Ce que je veux dire, c'est pouvoir faire

var funcB = funcA.clone(); // where clone() is my extension
funcB.newField = {...};    // without affecting funcA
90
Andrey Shchekin

essaye ça:

var x = function() {
    return 1;
};

var t = function(a,b,c) {
    return a+b+c;
};


Function.prototype.clone = function() {
    var that = this;
    var temp = function temporary() { return that.apply(this, arguments); };
    for(var key in this) {
        if (this.hasOwnProperty(key)) {
            temp[key] = this[key];
        }
    }
    return temp;
};

alert(x === x.clone());
alert(x() === x.clone()());

alert(t === t.clone());
alert(t(1,1,1) === t.clone()(1,1,1));
alert(t.clone()(1,1,1));
45
Jared

Voici une réponse mise à jour

var newFunc = oldFunc.bind({}); //clones the function with '{}' acting as it's new 'this' parameter

Cependant, ".bind" est une fonctionnalité moderne (> = iE9) de JavaScript (avec une solution de contournement de la compatibilité à partir de MDN).

https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind

Remarque: qu'il ne clone pas l'objet de fonction additionnel attaché properties, incluant la propriété prototype. Crédit à @jchook

Remarque: que la nouvelle fonction this est collée à l'argument donné sur bind (), même lors de nouveaux appels function apply (). Crédit à @Kevin

function oldFunc() { console.log(this.msg); }
var newFunc = oldFunc.bind( { msg:"You shall not pass!" } ); // this object is binded
newFunc.apply( { msg:"hello world" } ); //logs "You shall not pass!" instead

Remarque: objet de fonction liée, instanceof traite newFunc/oldFunc de la même manière. Crédit à @Christopher

(new newFunc()) instanceof oldFunc; //gives true
(new oldFunc()) instanceof newFunc; //gives true as well
newFunc == oldFunc;                 //gives false however
83
PicoCreator

Voici une version légèrement meilleure de la réponse de Jared. Celui-ci ne se retrouvera pas avec des fonctions profondément imbriquées, plus vous clonerez. Il appelle toujours l'original.

Function.prototype.clone = function() {
    var cloneObj = this;
    if(this.__isClone) {
      cloneObj = this.__clonedFrom;
    }

    var temp = function() { return cloneObj.apply(this, arguments); };
    for(var key in this) {
        temp[key] = this[key];
    }

    temp.__isClone = true;
    temp.__clonedFrom = cloneObj;

    return temp;
};

De plus, en réponse à la réponse mise à jour donnée par pico.creator, il convient de noter que la fonction bind() ajoutée en Javascript 1.8.5 pose le même problème que la réponse de Jared: elle continuera à imbriquer, ce qui ralentira la création de fonctions à chaque utilisation .

18
Justin Warkentin

Étant curieux mais toujours incapable de trouver la réponse au sujet de performance de la question ci-dessus, j’ai écrit ceci Gist pour nodejs afin de tester à la fois les performances et la fiabilité de toutes les solutions présentées (et analysées).

J'ai comparé les temps de mur d'une création de fonction de clone et l'exécution d'un clone . Les résultats ainsi que les erreurs d'assertion sont inclus dans le commentaire de Gist.

Plus mes deux cents (sur la suggestion de l'auteur):

clone0 cent (plus rapide mais plus laid):

Function.prototype.clone = function() {
  var newfun;
  eval('newfun=' + this.toString());
  for (var key in this)
    newfun[key] = this[key];
  return newfun;
};

clone4 cent (plus lent mais pour ceux qui n'aiment pas eval () à des fins connues uniquement par eux et leurs ancêtres):

Function.prototype.clone = function() {
  var newfun = new Function('return ' + this.toString())();
  for (var key in this)
    newfun[key] = this[key];
  return newfun;
};

En ce qui concerne les performances, si eval/new Function est plus lent que la solution wrapper (et cela dépend vraiment de la taille du corps de la fonction), il vous donne un clone de fonction nue (et je parle du vrai clone peu profond avec des propriétés mais pas de statut partagé) sans fuzz inutile. avec des propriétés cachées, des fonctions d’emballage et des problèmes de pile.

De plus, vous devez toujours prendre en compte un facteur important: moins il y a de code, moins il y a d'erreurs.

L'inconvénient de l'utilisation de la fonction eval/new est que le clone et la fonction d'origine fonctionneront dans différentes portées. Cela ne fonctionnera pas bien avec les fonctions qui utilisent des variables étendues. Les solutions utilisant un wrapping de type bind sont indépendantes de la portée.

8
royaltm

C'était très excitant de faire fonctionner cette méthode, elle crée donc un clone d'une fonction en utilisant l'appel de fonction.

Quelques limitations concernant les fermetures décrites dans Référence de fonction MDN

function cloneFunc( func ) {
  var reFn = /^function\s*([^\s(]*)\s*\(([^)]*)\)[^{]*\{([^]*)\}$/gi
    , s = func.toString().replace(/^\s|\s$/g, '')
    , m = reFn.exec(s);
  if (!m || !m.length) return; 
  var conf = {
      name : m[1] || '',
      args : m[2].replace(/\s+/g,'').split(','),
      body : m[3] || ''
  }
  var clone = Function.prototype.constructor.apply(this, [].concat(conf.args, conf.body));
  return clone;
}

Prendre plaisir.

7
Max Dolgov

Court et simple:

Function.prototype.clone = function() {
  return new Function('return ' + this.toString())();
};
6
micahblu
const oldFunction = params => {
  // do something
};

const clonedFunction = (...args) => oldFunction(...args);
3
zhenyulin

Si vous voulez créer un clone en utilisant le constructeur Function, quelque chose comme ceci devrait fonctionner:

_cloneFunction = function(_function){
    var _arguments, _body, _result;
    var _regexFunction = /^function[\s]+[\w]*\(([\w\s,_\$]*)?\)\{(.*)\}$/;
    var _regexArguments = /((?!=^|,)([\w\$_]))+/g;
    var _matches = _function.toString().match(_regexFunction)
    if(_matches){
        if(_matches[1]){
            _result = _matches[1].match(_regexArguments);
        }else{
            _result = [];
        }
        _result.Push(_matches[2]);
    }else{
        _result = [];
    }
    var _clone = Function.apply(Function, _result);
    // if you want to add attached properties
    for(var _key in _function){
        _clone[_key] = _function[_key];
    }
    return _clone;
}

Un test simple:

(function(){
    var _clone, _functions, _key, _subKey;
    _functions = [
        function(){ return 'anonymous function'; }
        ,function Foo(){ return 'named function'; }
        ,function Bar(){ var a = function(){ return 'function with internal function declaration'; }; return a; }
        ,function Biz(a,boo,c){ return 'function with parameters'; }
    ];
    _functions[0].a = 'a';
    _functions[0].b = 'b';
    _functions[1].b = 'b';
    for(_key in _functions){
        _clone = window._cloneFunction(_functions[_key]);
        console.log(_clone.toString(), _clone);
        console.log('keys:');
        for(_subKey in _clone){
            console.log('\t', _subKey, ': ', _clone[_subKey]);
        }
    }
})()

Ces clones perdront leurs noms et leur portée pour toutes les variables fermées sur.

1
tobymackenzie

Je me demandais simplement - pourquoi voudriez-vous cloner une fonction lorsque vous avez des prototypes ET pouvez définir l'étendue d'un appel de fonction comme bon vous semble

 var funcA = {};
 funcA.data = 'something';
 funcA.changeData = function(d){ this.data = d; }

 var funcB = {};
 funcB.data = 'else';

 funcA.changeData.call(funcB.data);

 alert(funcA.data + ' ' + funcB.data);
1
Upperstage

J'ai importé la réponse de Jared à ma manière:

    Function.prototype.clone = function() {
        var that = this;
        function newThat() {
            return (new that(
                arguments[0],
                arguments[1],
                arguments[2],
                arguments[3],
                arguments[4],
                arguments[5],
                arguments[6],
                arguments[7],
                arguments[8],
                arguments[9]
            ));
        }
        function __clone__() {
            if (this instanceof __clone__) {
                return newThat.apply(null, arguments);
            }
            return that.apply(this, arguments);
        }
        for(var key in this ) {
            if (this.hasOwnProperty(key)) {
                __clone__[key] = this[key];
            }
        }
        return __clone__;
    };

1) maintenant, il prend en charge le clonage des constructeurs (peut appeler avec new); dans ce cas, prend seulement 10 arguments (vous pouvez le modifier) ​​- impossibilité de passer tous les arguments dans le constructeur

2) tout est dans les fermetures correctes

1
max.minin
function cloneFunction(Func, ...args) {
  function newThat(...args2) {
    return new Func(...args2);
  }
  function clone() {
    if (this instanceof clone) {
      return newThat(...args);
    }
    return Func.apply(this, args);
  }
  for (const key in Func) {
    if (Func.hasOwnProperty(key)) {
      clone[key] = Func[key];
    }
  }
  Object.defineProperty(clone, 'name', { value: Func.name, configurable: true })
  return clone
};

function myFunction() {
  console.log('Called Function')
}

myFunction.value = 'something';

const newFunction = cloneFunction(myFunction);

newFunction.another = 'somethingelse';

console.log('Equal? ', newFunction === myFunction);
console.log('Names: ', myFunction.name, newFunction.name);
console.log(myFunction);
console.log(newFunction);
console.log('InstanceOf? ', newFunction instanceof myFunction);

myFunction();
newFunction();

Bien que je ne recommande jamais d’utiliser ceci, j’ai pensé que ce serait un petit défi intéressant de créer un clone plus précis en prenant certaines des pratiques qui semblaient être les meilleures et en les corrigeant un peu. Voici le résultat des journaux:

Equal?  false
Names:  myFunction myFunction
{ [Function: myFunction] value: 'something' }
{ [Function: myFunction] value: 'something', another: 'somethingelse' }
InstanceOf?  false
Called Function
Called Function
0

Cette réponse s’adresse aux personnes qui voient le clonage d’une fonction comme la réponse à leur utilisation souhaitée, mais pour lesquelles beaucoup de en fait n'ont pas besoin de cloner une fonction, car ce qu'elles veulent réellement, c'est simplement pouvoir associer différentes propriétés à même fonction, mais ne déclare cette fonction qu’une fois.

Faites cela en créant une fonction créatrice de fonction:

function createFunction(param1, param2) {
   function doSomething() {
      console.log('in the function!');
   }
   // Assign properties to `doSomething` if desired, perhaps based
   // on the arguments passed into `param1` and `param2`. Or,
   // even return a different function from among a group of them.
   return doSomething;
};

let a = createFunction();
a.something = 1;
let b = createFunction();
b.something = 2; // does not overwrite a.something
console.log(a.something);
a();
b();

Ce n'est pas exactement le même que celui que vous avez décrit, cependant, cela dépend de la façon dont vous voulez utiliser la fonction que vous souhaitez cloner. Cela utilise également plus de mémoire car il crée en réalité plusieurs copies de la fonction, une fois par appel. Cependant, cette technique peut résoudre le cas d'utilisation de certaines personnes sans la nécessité d'une fonction clone compliquée.

0
ErikE