web-dev-qa-db-fra.com

Quelles techniques peuvent être utilisées pour définir une classe en JavaScript et quels sont leurs compromis?

Je préfère utiliser OOP dans des projets à grande échelle comme celui sur lequel je travaille actuellement. Je dois créer plusieurs classes en JavaScript mais, si je ne me trompe pas, il existe au moins deux façons de le faire. Quelle serait la syntaxe et pourquoi cela serait-il fait?

J'aimerais éviter d'utiliser des bibliothèques tierces - du moins au début.
À la recherche d’autres réponses, j’ai trouvé l’article Programmation orientée objet avec JavaScript, première partie: héritage - Doc JavaScript qui traite de la programmation orientée objet en JavaScript. Y a-t-il une meilleure façon de faire l'héritage? 

674
Karim

Voici le moyen de le faire sans utiliser de bibliothèques externes:

// Define a class like this
function Person(name, gender){

   // Add object properties like this
   this.name = name;
   this.gender = gender;
}

// Add methods like this.  All Person objects will be able to invoke this
Person.prototype.speak = function(){
    alert("Howdy, my name is" + this.name);
};

// Instantiate new objects with 'new'
var person = new Person("Bob", "M");

// Invoke methods like this
person.speak(); // alerts "Howdy, my name is Bob"

Maintenant, la vraie réponse est bien plus complexe que cela. Par exemple, les classes en JavaScript n'existent pas. JavaScript utilise un schéma d'héritage basé sur prototype

En outre, de nombreuses bibliothèques JavaScript populaires possèdent leur propre style d'approximation des fonctionnalités de type classe en JavaScript. Vous voudrez vérifier au moins Prototype et jQuery

Décider lequel de ces choix est le "meilleur" est un excellent moyen de commencer une guerre sainte sur Stack Overflow. Si vous vous lancez dans un projet plus volumineux utilisant beaucoup de JavaScript, cela vaut vraiment la peine d'apprendre une bibliothèque populaire et de le faire à sa manière. Je suis un gars de Prototype, mais Stack Overflow semble pencher vers jQuery.

Dans la mesure où il n’existe qu’une "façon de le faire", sans aucune dépendance vis-à-vis des bibliothèques externes, j’ai écrit à peu près tout. 

730
Triptych

La meilleure façon de définir une classe en JavaScript est de ne pas définir de classe.

Sérieusement.

Il existe plusieurs types d’orientation objet, dont les suivants: 

  • OO basé sur les classes (introduit pour la première fois par Smalltalk)
  • OO basé sur un prototype (introduit pour la première fois par Self)
  • OO basé sur plusieurs méthodes (introduit pour la première fois par CommonLoops, je pense)
  • OO basé sur les prédicats (aucune idée)

Et probablement d'autres que je ne connais pas.

JavaScript implémente une OO basée sur un prototype. Dans les objets OO basés sur des prototypes, les nouveaux objets sont créés en copiant d'autres objets (au lieu d'être instanciés à partir d'un modèle de classe) et les méthodes vivent directement dans des objets plutôt que dans des classes. L'héritage se fait par délégation: si un objet n'a pas de méthode ou de propriété, il est recherché sur son (ses) prototype (s) (c'est-à-dire l'objet à partir duquel il a été cloné), puis les prototypes du prototype, etc.

En d'autres termes: il n'y a pas de cours.

JavaScript a en fait un joli tweak de ce modèle: constructors. Vous pouvez non seulement créer des objets en copiant ceux qui existent, vous pouvez également les construire "à partir de rien", pour ainsi dire. Si vous appelez une fonction avec le mot clé new, cette fonction devient un constructeur et le mot clé this ne pointe pas sur l'objet actuel, mais sur un objet "vide" créé. Vous pouvez donc configurer un objet comme bon vous semble. De cette manière, les constructeurs JavaScript peuvent assumer l’un des rôles de classes dans l’objet d’objet métier basé sur des classes traditionnel: servir de modèle ou de modèle pour les nouveaux objets.

