Quais técnicas podem ser usadas para definir uma class em JavaScript e quais são as suas compensações?

Eu prefiro usar OOP em projetos de grande escala como o que estou trabalhando agora. Eu preciso criar várias classs em JavaScript, mas, se não me engano, há pelo menos algumas maneiras de fazer isso. Qual seria a syntax e por que isso seria feito dessa maneira?

Eu gostaria de evitar o uso de bibliotecas de terceiros – pelo menos no começo.
Procurando por outras respostas, encontrei o artigo Programação Orientada a Objetos com JavaScript, Parte I: Herança – Doc JavaScript que discute programação orientada a objects em JavaScript. Existe uma maneira melhor de fazer inheritance?

Aqui está a maneira de fazer isso sem usar nenhuma biblioteca externa:

// 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" 

Agora a resposta real é muito mais complexa do que isso. Por exemplo, não existem classs em JavaScript. JavaScript usa um esquema de inheritance baseado em prototype .

Além disso, existem inúmeras bibliotecas JavaScript populares que têm seu próprio estilo de aproximação da funcionalidade de class em JavaScript. Você vai querer verificar pelo menos Prototype e jQuery .

Decidir qual deles é o “melhor” é uma ótima maneira de começar uma guerra santa no Stack Overflow. Se você está embarcando em um grande projeto pesado em JavaScript, definitivamente vale a pena aprender uma biblioteca popular e fazer do jeito dela. Eu sou um cara de protótipo, mas o Stack Overflow parece inclinar-se para o jQuery.

No que diz respeito à existência de apenas “uma maneira de fazê-lo”, sem nenhuma dependência de bibliotecas externas, o modo como escrevi é basicamente isso.

A melhor maneira de definir uma class em JavaScript é não definir uma class.

A sério.

Existem vários sabores diferentes de orientação a objects, alguns deles são:

  • OO baseado em class (introduzido pela primeira vez por Smalltalk)
  • OO baseado em protótipo (introduzido pela primeira vez por Self)
  • OO baseado em multimetodos (introduzido pela primeira vez por CommonLoops, eu acho)
  • OO baseado em predicado (sem ideia)

E provavelmente outros que eu não conheço.

JavaScript implementa o OO baseado em protótipo. No OO baseado em protótipo, novos objects são criados copiando outros objects (em vez de serem instanciados a partir de um modelo de class) e os methods residem diretamente em objects em vez de em classs. A inheritance é feita via delegação: se um object não tem um método ou propriedade, ele é procurado em seu (s) protótipo (s) (ou seja, o object do qual foi clonado), depois os protótipos do protótipo e assim por diante.

Em outras palavras: não há classs.

JavaScript realmente tem um bom ajuste desse modelo: construtores. Não apenas você pode criar objects copiando os existentes, mas também pode construí-los “fora do ar”, por assim dizer. Se você chamar uma function com a new palavra-chave, essa function se tornará um construtor e a palavra this chave this não apontará para o object atual, mas para um recém-criado “vazio”. Então, você pode configurar um object da maneira que quiser. Dessa forma, os construtores JavaScript podem assumir uma das funções das classs no OO tradicional baseado em class: servir como um modelo ou blueprint para novos objects.

Agora, JavaScript é uma linguagem muito poderosa, então é muito fácil implementar um sistema OO baseado em class dentro do JavaScript, se você quiser. No entanto, você só deve fazer isso se realmente precisar e não apenas porque é assim que o Java faz.

Classes ES2015

