Quais técnicas podem ser usadas para definir uma classe em JavaScript e quais são suas vantagens e desvantagens?

686

Prefiro usar o OOP em projetos de grande escala como o que estou trabalhando no momento. Preciso criar várias classes em JavaScript, mas, se não me engano, há pelo menos algumas maneiras de fazer isso. Qual seria a sintaxe e por que isso seria feito dessa maneira?

Eu gostaria de evitar o uso de bibliotecas de terceiros - pelo menos a princípio.
Procurando outras respostas, encontrei o artigo Programação Orientada a Objetos com JavaScript, Parte I: Herança - Doc JavaScript que discute a programação orientada a objetos em JavaScript. Existe uma maneira melhor de fazer herança?

Karim
fonte
Nota: Esta é uma duplicata de stackoverflow.com/questions/355848
Jason S
3
Pessoalmente, gosto de declarar membros da classe dentro do corpo da função. Eu uso a técnica 'consertando isso' para criar um fechamento para que ele se comporte mais como uma classe. Eu tenho um exemplo detalhado no meu blog: ncombo.wordpress.com/2012/12/30/...
Jon
Portamos a maior parte da funcionalidade C ++ OOP para JavaScript com uma sintaxe simples e natural. Veja minha resposta aqui: stackoverflow.com/a/18239463/1115652
Não há aulas em JavaScript. Mas se você deseja simular o comportamento de classe em JS, é possível. Veja detalhes em: symfony-world.blogspot.com/2013/10/…
ducin

Respostas:

743

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 classes em JavaScript. JavaScript usa um prototypeesquema de herança baseado em.

Além disso, existem inúmeras bibliotecas JavaScript populares que têm seu próprio estilo de aproximação da funcionalidade de classe no JavaScript. Você deve conferir pelo menos o Prototype e o jQuery .

Decidir qual desses é o "melhor" é uma ótima maneira de iniciar uma guerra santa no Stack Overflow. Se você está embarcando em um projeto maior com JavaScript, definitivamente vale a pena aprender uma biblioteca popular e fazê-lo da maneira deles. Eu sou um cara do protótipo, mas o Stack Overflow parece inclinar-se para o jQuery.

Na medida em que existe apenas "uma maneira de fazê-lo", sem nenhuma dependência de bibliotecas externas, a maneira como escrevi é praticamente isso.