Maintenant, JavaScript est un langage très puissant, il est donc assez facile de mettre en œuvre un système OO basé sur les classes dans JavaScript si vous le souhaitez. Cependant, vous ne devriez le faire que si vous en avez vraiment besoin et pas seulement parce que c'est ainsi que Java le fait.

212
Jörg W Mittag

Classes ES2015

Dans la spécification ES2015, vous pouvez utiliser la syntaxe de classe, qui n'est qu'un sucre sur le système prototype.

class Person {
  constructor(name) {
    this.name = name;
  }
  toString() {
    return `My name is ${ this.name }.`;
  }
}

class Employee extends Person {
  constructor(name, hours) {
    super(name);
    this.hours = hours;
  }
  toString() {
    return `${ super.toString() } I work ${ this.hours } hours.`;
  }
}

Avantages

Le principal avantage est que les outils d'analyse statiques trouvent plus facile de cibler cette syntaxe. Il est également plus facile pour d'autres personnes issues de langues basées sur des classes d'utiliser cette langue comme un polyglotte. 

Mises en garde

Méfiez-vous de ses limites actuelles. Pour obtenir des propriétés privées, il faut recourir à en utilisant des symboles ou des WeakMaps . Dans les prochaines versions, les classes seront probablement développées pour inclure ces fonctionnalités manquantes.

Soutien

Le support du navigateur n’est pas très bon pour le moment (supporté par presque tout le monde sauf IE), mais vous pouvez maintenant utiliser ces fonctionnalités avec un transpiler comme Babel .

Ressources

78
Dale

Je préfère utiliser Daniel/Moore{SUPER: SYSTEM}. C'est une discipline qui offre des avantages tels que les variables d'instance vraies, l'héritage basé sur les traits, les hiérarchies de classes et les options de configuration. L'exemple ci-dessous illustre l'utilisation de variables d'instance vraies, ce qui constitue, selon moi, le plus gros avantage. Si vous n'avez pas besoin de variables d'instance et que vous ne vous contentez que de variables publiques ou privées, il existe probablement des systèmes plus simples.

function Person(I) {
  I = I || {};

  Object.reverseMerge(I, {
    name: "McLovin",
    age: 25,
    homeState: "Hawaii"
  });

  return {
    introduce: function() {
      return "Hi I'm " + I.name + " and I'm " + I.age;
    }
  };
}

var fogel = Person({
  age: "old enough"
});
fogel.introduce(); // "Hi I'm McLovin and I'm old enough"

Wow, ce n'est pas vraiment très utile en soi, mais jetez un œil à l'ajout d'une sous-classe:

function Ninja(I) {
  I = I || {};

  Object.reverseMerge(I, {
    belt: "black"
  });

  // Ninja is a subclass of person
  return Object.extend(Person(I), {
    greetChallenger: function() {
      return "In all my " + I.age + " years as a ninja, I've never met a challenger as worthy as you...";
    }
  });
}

var resig = Ninja({name: "John Resig"});

resig.introduce(); // "Hi I'm John Resig and I'm 25"

Un autre avantage est la possibilité d'avoir un héritage basé sur des modules et des traits.

// The Bindable module
function Bindable() {

  var eventCallbacks = {};

  return {
    bind: function(event, callback) {
      eventCallbacks[event] = eventCallbacks[event] || [];

      eventCallbacks[event].Push(callback);
    },

    trigger: function(event) {
      var callbacks = eventCallbacks[event];

      if(callbacks && callbacks.length) {
        var self = this;
        callbacks.forEach(function(callback) {
          callback(self);
        });
      }
    },
  };
}

Un exemple d'avoir la classe de personne incluent le module bindable.

function Person(I) {
  I = I || {};

  Object.reverseMerge(I, {
    name: "McLovin",
    age: 25,
    homeState: "Hawaii"
  });

  var self = {
    introduce: function() {
      return "Hi I'm " + I.name + " and I'm " + I.age;
    }
  };

  // Including the Bindable module
  Object.extend(self, Bindable());

  return self;
}

