18 votes

Comment hériter au mieux d'un objet JavaScript natif ? (surtout String)

Je suis un navigateur de longue date, mais c'est la première fois que je participe. Si j'oublie un détail de l'étiquette, faites-le moi savoir !

De plus, j'ai cherché partout, y compris sur ce site, mais je n'ai pas trouvé d'explication claire et succincte de ce que je cherche à faire exactement. Si je l'ai raté, indiquez-moi la bonne direction !

D'accord, je veux étendre certains objets JavaScript natifs, tels que Array et String. Cependant, je ne veux pas vraiment les étendre, mais créer de nouveaux objets qui héritent d'eux, puis les modifier.

Pour Array, ça marche :

var myArray = function (n){
    this.push(n);

    this.a = function (){
        alert(this[0]);
    };
}

myArray.prototype = Array.prototype;

var x = new myArray("foo");
x.a();

Cependant, pour String, la même chose ne fonctionne pas :

var myString = function (n){
    this = n;

    this.a = function (){
        alert(this);
    };
}

myString.prototype = String.prototype;

var x = new myString("foo");
x.a();

J'ai aussi essayé :

myString.prototype = new String();

En faisant des recherches sur le sujet, j'ai découvert que ça marche :

var myString = function (n){
    var s = new String(n);

    s.a = function (){
        alert(this);
    };

    return s;
}

var x = myString("foo");
x.a();

Cependant, j'ai presque l'impression de "tricher". Comme, je debe utiliser le "vrai" modèle d'héritage, et non ce raccourci.

Donc, mes questions :

1) Pouvez-vous me dire ce que je fais de mal en ce qui concerne l'héritage de String ? (De préférence avec un exemple fonctionnel...)

2) Entre l'exemple de l'héritage "réel" et l'exemple du "raccourci", pouvez-vous citer des avantages ou des inconvénients évidents d'une méthode plutôt que de l'autre ? Ou peut-être simplement des différences dans la façon dont l'une fonctionnerait par rapport à l'autre d'un point de vue fonctionnel (parce qu'en fin de compte, elles me paraissent identiques...).

Merci à tous !

EDITAR:

Merci à tous ceux qui ont commenté/répondu. Je pense que les informations de @CMS sont les meilleures car :

1) Il a répondu à mon problème d'héritage des chaînes en indiquant qu'en redéfinissant partiellement une chaîne dans mon propre objet chaîne, je pouvais le faire fonctionner. (par exemple, en surchargeant toString et toValue). 2) La création d'un nouvel objet qui hérite de Array a ses propres limites qui n'étaient pas immédiatement visibles et qui ne peuvent pas être contournées, même en redéfinissant partiellement Array.

À partir de ces deux éléments, je conclus que la revendication d'héritabilité de JavaScript ne s'étend qu'aux objets que vous créez vous-même, et que lorsqu'il s'agit d'objets natifs, tout le modèle s'effondre. (C'est probablement pourquoi 90% des exemples que vous trouvez sont Pet->Dog ou Human->Student, et non String->SuperString). Ce qui pourrait être expliqué par la réponse de @chjj selon laquelle ces objets sont vraiment destinés à être des valeurs primitives, même si tout en JS semble être un objet, et devraient donc être héritables à 100%.

Si cette conclusion est totalement erronée, veuillez me corriger. Et si elle est exacte, alors je suis sûr que ce n'est une nouvelle pour personne d'autre que moi - mais merci encore à tous pour vos commentaires. Je suppose que j'ai maintenant un choix à faire :

Soit aller de l'avant avec l'héritage parasite (mon deuxième exemple dont je connais maintenant le nom) et essayer de réduire son impact sur l'utilisation de la mémoire si possible, ou faire quelque chose comme @davin, @Jeff ou @chjj ont suggéré et soit psudo-redefine ou redéfinir totalement ces objets pour moi-même (ce qui semble un gaspillage).