Tríptico
fonte
48
Mas ele não funciona como a linguagem X onde eu aprendi o único caminho verdadeiro que uma coisinha que é usado para fazer instâncias de objetos devem trabalhar :(
Erik Reppen
2
De acordo com developer.mozilla.org/pt-BR/docs/Web/JavaScript/…, as propriedades também devem ser adicionadas ao protótipo ("Person.prototype.name = '';")
DaveD
1
@ DaveD - talvez tenha, mas não parece mais ..?
Kieren Johnstone
6
O jQuery nem fornece nenhuma maneira de criar funcionalidades semelhantes a classes ??? (Todas as classes que ele tem são classes CSS) Você deve removê-lo dessa parte da resposta.
Bergi 12/07/2014
7
A partir da metade secound de 2015 EcmaScript 6 novo padrão foi lançado, então eu proponho a fazê-lo da maneira nova (muito mais limpo e mais fáceis) es6-features.org/#ClassDefinition
DevWL
213

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

A sério.

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

  • OO baseado em classe (apresentado pela Smalltalk)
  • OO baseado em protótipo (apresentado pela Self)
  • OO baseado em vários métodos (introduzido pela primeira vez pelo CommonLoops, acho)
  • OO baseado em predicado (nenhuma ideia)

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

JavaScript implementa OO baseado em protótipo. No OO baseado em protótipo, novos objetos são criados copiando outros objetos (em vez de serem instanciados a partir de um modelo de classe) e os métodos vivem diretamente nos objetos, e não nas classes. A herança é feita via delegação: se um objeto não possui um método ou propriedade, ele é procurado em seu (s) protótipo (s) (ou seja, o objeto do qual foi clonado), depois nos protótipos do protótipo e assim por diante.

Em outras palavras: não há classes.

Na verdade, o JavaScript tem um belo ajuste nesse modelo: construtores. Não apenas você pode criar objetos copiando os existentes, mas também construí-los "do nada", por assim dizer. Se você chamar uma função com a newpalavra - chave, essa função se tornará um construtor e a thispalavra - chave não apontará para o objeto atual, mas para um objeto "vazio" recém-criado. Portanto, você pode configurar um objeto da maneira que desejar. Dessa forma, os construtores JavaScript podem assumir um dos papéis das classes no OO tradicional baseado em classes: servindo como modelo ou blueprint para novos objetos.

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

Jörg W Mittag
fonte
"Se você chamar uma função com a nova palavra-chave, essa função se tornará um construtor e a palavra-chave this não apontará para o objeto atual, mas para um recém-criado" vazio ". Se você chamar uma função sem a nova palavra-chave, isso se referirá ao contexto de chamada, por padrão, o objeto global (janela). No modo estrito, indefinido é o padrão. chamar, aplicar e vincular leva o contexto de chamada como primeiro parâmetro. developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/…
Elias Hasle
83

Classes ES2015

Na especificação do ES2015, você pode usar a sintaxe da classe que é apenas açúcar no sistema de 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 sintaxe. Também é mais fácil para outras pessoas provenientes de idiomas baseados em classe usarem o idioma 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 classes provavelmente serão expandidas para incluir esses recursos 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 recursos agora com um transpiler como o Babel .

Recursos

Dale
fonte
56

Eu prefiro usar o Daniel X. Moore {SUPER: SYSTEM}. Esta é uma disciplina que fornece benefícios como variáveis ​​de instância verdadeiras, herança baseada em características, hierarquias de classes e opções de configuração. O exemplo abaixo ilustra o uso de variáveis ​​de instância verdadeiras, que acredito serem a maior vantagem. Se você não precisar de variáveis ​​de instância e estiver satisfeito apenas com variáveis ​​públicas ou privadas, provavelmente haverá 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 subclasse:

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 herança baseada em características.

// 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 como a classe person inclui o módulo vinculá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: Eu sou Daniel X. Moore e este é o meu {SUPER: SYSTEM}. É a melhor maneira de definir uma classe em JavaScript.

Daniel X Moore
fonte
@DanielXMoore "Variáveis ​​de instância são compartilhadas entre instâncias individuais de uma classe" Essas não são variáveis ​​de instância, são variáveis ​​estáticas / de classe.
JAB
2
@JAB Isso está incorreto, as variáveis ​​estáticas / de classe são compartilhadas entre todas as instâncias de uma classe. Cada instância possui suas próprias variáveis ​​de instância.
Daniel X Moore
(Dito de outra forma, usando o significado normal do termo "variável de instância", se uma variável é ou não uma é ortogonal ao nível de acessibilidade da variável.)
JAB
2
Você quase soou como um super-herói para reivindicar o melhor xD
Dadan
Uma abordagem fácil de definir uma classe Javascript usando objetos JavaScript: wapgee.com/story/i/203
Ilyas karim
41
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'});
liammclennan
fonte
Essa é uma maneira muito elegante de criar uma estrutura de objetos utilizável sem precisar importar nada. Eu estava usando o sistema de classes da Resig, mas talvez eu goste mais disso. Obrigado.
precisa saber é o seguinte
29
O problema dessa abordagem é que toda vez que você cria uma nova instância Animal, ela redefine as funções, em vez de defini-las apenas uma vez com o protótipo.
Justin
33

A seguir estão as maneiras de criar objetos 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 personalizado de Crockford):

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 classe é 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());
Amol M Kulkarni
fonte
1
@ Justin: Por favor, deixe-me saber o que não é válido?
Amol M Kulkarni
Enquanto estudava essas notações, também me deparei com this.set (). Por exemplo: this.set ('port', 3000). Meu palpite é que isso é usado para definir a propriedade port para o objeto. Nesse caso, por que não usamos diretamente: {port: 3000}. Existe alguma documentação onde eu possa obter mais detalhes.
adityah
24

Eu acho que você deveria ler a Herança Prototípica de Douglas Crockford em JavaScript e a Herança Clássica em JavaScript .

Exemplos de sua página:

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

Efeito? Isso permitirá que você adicione métodos de maneira mais elegante:

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

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

Eu também recomendo os vídeos dele: 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 no site de Douglas Crockfor.

Jarek
fonte
25
Sou apenas eu? Como diabos isso é mais elegante? Eu chamaria de definições de funções com reais 'strings'como nomes de muitas coisas, mas elegante, não é um deles ...
fgysin Reintegrar Monica
4
@JAB, mas a reflexão é a exceção, não a regra. Com o método acima, você deve declarar todos os seus métodos com strings.
precisa
16

