web-dev-qa-db-fra.com

Ajout de propriétés personnalisées à une fonction

La recherche d'une réponse appropriée s'est avérée difficile en raison de l'existence de nombreux autres problèmes liés à mes mots clés. Je vais donc vous poser cette question ici.

Comme nous le savons, les fonctions en javascript sont des objets et ont leurs propres propriétés et méthodes (plus précisément, les fonctions, héritées de Function.prototype).

J'envisageais d'ajouter des propriétés personnalisées pour une fonction (méthode), sautons le "pourquoi?" partie et aller directement au code:

var something = {
    myMethod: function () {
        if (something.myMethod.someProperty === undefined) {
            something.myMethod.someProperty = "test";
        }
        console.log(something.myMethod);
    }
}

Lorsqu'elle est inspectée avec DOM Explorer de Firebug, la propriété est définie comme prévu. Cependant, comme je ne me considère pas comme un expert en javascript, j'ai les questions suivantes:

  1. Cette méthode peut-elle être considérée comme "appropriée" et conforme aux normes? Cela fonctionne dans Firefox mais beaucoup de choses fonctionnent comme prévu dans les navigateurs Web et ne sont en aucun cas des standards.
  2. Est-ce que ce type de modification d'objets en leur ajoutant de nouvelles propriétés est une bonne pratique?
46
Przemek

Il est un peu difficile de donner une réponse très significative à votre question, car vous avez en quelque sorte dit: "Voici ma solution, ça va?" sans expliquer quel problème vous essayez de résoudre (vous avez même dit explicitement que vous n'allez pas expliquer le "pourquoi"). Votre code semble être un code JavaScript valide qui sera exécuté, mais il semble également être une manière moins qu'optimale de faire les choses.

Si vous expliquez ce que vous voulez réellement réaliser, vous obtiendrez peut-être de bonnes suggestions pour améliorer la structure de votre code. Néanmoins, je vais vous donner une sorte de réponse:

Cette méthode peut-elle être considérée comme "appropriée" et conforme aux normes? Cela fonctionne dans Firefox mais beaucoup de choses fonctionnent comme prévu dans les navigateurs Web et ne sont en aucun cas des standards.

Les fonctions sont des objets (comme vous l'avez dit), il est donc possible de leur ajouter des propriétés. Ce n'est pas vraiment un problème de normes en tant que tel en ce qu'il s'agit d'une partie essentielle de JavaScript que tous les navigateurs prennent en charge.

Est-ce que ce type de modification d'objets en leur ajoutant de nouvelles propriétés est une bonne pratique?

C'est votre objet, vous pouvez ajouter les propriétés de votre choix. L'intérêt des objets est qu'ils possèdent des propriétés que vous pouvez manipuler. Je ne peux pas vraiment envisager un moyen d'utiliser des objets qui ne nécessite pas de les modifier, y compris l'ajout, la suppression et la mise à jour de propriétés et de méthodes.

Cela dit, pour moi, il n’a pas de sens d’ajouter des propriétés à la fonction myMethod, il serait plus habituel d’ajouter d’autres propriétés à votre objet something (votre fonction myMethod aurait normalement accès aux autres propriétés si elle était appelée correctement. de something via le mot clé this).

Si vous utilisez une fonction en tant que constructeur, il est généralement logique d’ajouter method au prototype associé et d’ajouter des propriétés (non-méthodes) à chaque instance, mais vous pouvez procéder de l’autre sens. le cas échéant. (Notant qu'une "méthode" est essentiellement une propriété qui référence une fonction.)

Le code spécifique que vous avez affiché n'ajoute pas de propriétés, il vérifie si la propriété somePropertydéjà existe et, le cas échéant, lui attribue une nouvelle valeur.

Vous pourriez bénéficier de la lecture de certains articles tels que ceux-ci chez MDN:

20
nnnnnn

Tout d'abord, il est important de comprendre que les propriétés de fonction standard (arguments, nom, appelant et longueur) ne peuvent pas être écrasées. Donc, oubliez d'ajouter une propriété avec ce nom.

L'ajout de vos propres propriétés personnalisées à une fonction peut être effectué de différentes manières qui devraient fonctionner dans tous les navigateurs.


Ajouter vos propres propriétés personnalisées à une fonction

Méthode 1: ajout de propriétés lors de l'exécution de la fonction: 

var doSomething = function() {
    doSomething.name = 'Tom';
    doSomething.name2 = 'John';
    return 'Beep';
};

console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
console.log('doSomething() : ' + doSomething());
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);

Sortie:

doSomething.name : 
doSomething.name2 : undefined
doSomething() : Beep
doSomething.name : 
doSomething.name2 : John 

Voie 1 (syntaxe alternative):

function doSomething() {
    doSomething.name = 'Tom';
    doSomething.name2 = 'John';
    return 'Beep';
};

console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
console.log('doSomething() : ' + doSomething());
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);

Sortie:

doSomething.name : doSomething
doSomething.name2 : undefined
doSomething() : Beep
doSomething.name : doSomething
doSomething.name2 : John 

Voie 1 (deuxième syntaxe alternative):

var doSomething = function f() {
    f.name = 'Tom';
    f.name2 = 'John';
    return 'Beep';
};

console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
console.log('doSomething() : ' + doSomething());
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);