var person = Person();
person.bind("eat", function() {
  alert(person.introduce() + " and I'm eating!");
});

person.trigger("eat"); // Blasts the alert!

Divulgation: Je suis Daniel X. Moore et voici mon{SUPER: SYSTEM}. C'est le meilleur moyen de définir une classe en JavaScript.

56
Daniel X Moore
var Animal = function(options) {
    var name = options.name;
    var animal = {};

    animal.getName = function() {
        return name;
    };

    var somePrivateMethod = function() {

    };

    return animal;
};

// usage
var cat = Animal({name: 'tiger'});
41
liammclennan

Voici les moyens de créer des objets en javascript, que j'ai déjà utilisé

Exemple 1:

obj = new Object();
obj.name = 'test';
obj.sayHello = function() {
    console.log('Hello '+ this.name);
}

Exemple 2:

obj = {};
obj.name = 'test';
obj.sayHello = function() {
    console.log('Hello '+ this.name);
}
obj.sayHello();

Exemple 3:

var obj = function(nameParam) {
    this.name = nameParam;
}
obj.prototype.sayHello = function() {
    console.log('Hello '+ this.name);
}

Exemple 4: Avantages réels de Object.create (). veuillez vous référer à [ce lien]

var Obj = {
    init: function(nameParam) {
        this.name = nameParam;
    },
    sayHello: function() {
        console.log('Hello '+ this.name);
    }
};
var usrObj = Object.create(Obj);  // <== one level of inheritance

usrObj.init('Bob');
usrObj.sayHello();

Exemple 5 (Object.create de Crockford personnalisé):

Object.build = function(o) {
   var initArgs = Array.prototype.slice.call(arguments,1)
   function F() {
      if((typeof o.init === 'function') && initArgs.length) {
         o.init.apply(this,initArgs)
      }
   }
   F.prototype = o
   return new F()
}
MY_GLOBAL = {i: 1, nextId: function(){return this.i++}}  // For example

var userB = {
    init: function(nameParam) {
        this.id = MY_GLOBAL.nextId();
        this.name = nameParam;
    },
    sayHello: function() {
        console.log('Hello '+ this.name);
    }
};
var bob = Object.build(userB, 'Bob');  // Different from your code
bob.sayHello();


Pour maintenir la réponse à jour avec ES6/ES2015

Une classe est définie comme ceci:

class Person {
    constructor(strName, numAge) {
        this.name = strName;
        this.age = numAge;
    }

    toString() {
        return '((Class::Person) named ' + this.name + ' & of age ' + this.age + ')';
    }
}

let objPerson = new Person("Bob",33);
console.log(objPerson.toString());
29
Amol M Kulkarni

Je pense que vous devriez lire/ Héritage prototypal en JavaScript et Héritage classique en JavaScript .

Exemples de sa page:

Function.prototype.method = function (name, func) {
    this.prototype[name] = func;
    return this;
};

Effet? Cela vous permettra d'ajouter des méthodes de manière plus élégante:

function Parenizor(value) {
    this.setValue(value);
}

Parenizor.method('setValue', function (value) {
    this.value = value;
    return this;
});

Je recommande également ses vidéos: JavaScript avancé .

Vous pouvez trouver plus de vidéos sur sa page: http://javascript.crockford.com/ /__. Dans le livre de John Reisig, vous trouverez de nombreux exemples sur le site Web de Douglas Crockfor.

24
Jarek

Parce que je n'admettrai pas le plan d'usine YUI/Crockford et que j'aime garder les choses autonomes et extensibles, voici ma variante:

function Person(params)
{
  this.name = params.name || defaultnamevalue;
  this.role = params.role || defaultrolevalue;

  if(typeof(this.speak)=='undefined') //guarantees one time prototyping
  {
    Person.prototype.speak = function() {/* do whatever */};
  }
}

var Robert = new Person({name:'Bob'});