Como não vou admitir o plano da fábrica da YUI / Crockford e porque gosto de manter as coisas independentes e extensíveis, esta é 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 teste typeof é sobre algo como o primeiro método prototipado

annakata
fonte
Eu gosto disso. Geralmente uso a sintaxe padrão do JS, porque não gosto da ideia de copiar funções em cada instância do objeto. Eu sempre sentia falta da beleza da solução independente, e isso a resolve muito bem.
Lukasz Korzybski
1
Não tenho certeza, mas entendi que definir a função protótipo dentro do escopo (um pouco como um fechamento) de uma função resulta em um vazamento de memória, pois o coletor de lixo não pode chegar lá na instância dessas classes.
Sanne 26/07
15

Se você é simples, pode evitar completamente a palavra-chave "nova" e apenas usar métodos de fábrica. Às vezes prefiro isso porque gosto de usar JSON para criar objetos.

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 sei ao certo qual é o impacto no desempenho para objetos grandes.

Sam
fonte
A linha obj.instancevar1 = var1 não é necessária, pois o objeto interno terá acesso aos parâmetros de getSomeObj ().
Triptych
Uau. Isso faz meu cérebro doer, mas há uma certa elegância nele. Portanto, a parte "obj.instancevar1 = var1" é o começo de um tipo de construtor, suponho?
22408 Karim
Acabei de ver o comentário do Triptych. Eu vejo. Então, você poderia fazer algo como "instancevar1: var1" onde o objeto interno está sendo instanciado.
22408 Karim
Exatamente ... quando você usa {} para definir um objeto, ele tem acesso a variáveis ​​que estão no escopo atualmente.
22408 Sam
10
Com essa abordagem, você perde a capacidade de herdar e, como não está usando obj.prototype.something, está definindo as funções sempre que estiver usando o objeto = mais memória e mais lento.
alguns
12
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 classe com o construtor para JavaScript.

Mick
fonte
10

A maneira 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

O motivo thaté que isso thispode ser vinculado a outra coisa se você fornecer um método como manipulador de eventos, para salvar o valor durante a instanciação e usá-lo mais tarde.

Edit: definitivamente não é a melhor maneira, apenas uma maneira simples. Também estou esperando por boas respostas!

orip
fonte
1
A construção that = this não é necessária aqui. Além disso, os métodos add () e avg () serão copiados para cada "instância" da classe Foo, em vez de compartilhados entre eles.
Triptych
1
É necessário (mais ou menos) nesse caso, mas não no caso simples que você forneceu.
Triptych
9

Você provavelmente deseja criar um tipo usando o padrão de dobramento:

    // 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 fornecerá um tipo chamado myType . Terá campos privados internos chamados de alternância e texto . Ele também terá esses membros expostos: os campos contam e números ; as propriedades de alternância , texto e numberLength ; os métodos incrementNumbersByCount e tweak .

O padrão de dobragem é totalmente detalhado aqui: Javascript Folding Pattern

intrepidis
fonte
3

Código de golfe 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());

tponthieux
fonte
2

O MooTools (Minhas Ferramentas Orientadas a Objetos) está centrado na idéia de classes . Você pode até estender e implementar com herança.

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

Ryan Florence
fonte
2

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.

Ulad Kasach
fonte
1

Humilhar

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"
Mikael Dúi Bolinder
fonte
1

Com base no exemplo do Triptych, isso pode ser ainda 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 objeto único, mas ainda é útil se você desejar encapsular vários nomes de variáveis ​​e métodos em uma classe. Normalmente, não haveria os argumentos "Bob, M" para o construtor, por exemplo, se os métodos fossem chamadas para um sistema com seus próprios dados, como um banco de dados ou rede.

Ainda sou muito novo com JS para ver por que isso não usa a prototypecoisa.

Roland
fonte
0

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

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

Vilx-
fonte
2
Orientado a Objeto? Eu pensei que era funcional .
Peter Mortensen
O link "ExtJS Tutorials" está quebrado.
Peter Mortensen
Eu acho que seria mais explicativo explicar que funções em javascript são objetos, e as regras de escopo entre colchetes de javascript tornam cada bloco de função encapsulado.
Mibbit
-1

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

Avinash Maurya
fonte