Sortie:

doSomething.name : f
doSomething.name2 : undefined
doSomething() : Beep
doSomething.name : f
doSomething.name2 : John 

Un problème avec cette stratégie est que vous devez exécuter votre fonction au moins une fois pour affecter les propriétés. Pour de nombreuses fonctions, ce n'est évidemment pas ce que vous voulez. Alors considérons les autres options.


Voie 2: ajout de propriétés après avoir défini la fonction:

function doSomething() {
    return 'Beep';
};

doSomething.name = 'Tom';
doSomething.name2 = 'John';

console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
console.log('doSomething() : ' + doSomething());
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);

Sortie:

doSomething.name : doSomething
doSomething.name2 : John
doSomething() : Beep
doSomething.name : doSomething
doSomething.name2 : John 

Désormais, vous n'avez pas besoin d'exécuter votre fonction avant de pouvoir accéder à vos propriétés. Cependant, un inconvénient est que vos propriétés se sentent déconnectées de votre fonction.


Voie 3: envelopper votre fonction dans la fonction anonyme: 

var doSomething = (function(args) {
    var f = function() {
        return 'Beep';
    };
    for (i in args) {
        f[i] = args[i];
    }
    return f;
}({
    'name': 'Tom',
    'name2': 'John'
}));

console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
console.log('doSomething() : ' + doSomething());
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);

Sortie:

doSomething.name : 
doSomething.name2 : John
doSomething() : Beep
doSomething.name : 
doSomething.name2 : John 

En enveloppant votre fonction dans une fonction anonyme, vous pouvez collecter vos attributs dans un objet et utiliser une boucle pour ajouter ces attributs un par un dans la fonction anonyme. De cette façon, vos attributs se sentent plus liés à votre fonction. Cette technique est également très utile lorsque vos attributs doivent être copiés à partir d’un objet existant. Un inconvénient, cependant, est que vous ne pouvez ajouter que plusieurs attributs en même temps lorsque vous définissez votre fonction. De plus, cela ne donne pas exactement le code DRY] si vous voulez souvent ajouter des propriétés à une fonction.


Manière 4: ajouter une fonction 'extend' à votre fonction, qui ajoute les propriétés d'un objet à lui-même une par une: 

var doSomething = function() {
    return 'Beep';
};

doSomething.extend = function(args) {
    for (i in args) {
        this[i] = args[i];
    }
    return this;
}

doSomething.extend({
    'name': 'Tom',
    'name2': 'John'
});

console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
console.log('doSomething() : ' + doSomething());
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);

Sortie:

doSomething.name : 
doSomething.name2 : John
doSomething() : Beep
doSomething.name : 
doSomething.name2 : John 

De cette façon, vous pouvez étendre plusieurs propriétés et/ou copier des propriétés d'un autre projet à tout moment. Encore une fois, cependant, votre code n’est pas DRY si vous le faites plus souvent.


Voie 5: Créer une fonction générique 'extend':

var extend = function(obj, args) {
    if (Array.isArray(args) || (args !== null && typeof args === 'object')) {
        for (i in args) {
            obj[i] = args[i];
        }
    }
    return obj;
}

var doSomething = extend(
    function() {
        return 'Beep';
    }, {
        'name': 'Tom',
        'name2': 'John'
    }
);

console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
console.log('doSomething() : ' + doSomething());
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);

Sortie:

doSomething.name : 
doSomething.name2 : John
doSomething() : Beep
doSomething.name : 
doSomething.name2 : John 