@CMS - compilez vos informations dans une réponse et je la choisirai.

12voto

chjj Points 5676

La façon douloureusement simple mais imparfaite de procéder serait la suivante :

var MyString = function() {};

MyString.prototype = new String();

Ce que vous demandez est cependant étrange car, normalement, en JS, vous ne les traitez pas comme des objets chaîne, mais comme des types "chaîne", comme des valeurs primitives. De plus, les chaînes ne sont pas du tout mutables. Vous pouvez avoir n'importe quel objet act comme si s'il s'agissait d'une chaîne de caractères en spécifiant un .toString méthode :

var obj = {};
obj.toString = function() {
  return this.value;
};
obj.value = 'hello';

console.log(obj + ' world!');

Mais il est évident qu'il n'y aura pas de méthodes pour les chaînes de caractères. Vous pouvez faire l'héritage de plusieurs façons. L'une d'entre elles est la méthode "originale" que javascript était censé utiliser, et que vous et moi avons postée ci-dessus, ou :

var MyString = function() {};
var fn = function() {};
fn.prototype = String.prototype;
MyString.prototype = new fn();

Cela permet d'ajouter à une chaîne de prototypes sans invoquer un constructeur.

La méthode ES5 serait :

MyString.prototype = Object.create(String.prototype, {
  constructor: { value: MyString }
});

La méthode non standard, mais la plus pratique, est la suivante :

MyString.prototype.__proto__ = String.prototype;

Donc, finalement, ce que vous pourrait faire est la suivante :

var MyString = function(str) {
  this._value = str;
};

// non-standard, this is just an example
MyString.prototype.__proto__ = String.prototype;

MyString.prototype.toString = function() {
  return this._value;
};

Les méthodes de chaîne héritées pourrait travailler en utilisant cette méthode, je ne suis pas sûr. Je pense que c'est possible car il y a une méthode toString. Cela dépend de la façon dont ils sont implémentés en interne par le moteur JS. Mais ce n'est pas forcément le cas. Vous devriez simplement définir votre propre méthode. Encore une fois, ce que vous demandez est très étrange.

Vous pouvez également essayer d'invoquer le constructeur parent directement :

var MyString = function(str) {
  String.call(this, str);
};

MyString.prototype.__proto__ = String.prototype;

Mais c'est aussi un peu vague.

Ce que tu essaies de faire avec ça n'en vaut probablement pas la peine. Je parie qu'il y a une meilleure façon de faire ce pour quoi vous essayez de l'utiliser.

Si vous voulez un absolument moyen fiable de le faire :

// warning, not backwardly compatible with non-ES5 engines

var MyString = function(str) {
  this._value = str;
};

Object.getOwnPropertyNames(String.prototype).forEach(function(key) {
  var func = String.prototype[key];
  MyString.prototype[key] = function() {
    return func.apply(this._value, arguments);
  };
});

Cela fera du curry sur this._value à chaque méthode String. Ce sera intéressant car votre chaîne sera mutable, contrairement aux vraies chaînes javascript.

Tu pourrais faire ça :

    return this._value = func.apply(this._value, arguments);

Ce qui ajouterait une dynamique intéressante. Si vous voulez qu'il renvoie une de vos chaînes au lieu d'une chaîne native :

    return new MyString(func.apply(this._value, arguments));

Ou simplement :

    this._value = func.apply(this._value, arguments);
    return this;

Il y a plusieurs façons de s'y prendre, selon le comportement que vous souhaitez obtenir.

De plus, votre chaîne n'aura pas de longueur ou d'index comme les chaînes javascript, une façon de résoudre ce problème serait de mettre dans le constructeur :

var MyString = function(str) {
  this._value = str;
  this.length = str.length;
  // very rough to instantiate
  for (var i = 0, l = str.length; i < l; i++) {
    this[i] = str[i];
  }
};

C'est très compliqué. Selon l'implémentation, vous pourriez simplement invoquer le constructeur pour ajouter les index et la longueur. Vous pouvez également utiliser un getter pour la longueur si vous voulez utiliser ES5.

Une fois encore, ce que vous voulez faire ici n'est en aucun cas idéal. Ce sera lent et inutile.

5voto

davin Points 19682

Cette ligne n'est pas valide :

this = n;

this n'est pas une valeur lval valide. Autrement dit, vous ne pouvez pas assigner à la valeur référencée par this . Jamais. Ce n'est tout simplement pas du javascript valide. Votre exemple fonctionnera si vous le faites :

var myString = function (n){
    this.prop = n;

    this.a = function (){
        alert(this.prop);
    };
}

myString.prototype = new String; // or String.prototype;

var x = new myString("foo");
x.a();

En ce qui concerne votre solution de contournement, vous devez réaliser que tout ce que vous faites est de créer un objet String, d'augmenter une propriété de fonction, puis de l'appeler. Il y a no l'héritage a lieu.

Par exemple, si vous exécutez x instanceof myString dans mon exemple ci-dessus, il est évalué à true mais dans votre exemple, ce n'est pas le cas, car la fonction myString n'est pas un type, c'est juste une fonction régulière.

1voto

kennebec Points 33886

Vous ne pouvez pas attribuer le ce dans un constructeur

this=n est une erreur

Votre myarray n'est qu'un alias pour le Array natif - toutes les modifications que vous apportez à myarray.prototype sont des modifications à Array.prototype.

1voto

Jeff Points 4795

J'envisagerais de créer un nouvel objet, en utilisant l'objet natif comme champ d'appui et en recréant manuellement les fonctions pour les objets natifs. Pour un exemple de code très grossier et non testé...

var myString = {
    _value = ''
    ,substring:_value.substring
    ,indexOf:_value.indexOf
}

Maintenant, je suis sûr que ça ne marchera pas comme prévu. Mais je pense qu'avec quelques modifications, cela pourrait ressembler à un objet hérité de String.

0voto

Martin Wantke Points 26

La norme ECMAScript6 permet d'hériter directement des fonctions de construction d'un objet natif.

class ExtendedNumber extends Number
{
    constructor(value)
    {
        super(value);
    }

    add(argument)
    {
        return this + argument;
    }
}

var nombre = nouveau ExtendedNumber(2) ;
console.log(number instanceof Number) ; // vrai
console.log(nombre + 1) ; // 4
console.log(nombre.add(3)) ; // 5
console.log(Nombre(1)) ; // 1

Dans ECMAScript5, il est possible d'hériter comme ceci :

function ExtendedNumber(value)
{
    if(!(this instanceof arguments.callee)) { return Number(value); }

    var self = new Number(value);

    self.add = function(argument)
    {
        return self + argument;
    };

    return self;
}

Cependant, les objets générés ne sont pas primitifs :

console.log(number); // ExtendedNumber {[[PrimitiveValue]]: 2}

Mais vous pouvez étendre un type primitif en étendant son prototype réel qui est utilisé :

var str = "some text";
var proto = Object.getPrototypeOf(str);
proto.replaceAll = function(substring, newstring)
{
    return this.replace(new RegExp(substring, 'g'), newstring);
}

console.log(str.replaceAll('e', 'E')); // somE tExt

Un transfert vers une notation orientée classe est un peu délicat :

function ExtendedString(value)
{
    if(this instanceof arguments.callee) { throw new Error("Calling " + arguments.callee.name + " as a constructor function is not allowed."); }
    if(this.constructor != String) { value = value == undefined || value == null || value.constructor != String ? "" : value; arguments.callee.bind(Object.getPrototypeOf(value))(); return value; }

    this.replaceAll = function(substring, newstring)
    {
        return this.replace(new RegExp(substring, 'g'), newstring);
    }
}

console.log(!!ExtendedString("str").replaceAll); // true

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X