où, idéalement, le test typeof porte sur quelque chose comme la première méthode prototypée

16
annakata

Si vous optez pour simple, vous pouvez éviter le "nouveau" mot-clé entièrement et simplement utiliser les méthodes d'usine. Je préfère cela parfois, parce que j'aime utiliser JSON pour créer des objets.

function getSomeObj(var1, var2){
  var obj = {
     instancevar1: var1,
     instancevar2: var2,
     someMethod: function(param)
     {  
          //stuff; 
     }
  };
  return obj;
}

var myobj = getSomeObj("var1", "var2");
myobj.someMethod("bla");

Je ne suis toutefois pas certain de l'impact négatif sur les performances des gros objets.

15
Sam
var Student = (function () {
    function Student(firstname, lastname) {
        this.firstname = firstname;
        this.lastname = lastname;
        this.fullname = firstname + " " + lastname;
    }

    Student.prototype.sayMyName = function () {
        return this.fullname;
    };

    return Student;
}());

var user = new Student("Jane", "User");
var user_fullname = user.sayMyName();

C'est la façon dont TypeScript compile une classe avec un constructeur en JavaScript.

12
Mick

Le moyen simple est:

function Foo(a) {
  var that=this;

  function privateMethod() { .. }

  // public methods
  that.add = function(b) {
    return a + b;
  };
  that.avg = function(b) {
    return that.add(b) / 2; // calling another public method
  };
}

var x = new Foo(10);
alert(x.add(2)); // 12
alert(x.avg(20)); // 15

La raison de that est que this peut être lié à autre chose si vous donnez une méthode en tant que gestionnaire d'événements. Vous devez donc enregistrer la valeur lors de l'instanciation et l'utiliser ultérieurement.

Edit: ce n'est certainement pas le meilleur moyen, mais un moyen simple. J'attends de bonnes réponses aussi!

10
orip

Vous voulez probablement créer un type en utilisant le modèle pliant:

    // Here is the constructor section.
    var myType = function () {
        var N = {}, // Enclosed (private) members are here.
            X = this; // Exposed (public) members are here.

        (function ENCLOSED_FIELDS() {
            N.toggle = false;
            N.text = '';
        }());

        (function EXPOSED_FIELDS() {
            X.count = 0;
            X.numbers = [1, 2, 3];
        }());

        // The properties below have access to the enclosed fields.
        // Careful with functions exposed within the closure of the
        // constructor, each new instance will have it's own copy.
        (function EXPOSED_PROPERTIES_WITHIN_CONSTRUCTOR() {
            Object.defineProperty(X, 'toggle', {
                get: function () {
                    var before = N.toggle;
                    N.toggle = !N.toggle;
                    return before;
                }
            });

            Object.defineProperty(X, 'text', {
                get: function () {
                    return N.text;
                },
                set: function (value) {
                    N.text = value;
                }
            });
        }());
    };

    // Here is the prototype section.
    (function PROTOTYPE() {
        var P = myType.prototype;

        (function EXPOSED_PROPERTIES_WITHIN_PROTOTYPE() {
            Object.defineProperty(P, 'numberLength', {
                get: function () {
                    return this.numbers.length;
                }
            });
        }());

        (function EXPOSED_METHODS() {
            P.incrementNumbersByCount = function () {
                var i;
                for (i = 0; i < this.numbers.length; i++) {
                    this.numbers[i] += this.count;
                }
            };
            P.Tweak = function () {
                if (this.toggle) {
                    this.count++;
                }
                this.text = 'tweaked';
            };
        }());
    }());

Ce code vous donnera un type appelé myType. Il aura des champs privés internes appelés toggle et text. Il aura également les membres exposés suivants: les champs count et numbers; les propriétés toggle, text et numberLength; les méthodes incrementNumbersByCount et Tweak.

Le modèle de pliage est entièrement détaillé ici: Modèle de pliage Javascript

9
intrepidis

Code golf pour @ liammclennan's répondre .

