IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

JavaScript Discussion :

Fonctions, prototypes, bizarreries


Sujet :

JavaScript

  1. #1
    Expert éminent
    Avatar de Watilin
    Homme Profil pro
    En recherche d'emploi
    Inscrit en
    Juin 2010
    Messages
    3 094
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France, Ille et Vilaine (Bretagne)

    Informations professionnelles :
    Activité : En recherche d'emploi

    Informations forums :
    Inscription : Juin 2010
    Messages : 3 094
    Points : 6 755
    Points
    6 755
    Par défaut Fonctions, prototypes, bizarreries
    Bonsoir toutes et tous,

    Voici des observations que j’ai tirées de mes propres tests, en lien avec la conversation l’opérateur new, bonne ou mauvaise pratique ?
    J’ai estimé que c’était suffisamment hors-sujet pour mériter l’ouverture d’un nouveau fil.

    Ces observations mettent en lumière un certain nombre de curiosités et d’incohérences du langage.

    Quand on crée une fonction avec le mot-clé function (même sans intention d’utiliser cette fonction comme un constructeur), cette fonction reçoit une propriété prototype. Cette propriété est un objet, héritant de Object.prototype, et possédant une propriété propre constructor qui référence la fonction qu’on vient de créer.

    Pour lever toute ambiguïté, je rappelle que la propriété prototype de la fonction n’est pas le prototype de cette fonction ; il s’agit en réalité du prototype des objets qui sont créés par cette fonction.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    function f() {}
    var o = new f();
    console.log(f.prototype === Object.getPrototypeOf(o)); // true
    Le prototype des fonctions, Function.prototype, n’a lui-même pas de propriété prototype. À force d’y réfléchir ça a fini par me coûter cher en aspirine me paraître logique si on admet l’explication suivante : les fonctions reçoivent leur propriété prototype au moment où elles sont construites. Le mot important ici est « construites », ce qui signifie qu’elles ont un constructeur, et donc que, d’une certaine façon, les fonctions sont des objets.
    D’autres observations qui vont dans ce sens :
    • Les fonctions sont passées par référence, contairement aux types primitifs Number, Boolean, String, qui sont passés par copie.
    • instanceof marche avec les fonctions, alors que, par exemple, "" instanceof String renvoie false, ce qui est contre-intuitif.
    • Les types primitifs sont « encapsulables » :
      Code : Sélectionner tout - Visualiser dans une fenêtre à part
      1
      2
      3
      4
      5
      var s = "abc";
      console.log(typeof s); // "string"
       
      var objS = new String("abc"); // chaîne encapsulée
      console.log(typeof objS); // "object"
      … Mais les fonctions, elles, ne sont pas encapsulables. (Pour la simple raison que leur constructeur ne fait pas d’encapsulation. Quand on passe une fonction à Function, elle est convertie en chaîne et traitée comme une fonction-instruction dans le corps de la nouvelle fonction, ce qui donne lieu à une situation côcasse : on est obligé de nommer la fonction qu’on passe, et on reçoit en retour une fonction anonyme.)

    (Note : n’essayer pas d’encapsuler des valeurs chez vous les enfants, c’est extrêmement dangereux inutile et je décline toute responsabilité.)

    Par contre les fonctions ont leur propre valeur pour typeof, contrairement à Array par exemple, ce qui penche plutôt en faveur de « les fonctions sont un type primitif ».

    J’ai tenté d’instancier un objet à partir du prototype des fonctions, j’ai obtenu une erreur peu commune :
    TypeError: Function.prototype is not a constructor
    Il y a un seul autre cas où on peut obtenir cette erreur : quand on tente de créer un objet avec une fonction-flèche, () => {}. (Les fonctions-flèches sont apparues avec ECMAScript 2015.)

    Quand on spécialise les fonctions, elles perdent leur caractère invocable.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    function SubFunction () {}
    SubFunction.prototype = function () {};
     
    var sf = new SubFunction();
    sf(); // TypeError: sf is not a function
    Même résultat avec Object.create :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    var sf2 = Object.create(function () {});
    sf2(); // TypeError: sf2 is not a function
    C’est un peu dommage quand on y pense, car ce serait intéressant de pouvoir invoquer n’importe quoi comme une fonction. Ça ajouterait encore plus de bordel souplesse au langage. PHP, par exemple, permet de faire ça en ajoutant une « méthode magique » __invoke à une définition de classe. JS pourrait faire ça en proposant, par exemple, l’ajout du symbole connu Symbol.invoke, ou en ajoutant une trappe invoke sur les proxys.


    Voilà, c’est un peu décousu, désolé. En tout cas n’hésitez pas si vous avez des remarques à faire, des réflexions à partager ou des questions à poser

  2. #2
    Rédacteur

    Avatar de danielhagnoul
    Homme Profil pro
    Étudiant perpétuel
    Inscrit en
    Février 2009
    Messages
    6 389
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 74
    Localisation : Belgique

    Informations professionnelles :
    Activité : Étudiant perpétuel
    Secteur : Enseignement

    Informations forums :
    Inscription : Février 2009
    Messages : 6 389
    Points : 22 933
    Points
    22 933
    Billets dans le blog
    125
    Par défaut


    En JavaScript, chaque fonction est un objet Function : https://developer.mozilla.org/fr/doc...obaux/Function

  3. #3
    Rédacteur/Modérateur

    Avatar de SylvainPV
    Profil pro
    Inscrit en
    Novembre 2012
    Messages
    3 375
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2012
    Messages : 3 375
    Points : 9 944
    Points
    9 944
    Par défaut
    Pour expliquer ces bizarreries, il faut mentionner que l'ajout de l'opérateur new et des constructeurs s'est fait au dernier moment dans la conception du langage Mocha (le PoC de Brendan Eich fait en dix jours qui deviendra JavaScript par la suite). Netscape a fait pression pour que la syntaxe "ressemble davantage" à Java, ce qui a amené à ces modifications qui dénotent par rapport à la simplicité et l'élégance d'autres aspects du langage. Le changement de contexte "this", les propriétés prototype et constructor ajoutés à toutes les fonctions, tout ça fait très brouillon.

    A noter que les arrow functions d'ES2015 ont bien rectifié le tir: elles n'ont pas de propriété "prototype" et ne peuvent pas être invoquées comme constructeur avec "new". De plus, elles n'ont pas de contexte attaché, c'est à dire que si on utilise le mot-clé "this" dans le code d'une arrow function, il fera référence au contexte du scope parent. Un bon nettoyage de printemps qui fait du bien, utilisez les arrow functions sans modération !

    Pour ta dernière remarque sur le fait de pouvoir invoquer n'importe quoi comme fonction, la spec des Proxy ne le permet pas, car cela impliquerait que l'objet change de nature (par définition, un objet peut-être invoqué si et seulement si c'est une fonction) ; cf : https://esdiscuss.org/topic/why-prox...-function-only

  4. #4
    Membre éclairé
    Femme Profil pro
    Autre
    Inscrit en
    Janvier 2017
    Messages
    335
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Nord (Nord Pas de Calais)

    Informations professionnelles :
    Activité : Autre

    Informations forums :
    Inscription : Janvier 2017
    Messages : 335
    Points : 715
    Points
    715
    Par défaut
    Bonjour,
    Citation Envoyé par Watilin Voir le message
    Par contre les fonctions ont leur propre valeur pour typeof, contrairement à Array par exemple, ce qui penche plutôt en faveur de « les fonctions sont un type primitif ».
    typeof n'est pas tout à fait en phase avec la réalité des types :
    https://developer.mozilla.org/fr/doc...pes_de_données

    Voir également cette page : https://blog.lesieur.name/les-types-...r-tout-savoir/
    En particulier le paragraphe : "Function et Null sont dans un bâteau"

    Quelques tests :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    function tutu(){}
    console.log(typeof tutu); //function
    console.log(typeof Math); //object
    console.log(tutu instanceof Object); //true
    console.log(Object.getPrototypeOf(tutu)===Function.prototype); //true
    console.log(Object.getPrototypeOf(Function.prototype)===Object.prototype); //true
    console.log(Object.getPrototypeOf(Object.getPrototypeOf(tutu))===Object.prototype); //true
    Citation Envoyé par danielhagnoul Voir le message
    En JavaScript, chaque fonction est un objet Function : https://developer.mozilla.org/fr/doc...obaux/Function
    Effectivement, et on peut noter les paramètres requis pour le constructeur, je crois bien que je n'y avais jamais fait attention.

  5. #5
    Expert éminent
    Avatar de Watilin
    Homme Profil pro
    En recherche d'emploi
    Inscrit en
    Juin 2010
    Messages
    3 094
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France, Ille et Vilaine (Bretagne)

    Informations professionnelles :
    Activité : En recherche d'emploi

    Informations forums :
    Inscription : Juin 2010
    Messages : 3 094
    Points : 6 755
    Points
    6 755
    Par défaut
    Ok, alors c’est officiel, les fonctions sont des objets, mais des objets avec une capacité spéciale : on peut les invoquer. Et comme il est indiqué dans le lien que Sylvain a donné, cette capacité « d’invocabilité » est étroitement liée avec le fait que les fonctions ont une valeur typeof prévues spécialement pour elles.

    Pour être honnête, j’ai été un peu retors en utilisant typeof dans mon argumentation. En réalité je me sers rarement de cet opérateur, et ce n’est même pas parce qu’il est mal foutu : simplement, j’ai rarement besoin de détecter le type de mes valeurs dans les applications que je développe.

    Mais si ça ne tenait qu’à moi, je changerais typeof pour le rendre un peu plus utile. Déjà, je corrigerais l’énormité de typeof null qui renvoie "object" ; et il aurait aussi des valeurs spécifiques pour les autres constructeurs natifs du langage, en particulier Array et RegExp qui ont une syntaxe littérale.

    Bien sûr tout ça n’arrivera pas, car trop d’ancien code sur le Web repose sur le comportement actuel de typeof, et changer ce comportement serait casser une partie du Web, une partie dont la taille est difficile à mesurer, mais sans doute très grande.

    À propos de constructeurs natifs, Date a une place toute spéciale dans mon cœur, et je sais que ça a déjà été discuté au moins une fois sur ce forum : il se comporte tantôt comme un constructeur, tantôt comme une fonction, selon qu’on utilise new ou pas. Je ne vais pas rentrer dans les détails mais essayez vous-mêmes, vous allez voir c’est merveilleux.

    Comme il est conseillé dans tous les guides JavaScript de tous les Internets, il faut bien entendu écrire des constructeurs qui renvoient une valeur différente quand ils sont invoqués sans new. D’ailleurs je pense que c’est pour ça que l’opérateur… Non, la propriété… Enfin, le « truc » new.target a été inventé. Ce petit bijou est une merveille d’élégance syntaxique. (Pour celles et ceux qui ont un doute, ce paragraphe entier est sarcastique )

    Je suis également tenté de donner une valeur typeof pour les constructeurs non natifs (je pense notamment à ceux du DOM). Quelque chose comme "NonNativeObject" pourraît s’avérer utile. Ça nous assure déjà que ce n’est pas null, et nous permet d’enchaîner derrière avec instanceof ou isPrototypeOf, ou des moyens fournis par le DOM lui-même (là je pense par exemple à la propriété node.nodeType, qui est un des moyens de déterminer si un nœud est une instance de Element ou de Text, entre autres).

    @Loralina, le lien que tu donnes présente aussi, de manière très intéressante, la différence entre un type primitif et un objet :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    var five = 5;
    five.three = 3;
    console.log(five + five.three); // devinez
    On peut attacher des propriétés sur une fonction, ce qui ajoute encore une preuve que ce sont des objets.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    function uneFonction() {}
    uneFonction.uneProp = "une valeur";
    console.log(uneFonction.uneProp);
    Une autre chose que l’article de Bruno Lesieur m’a fait réaliser, c’est ce qui se passe quand on écrit :
    Le nombre 42 étant un primitif, il n’a pas de méthode .toFixed — à vrai dire il n’a aucune méthode, ni aucune propriété, car ce n’est pas un objet. Mais alors, comment l’instruction ci-dessus peut-elle retourner 42.00 ?

    C’est parce que le nombre est converti en une valeur intermédiaire new Number(42), une valeur encapsulée, qui est donc de type objet et qui a les méthodes de Number.prototype.

    Cela répond à une question que je m’étais posée, « pourquoi diable peut-on encapsuler des valeurs, à quoi cela sert-il à part à être un des pièges idiots du langage ? »
    La réponse est : « c’est le mécanisme qui permet aux types primitifs d’avoir des méthodes, et qui permet aux développeuses et développeurs d’enrichir les prototypes de ces mêmes types primitifs, même si c’est déconseillé par ailleurs. »

  6. #6
    Membre expert
    Homme Profil pro
    Inscrit en
    Octobre 2011
    Messages
    2 886
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Octobre 2011
    Messages : 2 886
    Points : 3 725
    Points
    3 725
    Par défaut
    Salut,

    Oui moi aussi ça m'avait paru un peu étrange que les fonctions soient des objets à part entière et ça a des avantages...

    J'avais vu justement qu'on peut leur ajouter des propriétés comme tous les objets et ces propriétés sont dites "statiques" je crois...

    D'ailleurs dans les class ES6 on utilise le mot "static"...

  7. #7
    Rédacteur/Modérateur

    Avatar de SylvainPV
    Profil pro
    Inscrit en
    Novembre 2012
    Messages
    3 375
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2012
    Messages : 3 375
    Points : 9 944
    Points
    9 944
    Par défaut
    Pour l'anecdote, il est possible d'invoquer des méthodes directement sur un nombre sans l'usage de parenthèses, mais pour différencier de l'écriture décimale, il faut doubler le "." comme ceci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    42..toFixed(2) // ou 42.0.toFixed(2)
    à ne pas confondre avec l'opérateur spread et rest. D'ailleurs on peut utiliser les deux ensembles:


Discussions similaires

  1. Namespace et fonctions : prototypes et déclaration
    Par Kaluza dans le forum Débuter
    Réponses: 2
    Dernier message: 10/04/2011, 01h26
  2. [Prototype] Fonction => Prototype
    Par buxbux dans le forum Bibliothèques & Frameworks
    Réponses: 4
    Dernier message: 26/03/2009, 18h16
  3. [Prototype] [POO] Fonction + prototype
    Par crickeur dans le forum Bibliothèques & Frameworks
    Réponses: 1
    Dernier message: 14/06/2008, 15h27
  4. pb appel de fonction prototypée
    Par taka10 dans le forum Général JavaScript
    Réponses: 1
    Dernier message: 08/06/2006, 08h39
  5. Récupérer le prototype d'une fonction
    Par uaz dans le forum Général Python
    Réponses: 2
    Dernier message: 27/07/2004, 17h24

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo