1. #1
    Membre averti

    Homme Profil pro
    Développeur Web
    Inscrit en
    octobre 2007
    Messages
    614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : octobre 2007
    Messages : 614
    Points : 388
    Points
    388

    Par défaut Tout sur les objets vierges, en JavaScript

    Bonjour à tous,

    Je voudrais vous faire part de quelques-unes de mes best-practices en JS, qui vont me faire faire une nouvelle version de chacun de mes outils publiés.

    Férus de JS? Ne partez pas trop vite tout de même, bien que ce que je vais énoncer ici coule de source, je suis certain que vous n'en utilisez pas autant que vous le devriez et que vous pensez trop rarement au cas où vous en rencontreriez un fourni par un script tiers.


    Qu'est-ce qu'un objet vierge et comment en crée-t-on un?

    Un objet vierge est un objet n'ayant aucune propriété, ni méthode, hormis __proto__, standardisé avec l'ES6.


    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    var object;  
     
    object = Object.create(null);

    Le prototype d'un objet vierge et l'opérateur instanceof

    Un objet vierge ainsi créé a null, pour tout prototype :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    var object;  
     
    object = Object.create(null);  
     
    console.log(Object.getPrototypeOf(object)); // null
    Un objet vierge n'est pas une instance d'Object :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    var object;  
     
    object = Object.create(null);  
     
    console.log(object instanceof Object); // false

    L'usage de la méthode Object.prototype.hasOwnProperty().

    Un objet, en JS, dès lors qu'il est créé sur base du prototype d'Object, hérite de tout un tas de méthodes.

    En plus de ces méthodes natives, de nombreux scripts étendent les prototypes au moyen de polyfills.

    Ces propriétés vous ont déjà certainement, un peu gêné lorsque vous vouliez itérer sur les propriétés et/ou méthodes non-héritées, devant tester si c'est une propriété qui lui est propre :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    var object,  
        properties,  
        iterator,  
        length;  
     
    object = {};  
    object.property = 'value';  
    properties = Object.getOwnPropertyNames(object);  
    iterator = 0;  
    length = properties.length;  
     
    for (;iterator < length;iterator += 1) {  
        if (object.hasOwnProperty(property)) {  
            // propriété propre à l'objet  
        }  
    }
    Sachant qu'un script tiers pourrait très bien vous fournir des objets vierges, ce code planterait votre script lamentablement :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    var object,  
        properties,  
        iterator,  
        length;  
     
    object = Object.create(null);  
    object.property = 'value';  
    properties = Object.getOwnPropertyNames(object);  
    iterator = 0;  
    length = properties.length;  
     
    for (;iterator < length;iterator += 1) {  
        if (object.hasOwnProperty(properties[iterator])) {  
            // crash  
        }  
    }
    Tester l'existence de la méthode hasOwnProperty est une solution bancale, en effet :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    var object,  
        properties,  
        iterator,  
        length;  
     
    object = {};  
    object.property = 'value';  
    properties = Object.getOwnPropertyNames(object);  
    iterator = 0;  
    length = properties.length;  
     
    for (;iterator < length;iterator += 1) {  
        if (object.hasOwnProperty) {  
            if (object.hasOwnProperty(properties[iterator])) {  
                // propriété propre à l'objet  
            }  
        }  
    }
    Une solution sûre est tout de même possible avec cette même méthode...

    Dans cet exemple, j'utiliserai une fonction s'appelant demethodize, c'est une méthode empruntée à SylvainPV, servant à raccourcir l'usage d'une méthode, sur une instance, tout en optimisant les performances de son appel :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    var object,  
        properties,  
        iterator,  
        length,  
        demethodize,  
        hasOwnProperty,  
        property;  
     
    object = {};  
    object.property = 'value';  
    properties = Object.getOwnPropertyNames(object);  
    iterator = 0;  
    length = properties.length;  
    demethodize = Function.bind.bind(Function.call);  
    hasOwnProperty = demethodize(Object.prototype.hasOwnProperty);  
     
    for (;iterator < length;iterator += 1) {  
        if (hasOwnProperty(object, properties[iterator])) {  
            // propriété propre à l'objet  
        }  
    }

    Mais pourquoi, utiliserait-on des objets vierges, s'ils peuvent être si problématiques?

    En réalité, la question est posée à l'envers, les objets problématiques, ce sont les objets classiques!

    Certains d'entre vous, nombreux, je l'espère... utilisent des closures pour encapsuler leur code, afin de ne pourrir l'espace global et peut-être aussi pour que de petits plaisantins ou de mauvais scripts n'aillent pas modifier des valeurs des propriétés et variables de leurs scripts.

    Cela suffit-il? assurément, non!

    En effet, toute modification d'Object.prototype va altérer chacune des instances, encapsulées ou non, il peut donc être très aisé de modifier le comportement de vos objets.

    Avec un objet classique :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var object;  
     
    object = {};  
    object.property = 'value';  
     
    Object.prototype.hack = function() { 
      this.property = 'modified'; 
    }; 
     
    object.hack(); 
    console.log(object.property); // 'modified'
    Avec un objet vierge :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var object;  
     
    object = Object.create(null);  
    object.property = 'value';  
     
     
    Object.prototype.hack = function() { 
      this.property = 'modified'; 
    }; 
     
    object.hack(); // TypeError: object.hack is not a function 
    console.log(object.property); // 'value'

    Les objets vierges et l'héritage

    Si aucune modification d'Object ne peut altérer vos objets, l'héritage n'en est pas moins exploitable :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    var object,  
        clone1,  
        clone2;  
     
    object = Object.create(null);  
    clone1 = Object.create(object);  
    clone2 = Object.create(clone1);  
     
    Object.getPrototypeOf(clone1).property = 'value';  
     
    console.log({  
        object: object,  
        clone1: clone1,  
        clone2: clone2  
    });  
    /*  
    {  
        object: {  
            property: 'value'  
        },  
        clone1: {  
            property: 'value'  
        },  
        clone2: {  
            property: 'value'  
        }  
    }  
    */
    Récupération du parent :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var demethodize,  
        getPrototypeOf,  
        object,  
        clone1;  
     
    demethodize = Function.bind.bind(Function.call);  
    getPrototypeOf = demethodize(Object.getPrototypeOf, null);  
     
    object = Object.create(null);  
    clone1 = Object.create(object);  
     
    console.log(getPrototypeOf(clone1) === object); // true
    Test d'héritage :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    var getPrototypeOf,  
        isCloneOf,  
        object,  
        clone1,  
        clone2;  
     
    getPrototypeOf = Object.getPrototypeOf;  
     
    isCloneOf = function (clone, original) {  
        var parent;  
     
        if (typeof clone !== 'object') {  
            return false;  
        }  
     
        while (parent !== null) {  
            parent = getPrototypeOf(parent || clone);  
     
            if (parent === original) {  
                return true;  
            }  
        }  
     
        return false;  
    };  
     
    object = Object.create(null);  
    clone1 = Object.create(object);  
    clone2 = Object.create(clone1);  
     
    console.log(isCloneOf(clone2, object)); // true

    Méfiez-vous du JSON

    En effet, les objets qu'il vous fournit héritent tous d'Object, ils ne sont donc pas sûrs.

    Un petit hack s'impose donc :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    var parseJSON;  
     
    parseJSON = (function () {  
        var create,  
            getOwnPropertyNames,  
            parse,  
            reparse,  
            reparseArray,  
            reparseObject;  
     
        parse = JSON.parse;  
        create = Object.create;  
        getOwnPropertyNames = Object.getOwnPropertyNames;  
     
        reparse = function (value) {  
            if (typeof value !== 'object') {  
                return value;  
            }  
     
            if (value instanceof Array) {  
                return reparseArray(value);  
            }  
     
            return reparseObject(value);  
        };  
     
        reparseArray = function (array) {  
            var iterator,  
                length;  
     
            iterator = 0;  
            length = array.length;  
     
            for (;iterator < length;iterator += 1) {  
                array[iterator] = reparse(array[iterator]);  
            }  
     
            return array;  
        };  
     
        reparseObject = function (object) {  
            var virgin,  
                properties,  
                iterator,  
                length,  
                name;  
     
            virgin = create(null);  
            names = getOwnPropertyNames(object);  
            length = names.length;  
     
            for (;iterator < length;iterator += 1) {  
                name = names[iterator];  
     
                virgin[name] = object[name];  
            }  
     
            return virgin;  
        };  
     
        return function (data, reviver) {  
            return reparse(JSON.parse(data, typeof reviver === 'function'  
                ? function (key, value) {  
                    return reparse(reviver(key, value));  
                }  
                : reviver  
            );  
        };  
    }());

    Conclusion

    Les objets vierges sont une solution, combinée aux closures (voire d'un sandboxing), pour renforcer la fiabilité du comportement des objets créés par vos scripts.

    Je vous conseille de aliaser/deméthodiser le plus tôt possible les méthodes nécessaires d'Object, afin d'être sûr de leurs valeur de retour.

    Alors, oui, certains objets que vous traiterez n'en seront pas forcément mais sachant que l'on peut créer des objets vierges ou non, si un script crée des objets qui devaient hériter d'Object, à mon sens, nous n'avons d'autre choix que de considérer que son concepteur a sciemment choisi que ses objets devaient posséder les méthodes du prototype.


    hasOwnProperty or not?
    Afin d'obtenir plus facilement de l'aide, n'hésitez pas à poster votre code de carte bancaire

    Mon GitHub

  2. #2
    Membre éprouvé
    Avatar de Gecko
    Homme Profil pro
    Développeur décisionnel
    Inscrit en
    décembre 2008
    Messages
    498
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France, Nord (Nord Pas de Calais)

    Informations professionnelles :
    Activité : Développeur décisionnel

    Informations forums :
    Inscription : décembre 2008
    Messages : 498
    Points : 1 263
    Points
    1 263

    Par défaut

    Comme d'hab échanger sur JS et tout ce qui gravite autour avec toi est vraiment kiffant
    Code php : Sélectionner tout - Visualiser dans une fenêtre à part
    if ($toBe || !$toBe) echo 'That is the question';

    Mes projets: DVP I/O

  3. #3
    Membre averti

    Homme Profil pro
    Développeur Web
    Inscrit en
    octobre 2007
    Messages
    614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : octobre 2007
    Messages : 614
    Points : 388
    Points
    388

    Par défaut

    Merci...
    Afin d'obtenir plus facilement de l'aide, n'hésitez pas à poster votre code de carte bancaire

    Mon GitHub

  4. #4
    Invité
    Invité(e)

    Par défaut

    C'est vraiment un super taff et je pense que ça va aider beaucoup de dev.

  5. #5
    Expert confirmé Avatar de Watilin
    Homme Profil pro
    En recherche d'emploi
    Inscrit en
    juin 2010
    Messages
    2 453
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 29
    Localisation : France, Ille et Vilaine (Bretagne)

    Informations professionnelles :
    Activité : En recherche d'emploi

    Informations forums :
    Inscription : juin 2010
    Messages : 2 453
    Points : 4 942
    Points
    4 942

    Par défaut

    Les concepts sont intéressants et bien présentés, et sont certainement utiles dans le contexte JS serveur. Mais côté navigateur, je crois vraiment que c'est une surcharge de travail superflue (l'exemple de JSON est frappant), car la sécurité ne se gère pas côté client. Et quand je parle de travail, je veux dire aussi bien le travail du développeur que celui du processeur.

    Voilà le bémol que je rajouterais à ton article.

    En dehors de ça, je reconnais l'utilité, ou même simplement la beauté, de chercher à faire du script fool-proof. On se souvient de l'échec de la librairie Prototype.js, dont le cœur de fonctionnement était d’étendre le prototype des objets natifs (y compris du DOM).
    La FAQ JavaScript – Les cours JavaScript
    Un article du MDN n’a pas de version française ? Je peux peut-être le traduire, envoyez-moi un MP

    La touche F12 : l’outil indispensable à tout développeur JavaScript !

  6. #6
    Rédacteur/Modérateur

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

    Informations forums :
    Inscription : novembre 2012
    Messages : 3 197
    Points : 9 305
    Points
    9 305

    Par défaut

    Sympa l'article

    Un truc que je ne pense pas avoir vu mentionné, c'est qu'en utilisant des objets vierges, on peut tester la présence d'une propriété avec l'opérateur in plutôt que de devoir passer par hasOwnProperty

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
    var dictionnary = Object.create(null);
     
    var object = {};
     
    console.log("hasOwnProperty" in object) // true
    console.log(object.hasOwnProperty("hasOwnProperty")) // false
    console.log("hasOwnProperty" in dictionnary) // false
    One Web to rule them all

  7. #7
    Membre averti

    Homme Profil pro
    Développeur Web
    Inscrit en
    octobre 2007
    Messages
    614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : octobre 2007
    Messages : 614
    Points : 388
    Points
    388

    Par défaut

    Citation Envoyé par Watilin Voir le message
    Mais côté navigateur, je crois vraiment que c'est une surcharge de travail superflue (l'exemple de JSON est frappant), car la sécurité ne se gère pas côté client.
    En effet, il ne s'agit certainement pas de sécurité côté client... néanmoins, je voulais mettre en garde contre des crashs éventuels dans l'exécution d'un script, lorsqu'on utilise instance.hasOwnProperty(), beaucoup trop souvent utilisé.

    De plus, toujours côté client, la plupart des développeurs (et ça a été longtemps mon cas aussi), pensent à tort, que d'enfermer son code dans une closure le rend inaltérable, se rassurant donc sur le comportement de celui-ci...

    Pour le reste, merci beaucoup.
    Afin d'obtenir plus facilement de l'aide, n'hésitez pas à poster votre code de carte bancaire

    Mon GitHub

  8. #8
    Membre averti

    Homme Profil pro
    Développeur Web
    Inscrit en
    octobre 2007
    Messages
    614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : octobre 2007
    Messages : 614
    Points : 388
    Points
    388

    Par défaut

    Citation Envoyé par SylvainPV Voir le message
    Sympa l'article

    Un truc que je ne pense pas avoir vu mentionné, c'est qu'en utilisant des objets vierges, on peut tester la présence d'une propriété avec l'opérateur in plutôt que de devoir passer par hasOwnProperty
    C'est volontairement que j'ai retiré cette partie car cela ne change pas d'un objet classique, on peut tout de même vouloir savoir s'il s'agit d'une propriété héritée ou non.
    Afin d'obtenir plus facilement de l'aide, n'hésitez pas à poster votre code de carte bancaire

    Mon GitHub

  9. #9
    Rédacteur/Modérateur

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

    Informations forums :
    Inscription : novembre 2012
    Messages : 3 197
    Points : 9 305
    Points
    9 305

    Par défaut

    Je croyais que la définition d'un objet vierge était qu'il n'héritait de rien Comment pourrait-il avoir des propriétés héritées ?
    One Web to rule them all

  10. #10
    Membre averti

    Homme Profil pro
    Développeur Web
    Inscrit en
    octobre 2007
    Messages
    614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : octobre 2007
    Messages : 614
    Points : 388
    Points
    388

    Par défaut

    Un null object, oui... perso, c'est que j'entends par objet vierge, c'est plutôt un objet n'étendant pas Object, n'héritant donc pas de toutes ses propriétés et méthodes.

    En revanche, selon ce que j'entends par un objet vierge, un tel objet peut très peut très bien hériter d'un autre objet vierge... d'ailleurs, Object.prototype en est un.

    C'était un peu confus, je te l'accorde...
    Afin d'obtenir plus facilement de l'aide, n'hésitez pas à poster votre code de carte bancaire

    Mon GitHub

Discussions similaires

  1. Réponses: 1
    Dernier message: 07/07/2008, 09h00
  2. Réponses: 3
    Dernier message: 22/12/2005, 00h40
  3. question sur les objets
    Par afrikha dans le forum Langage
    Réponses: 14
    Dernier message: 07/12/2005, 15h21
  4. Réponses: 5
    Dernier message: 24/04/2005, 04h09
  5. question de débutant sur les objets
    Par boucher_emilie dans le forum ASP
    Réponses: 3
    Dernier message: 06/08/2004, 10h51

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