var Animal = function (args) {
  return {
    name: args.name,

    getName: function () {
      return this.name; // member access
    },

    callGetName: function () {
      return this.getName(); // method call
    }
  };
};

var cat = Animal({ name: 'tiger' });
console.log(cat.callGetName());

3
tponthieux

MooTools (Mes outils orientés objet) est centré sur l’idée de classes . Vous pouvez même étendre et implémenter avec héritage. 

Une fois maîtrisé, il crée un javascript puissant et ridiculement réutilisable.

2
Ryan Florence

Classes basées sur les objets avec héritage

var baseObject = 
{
     // Replication / Constructor function
     new : function(){
         return Object.create(this);   
     },

    aProperty : null,
    aMethod : function(param){
      alert("Heres your " + param + "!");
    },
}


newObject = baseObject.new();
newObject.aProperty = "Hello";

anotherObject = Object.create(baseObject); 
anotherObject.aProperty = "There";

console.log(newObject.aProperty) // "Hello"
console.log(anotherObject.aProperty) // "There"
console.log(baseObject.aProperty) // null

Simple, adorable, et c'est fini.

2
Ulad Kasach

Une base

function Base(kind) {
    this.kind = kind;
}

Une classe

// Shared var
var _greeting;

(function _init() {
    Class.prototype = new Base();
    Class.prototype.constructor = Class;
    Class.prototype.log = function() { _log.apply(this, arguments); }
    _greeting = "Good afternoon!";
})();

function Class(name, kind) {
    Base.call(this, kind);
    this.name = name;
}

// Shared function
function _log() {
    console.log(_greeting + " Me name is " + this.name + " and I'm a " + this.kind);
}

Action

var c = new Class("Joe", "Object");
c.log(); // "Good afternoon! Me name is Joe and I'm a Object"
1

Basé sur l'exemple de Triptych, cela pourrait même être plus simple:

    // Define a class and instantiate it
    var ThePerson = new function Person(name, gender) {
        // Add class data members
        this.name = name;
        this.gender = gender;
        // Add class methods
        this.hello = function () { alert('Hello, this is ' + this.name); }
    }("Bob", "M"); // this instantiates the 'new' object

    // Use the object
    ThePerson.hello(); // alerts "Hello, this is Bob"

Cela crée uniquement une instance d'objet unique, mais reste utile si vous souhaitez encapsuler un tas de noms pour des variables et des méthodes dans une classe. Normalement, il n'y aurait pas d'argument "Bob, M" au constructeur, par exemple si les méthodes étaient des appels à un système avec ses propres données, telles qu'une base de données ou un réseau.

Je suis encore trop nouveau avec JS pour voir pourquoi cela n'utilise pas la chose prototype.

1
Roland

//new way using this and new
function Persons(name) {
  this.name = name;
  this.greeting = function() {
    alert('Hi! I\'m ' + this.name + '.');
  };
}

var gee=new Persons("gee");
gee.greeting();

var gray=new Persons("gray");
gray.greeting();

//old way
function createPerson(name){
 var obj={};
 obj.name=name;
 obj.greeting = function(){
 console.log("hello I am"+obj.name);
 }; 
  return obj;
}

var gita=createPerson('Gita');
gita.greeting();

0
Avinash Maurya

JavaScript est orienté objet , mais il est radicalement différent des autres OOP langages comme Java, C # ou C++. N'essayez pas de le comprendre comme ça. Jetez cette vieille connaissance et recommencez. JavaScript nécessite une réflexion différente.

Je suggérerais d’obtenir un bon manuel ou quelque chose à ce sujet. J'ai moi-même trouvé Tutoriels ExtJS le meilleur pour moi, bien que je n'aie pas utilisé le framework avant ou après l'avoir lu. Mais cela donne une bonne explication sur ce qui est quoi dans le monde JavaScript. Désolé, il semble que ce contenu ait été supprimé. Voici un lien vers archive.org copie à la place. Fonctionne aujourd'hui. : P

0
Vilx-