Une fonction d'extension génétique permet une approche plus DRY, vous permettant d'ajouter l'objet ou tout projet à un autre objet.


Manière 6: Créez un objet extendableFunction et utilisez-le pour attacher une fonction extend à une fonction: 

var extendableFunction = (function() {
    var extend = function(args) {
        if (Array.isArray(args) || (args !== null && typeof args === 'object')) {
            for (i in args) {
                this[i] = args[i];
            }
        }
        return this;
    };
    var ef = function(v, obj) {
        v.extend = extend;
        return v.extend(obj);
    };

    ef.create = function(v, args) {
        return new this(v, args);
    };
    return ef;
})();

var doSomething = extendableFunction.create(
    function() {
        return 'Beep';
    }, {
        'name': 'Tom',
        'name2': 'John'
    }
);

console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
console.log('doSomething() : ' + doSomething());
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);

Sortie:

doSomething.name : 
doSomething.name2 : John
doSomething() : Beep
doSomething.name : 
doSomething.name2 : John 

Plutôt que d'utiliser une fonction générique 'extend', cette technique vous permet de générer des fonctions auxquelles est associée une méthode 'extend'.


Voie 7: Ajouter une fonction 'extend' au prototype de fonction: 

Function.prototype.extend = function(args) {
    if (Array.isArray(args) || (args !== null && typeof args === 'object')) {
        for (i in args) {
            this[i] = args[i];
        }
    }
    return this;
};

var doSomething = function() {
    return 'Beep';
}.extend({
    name : 'Tom',
    name2 : 'John'
});

console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
console.log('doSomething() : ' + doSomething());
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);

Sortie:

doSomething.name : 
doSomething.name2 : John
doSomething() : Beep
doSomething.name : 
doSomething.name2 : John 

Un grand avantage de cette technique réside dans le fait qu’il est très facile d’ajouter de nouvelles propriétés à une fonction et DRY ainsi qu’à une OO complète. En outre, c'est assez convivial mémoire. Un inconvénient, cependant, est que ce n'est pas très à l'épreuve du futur. Au cas où les futurs navigateurs ajouteraient une fonction native 'extend' au prototype Function, cela pourrait endommager votre code.


Manière 8: Exécute une fonction de manière récursive une fois, puis la renvoie: 

var doSomething = (function f(arg1) {
    if(f.name2 === undefined) {
        f.name = 'Tom';
        f.name2 = 'John';
        f.extend = function(args) {
            if (Array.isArray(args) || (args !== null && typeof args === 'object')) {
                for (i in args) {
                    this[i] = args[i];
                }
            }
            return this;
        };
        return f;
    } else {
        return 'Beep';
    }
})();

console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
console.log('doSomething() : ' + doSomething());
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);

Sortie:

doSomething.name : f
doSomething.name2 : John
doSomething() : Beep
doSomething.name : f
doSomething.name2 : John 

Exécutez une fonction une fois et demandez-lui de vérifier si l'une de ses propriétés est définie. S'il n'est pas défini, définissez les propriétés et revenez lui-même. Si défini, exécute la fonction. Si vous incluez une fonction 'extend' parmi les propriétés, vous pourrez l'exécuter ultérieurement pour ajouter de nouvelles propriétés.


Ajouter vos propres propriétés personnalisées à un objet

Malgré toutes ces options, je recommanderais néanmoins de ne pas ajouter de propriétés à une fonction. Il vaut bien mieux ajouter des propriétés aux objets!

Personnellement, je préfère les classes singleton avec la syntaxe suivante.

var keyValueStore = (function() {
    return {
        'data' : {},
        'get' : function(key) { return keyValueStore.data[key]; },
        'set' : function(key, value) { keyValueStore.data[key] = value; },
        'delete' : function(key) { delete keyValueStore.data[key]; },
        'getLength' : function() {
            var l = 0;
            for (p in keyValueStore.data) l++;
            return l;
        }
    }
})();

Un avantage de cette syntaxe est qu’elle autorise les variables publiques et privées. Par exemple, voici comment rendre la variable 'data' privée:

var keyValueStore = (function() {
    var data = {};

    return {
        'get' : function(key) { return data[key]; },
        'set' : function(key, value) { data[key] = value; },
        'delete' : function(key) { delete data[key]; },
        'getLength' : function() {
            var l = 0;
            for (p in data) l++;
            return l;
        }
    }
})();

