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





objet prototype (16)


Je préfère utiliser {SUPER: SYSTEM} Daniel X. Moore. C'est une discipline qui offre des avantages tels que les vraies variables d'instance, l'héritage basé sur les caractères, les hiérarchies de classes et les options de configuration. L'exemple ci-dessous illustre l'utilisation de vraies variables d'instance, ce qui, selon moi, est le plus grand avantage. Si vous n'avez pas besoin de variables d'instance et êtes content avec seulement des variables publiques ou privées, il y a 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 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 des modules et des héritages basés sur les 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 personne inclut 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 {SUPER: SYSTEM} mon {SUPER: SYSTEM} . C'est le meilleur moyen de définir une classe en JavaScript.

Je préfère utiliser OOP dans des projets à grande échelle comme celui sur lequel je travaille en ce moment. J'ai besoin de créer plusieurs classes en JavaScript mais, si je ne me trompe pas, il y a au moins quelques façons de le faire. Quelle serait la syntaxe et pourquoi cela serait-il fait de cette façon?

Je voudrais é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, Partie I: Inheritance - 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?




Voici la façon 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 beaucoup plus complexe que cela. Par exemple, il n'y a pas de classes en JavaScript. JavaScript utilise un schéma d'héritage basé sur un prototype .

En outre, il existe de nombreuses bibliothèques JavaScript populaires qui ont leur propre style d'approximation des fonctionnalités de type classe en JavaScript. Vous aurez envie de vérifier au moins Prototype et jQuery .

Décider lequel d'entre eux est le «meilleur» est une excellente façon de commencer une guerre sainte sur . Si vous vous lancez dans un projet JavaScript plus volumineux, il vaut vraiment la peine d'apprendre une bibliothèque populaire et de le faire à sa manière. Je suis un prototype, mais semble s'appuyer sur jQuery.

Dans la mesure où il n'y a que «une seule façon de le faire», sans aucune dépendance aux bibliothèques externes, la façon dont j'ai écrit est à peu près tout.




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'});



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 est toujours utile si vous souhaitez encapsuler un groupe de noms pour variable et méthodes dans une classe. Normalement, il n'y aurait pas les arguments "Bob, M" pour le constructeur, par exemple si les méthodes seraient des appels à un système avec ses propres données, comme une base de données ou un réseau.

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




Vous voulez probablement créer un type en utilisant le motif 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 aussi ces membres exposés: les champs comptent et les nombres ; les propriétés toggle , text et numberLength ; les méthodes incrementNumbersByCount et tweak .

Le motif pliant est entièrement détaillé ici: Javascript Folding Pattern




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 comme ça que TypeScript compile la classe avec le constructeur en JavaScript.




JavaScript est object-oriented , mais il est radicalement différent des autres OOP comme Java, C # ou C ++. N'essayez pas de le comprendre comme ça. Jetez cette vieille connaissance et recommencez à zéro. JavaScript a besoin d'une réflexion différente.

Je suggère d'obtenir un bon manuel ou quelque chose sur le sujet. J'ai moi-même trouvé les tutoriels ExtJS les meilleurs pour moi, même si je n'ai 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 a été supprimé. Voici un lien vers archive.org à la place. Fonctionne aujourd'hui. : P




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 en est que this peut être lié à autre chose si vous donnez une méthode en tant que gestionnaire d'événement, donc vous enregistrez la valeur pendant l'instanciation et l'utilisez plus tard.

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




ES2015 Classes

Dans la spécification ES2015, vous pouvez utiliser la syntaxe de classe qui est juste 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 statique trouvent plus facile de cibler cette syntaxe. Il est également plus facile pour les autres utilisateurs de langues basées sur les classes d'utiliser la langue comme polyglotte.

Avertissements

Méfiez-vous de ses limites actuelles. Pour obtenir des propriétés privées, il faut utiliser Symbols ou WeakMaps . Dans les prochaines versions, les classes seront probablement étendues 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 utiliser ces fonctionnalités maintenant avec un transpiler comme Babel .

Ressources




Parce que je ne vais pas admettre le plan d'usine YUI / Crockford et parce que j'aime garder les choses autonomes et extensibles c'est 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 est sur quelque chose comme la première méthode prototypée




Classes basées sur des 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, doux, et c'est fait.




Code golf pour la answer @ liammclennan.

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());




Si vous allez simple, vous pouvez éviter complètement le "nouveau" mot-clé et simplement utiliser des 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 pas sûr de ce que la performance est atteinte pour les gros objets, cependant.




Je pense que vous devriez lire l' héritage prototypique de Douglas Crockford en JavaScript et l'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 pouvez trouver de nombreux exemples sur le site de Douglas Crockfor.




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"



Keep it simple en code JavaScript simple:

function qs(key) {
    var vars = [], hash;
    var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
    for(var i = 0; i < hashes.length; i++)
    {
        hash = hashes[i].split('=');
        vars.push(hash[0]);
        vars[hash[0]] = hash[1];
    }
    return vars[key];
}

Appelez-le de n'importe où dans le code JavaScript:

var result = qs('someKey');






javascript oop class