Na especificação ES2015, você pode usar a syntax de class que é apenas açúcar sobre o sistema protótipo.

 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.`; } } 

Benefícios

O principal benefício é que as ferramentas de análise estática acham mais fácil direcionar essa syntax. Também é mais fácil para os outros usuários de linguagens baseadas em classs usarem a linguagem como poliglota.

Ressalvas

Desconfie de suas limitações atuais. Para obter propriedades particulares, é preciso recorrer ao uso de Symbols ou WeakMaps . Em versões futuras, as classs provavelmente serão expandidas para include esses resources ausentes.

Apoio, suporte

O suporte ao navegador não é muito bom no momento (suportado por quase todos, exceto o IE), mas você pode usar esses resources agora com um transpilador como o Babel .

Recursos

  • Classes em ECMAScript 6 (semântica final)
  • O que? Esperar. Mesmo? Ah não! (um post sobre classs ES6 e privacidade)
  • Tabela de Compatibilidade – Classes
  • Babel – Classes

Eu prefiro usar o {SUPER: SYSTEM} Daniel X. Moore. Esta é uma disciplina que fornece benefícios, como variables ​​de instância reais, inheritance baseada em traços, hierarquias de classs e opções de configuração. O exemplo abaixo ilustra o uso de variables ​​de instância verdadeiras, o que acredito ser a maior vantagem. Se você não precisa de variables ​​de instância e está satisfeito apenas com variables ​​públicas ou privadas, provavelmente existem sistemas mais 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" 

Uau, isso não é realmente muito útil por si só, mas dê uma olhada em adicionar uma subclass:

 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" 

Outra vantagem é a capacidade de ter módulos e inheritance baseada em traços.

 // 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); }); } }, }; } 

Um exemplo de ter a class de pessoa inclui o módulo ligável.

 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! 

Divulgação: Sou Daniel X. Moore e este é o meu {SUPER: SYSTEM} . É a melhor maneira de definir uma class em JavaScript.

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

A seguir estão as maneiras de criar objects em javascript, que eu usei até agora

Exemplo 1:

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

Exemplo 2:

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

Exemplo 3:

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

Exemplo 4: Benefícios reais de Object.create (). por favor consulte [este link]

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

Exemplo 5 (Object.create de Crockford personalizado):

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


Para manter a resposta atualizada com o ES6 / ES2015

Uma class é definida assim:

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

Eu acho que você deveria ler a inheritance prototípica de Douglas Crockford em JavaScript e inheritance clássica em JavaScript .

Exemplos da sua página:

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

Efeito? Isso permitirá que você adicione methods de maneira mais elegante:

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

Eu também recomendo seus vídeos: JavaScript avançado .

Você pode encontrar mais vídeos em sua página: http://javascript.crockford.com/ No livro de John Reisig você pode encontrar muitos exemplos do site de Douglas Crockfor.

Porque eu não vou admitir o plano de fábrica da YUI / Crockford e porque eu gosto de manter as coisas autônomas e extensíveis, essa é a minha variação:

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

onde idealmente o tipo de teste está em algo como o primeiro método prototipado

Se você está indo para o simples, você pode evitar a palavra-chave “novo” inteiramente e apenas usar methods de fábrica. Eu prefiro isso, às vezes, porque eu gosto de usar o JSON para criar objects.

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

Não tenho certeza qual é o impacto no desempenho de objects grandes, no entanto.

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

É assim que o TypeScript compila a class com o construtor para o JavaScript.

O caminho mais simples é:

 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 

A razão para that é que this pode ser vinculado a outra coisa se você der um método como manipulador de events, para salvar o valor durante a instanciação e usá-lo posteriormente.

Edit: definitivamente não é o melhor caminho, apenas uma maneira simples. Estou esperando por boas respostas também!

Você provavelmente deseja criar um tipo usando o Padrão de Dobra:

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

Esse código lhe dará um tipo chamado myType . Ele terá campos privados internos chamados alternar e texto . Ele também terá esses membros expostos: os campos count e numbers ; as propriedades toggle , text e numberLength ; os methods incrementNumbersByCount e tweak .

O padrão de dobra é totalmente detalhado aqui: Padrão de Dobra Javascript

Code golf para a resposta de @ 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()); 

MooTools (My Object Oriented Tools) é centrado na idéia de classs . Você pode até mesmo estender e implementar com inheritance.

Quando masterizado, ele cria um javascript ridiculamente reutilizável e poderoso.

Classes Baseadas em Objeto com Herança

 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 

Simples, doce e pronto.

Uma base

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

Uma aula

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

Açao

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

Com base no exemplo do Triptych, isso pode até ser mais simples:

  // 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" 

Isso cria apenas uma instância de object único, mas ainda é útil se você quiser encapsular vários nomes para variables ​​e methods em uma class. Normalmente, não haveria os argumentos “Bob, M” para o construtor, por exemplo, se os methods fossem chamadas para um sistema com seus próprios dados, como um database ou uma rede.

Eu ainda sou muito novo com JS para ver porque isso não usa o prototype .

O JavaScript é orientado a objects , mas é radicalmente diferente de outras linguagens OOP , como Java, C # ou C ++. Não tente entender isso assim. Jogue esse conhecimento antigo e comece de novo. JavaScript precisa de um pensamento diferente.

Eu sugiro obter um bom manual ou algo sobre o assunto. Eu mesmo achei os Tutoriais ExtJS os melhores para mim, embora eu não tenha usado o framework antes ou depois de lê-lo. Mas dá uma boa explicação sobre o que é o mundo do JavaScript. Desculpe, parece que esse conteúdo foi removido. Aqui está um link para a cópia do archive.org . Trabalha hoje. : P