Mais vous voulez plusieurs instances de banque de données, vous dites? Aucun problème!

var keyValueStore = (function() {
    var count = -1;

    return (function kvs() {
        count++; 
        return {
            'data' : {},
            'create' : function() { return new kvs(); },
            'count' : function() { return count; },
            'get' : function(key) { return this.data[key]; },
            'set' : function(key, value) { this.data[key] = value; },
            'delete' : function(key) { delete this.data[key]; },
            'getLength' : function() {
                var l = 0;
                for (p in this.data) l++;
                return l;
            }
        }
    })();
})();

Enfin, vous pouvez séparer les propriétés instance et singleton et utiliser un prototype pour les méthodes publiques de l'instance. Cela se traduit par la syntaxe suivante:

var keyValueStore = (function() {
    var count = 0; // Singleton private properties

    var kvs = function() {
        count++; // Instance private properties
        this.data = {};  // Instance public properties
    };

    kvs.prototype = { // Instance public properties
        'get' : function(key) { return this.data[key]; },
        'set' : function(key, value) { this.data[key] = value; },
        'delete' : function(key) { delete this.data[key]; },
        'getLength' : function() {
            var l = 0;
            for (p in this.data) l++;
            return l;
        }
    };

    return  { // Singleton public properties
        'create' : function() { return new kvs(); },
        'count' : function() { return count; }
    };
})();

Avec cette syntaxe, vous pouvez avoir:

  • plusieurs instances d'un objet
  • variables privées
  • variables de classe

Vous l'utilisez comme ceci:

kvs = keyValueStore.create();
kvs.set('Tom', "Baker");
kvs.set('Daisy', "Hostess");
var profession_of_daisy = kvs.get('Daisy');
kvs.delete('Daisy');
console.log(keyValueStore.count());
81
John Slegers

Associer des propriétés à des fonctions est un moyen magnifique (plutôt lent/hack-ish) de surcharger l'opérateur (), qui est généralement utilisé pour implémenter foncteurs : Les types d'objets qui ont un travail très important toutes ses autres fonctionnalités (le cas échéant) ne sont que des assistants. Vous pouvez également interpréter ces foncteurs comme, en gros, une fonction "avec état" dans laquelle l'état est public (la plupart des fonctions en ligne, par exemple, ont un état privé, c'est-à-dire un état de la portée locale).

Ce JSFiddle montre comment utiliser une fonction avec des propriétés personnalisées pour une fonction translator avec des utilitaires supplémentaires:

/**
 * Creates a new translator function with some utility methods attached to it.
 */
var createTranslator = function(dict) {
    var translator = function(Word) {
        return dict[Word];
    };

    translator.isWordDefined = function(Word) {
        return dict.hasOwnProperty(Word);
    };

    // Add more utilities to translator here...

    return translator;
};


// create dictionary
var en2deDictionary = {
    'banana': 'Banane',
    'Apple': 'Apfel'
};

// simple use case:
var translator = createTranslator(en2deDictionary);
var pre = $('<pre>');
$("body").append(pre);

pre.append(translator('banana') + '\n');
pre.append(translator('Apple') + '\n');
pre.append(translator.isWordDefined('w00t') + '\n');

Comme vous pouvez le constater, cette fonction est parfaite pour un traducteur dont le seul objectif est de traduire. Bien sûr, il existe de nombreux autres exemples de ces types d’objets, mais ils sont de loin moins communs que les types à fonctionnalité diversifiée, tels que les types classiques User, AnimalCar etc. Pour ces types de types, vous ne voulez ajouter des propriétés personnalisées que dans très peu de cas. Généralement, vous voulez définir ces classes comme des classes plus complètes et avoir leurs propriétés publiques accessibles via this et c'est prototype.

2
Domi

Je me rends compte que j'ai des années de retard, mais je pensais ajouter cet exemple - requirejs définit une propriété appelée "AMD" sur la fonction define (), ce qui est très pratique car le modèle UMD La fonction () qui est dans la portée est en fait une fonction define () d'AMD.

RequireJS source: http://requirejs.org/docs/release/2.1.9/comments/require.js

Motif UMD illustrant cet usage: https://github.com/umdjs/umd/blob/master/amdWeb.js

1
user456176

Je conviens qu’il s’agit d’une question difficile pouvant donner lieu à de multiples réponses, je préfère donc donner un exemple différent:

Supposons avoir une variable JavaScript Array, peuplée par un générateur:

var arr = [...new Array(10).keys()];

c'est

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Maintenant, nous voulons mapper cela dans un nouveau tableau - même longueur, en appliquant une fonction, afin de pouvoir utiliser la propriété native de la fonction map:

arr = arr.map((value,index) => ++value)

Nous venons de faire un value=value+1 et un retour, donc maintenant le tableau ressemblera à

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Ok, maintenant supposé avoir une Object JavaScript comme

var obj=new Object()

cela a été défini comme le tableau précédent (pour une raison folle):

arr.forEach((value,index) => obj[value]=value)

c'est à dire.

{0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9}

À ce stade, nous ne pouvons pas appliquer la même méthode map car elle n'est pas définie pour une Object. Nous devons donc la définir en tant que nouvelle prototype d'une Object:

Object.defineProperty(Object.prototype, 'mapObject', {
      value: function(f, ctx) {
          ctx = ctx || this;
          var self = this, result = {};
          Object.keys(self).forEach(function(k) {
              result[k] = f.call(ctx, self[k], k, self);
          });
          return result;
      }
    });

À ce stade, nous pourrions faire comme pour le tableau avant:

obj=obj.mapObject((value,key) => ++value )

de sorte que nous avons:

{0: 1, 1: 2, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}

Vous pouvez voir que nous avons mis à jour les valeurs uniquement:

[...Object.keys(obj)]
["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]

et nous pouvons retourner dans le tableau de sortie:

[...Object.keys(obj).map(k=>obj[k])]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Ici c'est au travail:

// Array.map
var arr = [...new Array(10).keys()];
console.log("array", arr)
arr = arr.map((value, index) => ++value)
console.log("mapped array", arr)
// new property
Object.defineProperty(Object.prototype, 'mapObject', {
  value: function(f, ctx) {
    ctx = ctx || this;
    var self = this,
      result = {};
    Object.keys(self).forEach(function(k) {
      result[k] = f.call(ctx, self[k], k, self);
    });
    return result;
  }
});

// Object.mapObject
var obj = new Object()
arr = [...new Array(10).keys()];
arr.forEach((value, index) => obj[value] = value)
console.log("object", obj)
obj = obj.mapObject((value, key) => ++value)
console.log("mapped object", obj)
console.log("object keys", [...Object.keys(obj)])
console.log("object values", [...Object.keys(obj).map(k => obj[k])])

0
loretoparisi

Il est parfaitement acceptable d'ajouter des propriétés ou des méthodes à un objet fonction. C'est fait assez souvent. L'objet jQuery/$ en est un exemple. C'est une fonction avec pas mal de méthodes attachées.

Lorsque des propriétés sont ajoutées à un constructeur, elles sont appelées propriétés 'statiques' et peuvent être invoquées sans instance de la classe. par exemple. Object.create.

Je n'ai pas assez de représentants pour écrire un commentaire, je vais donc dire ceci: Il est généralement considéré comme une mauvaise pratique d'étendre les prototypes d'objets intégrés, surtout si votre code doit jouer avec le code d'autres personnes. Cela peut avoir des conséquences imprévisibles et difficiles à suivre.

0
AdamW

Si vous souhaitez simplement ajouter des propriétés personnalisées à une fonction, il vous suffit d'ajouter ces propriétés à Function.prototype. Par exemple:

Function.prototype.SomeNewProperty = function () {//Do something awesome here...}
0
Nitij

Ajout possible à John Slegers grande réponse

N'est-il pas possible qu'après John Slegers:

Voie 2: ajouter des propriétés après avoir défini la fonction

Ajout d'un Way 2.5

function doSomething() {
    doSomething.prop = "Bundy";
    doSomething.doSomethingElse = function() {
        alert("Why Hello There! ;)");

    };

    let num = 3;
    while(num > 0) {
        alert(num);
        num--;  
    }
}

sayHi();
sayHi.doSomethingElse();
alert(doSomething.prop);

var ref = doSomething;

ref();
ref.doSomethingElse();
alert(ref.prop);

Mettre à la fois une propriété "variable" et une propriété de fonction par souci de complétude, directement dans la déclaration de fonction. Évitant ainsi de le "déconnecter". A laissé les rouages ​​internes par défaut de la fonction (une simple boucle) pour montrer que cela fonctionne toujours. Non?

0
brat