Como criar "adequadamente" um objeto personalizado em JavaScript?

471

Gostaria de saber qual é a melhor maneira de criar um objeto JavaScript que tenha propriedades e métodos.

Eu já vi exemplos em que a pessoa usou var self = thise depois usa self.em todas as funções para garantir que o escopo esteja sempre correto.

Então eu vi exemplos de .prototypecomo adicionar propriedades, enquanto outros o fazem em linha.

Alguém pode me dar um exemplo adequado de um objeto JavaScript com algumas propriedades e métodos?

Michael Stum
fonte
13
Não existe o "melhor" caminho.
Triptych
Não é selfuma palavra reservada? Se não, deveria ser; uma vez que selfé uma variável predefinida referente à janela atual. self === window
Shaz
2
@ Shaz: não é uma palavra reservada mais do que outras propriedades do windowmodelo de objeto do navegador, como documentou frames; você certamente pode reutilizar o identificador como um nome de variável. Embora, sim, estilisticamente prefiro var that= thisevitar qualquer possível confusão. Mesmo que window.selfseja inútil, raramente há motivo para tocá-lo.
bobince
7
Quando JS é reduzido, a atribuição thisa uma variável local (por exemplo self) reduz o tamanho do arquivo.
Patrick Fisher
Novo link do Classjs
Nikola

Respostas:

889

Existem dois modelos para implementar classes e instâncias no JavaScript: a forma de prototipagem e a forma de fechamento. Ambos têm vantagens e desvantagens, e há muitas variações estendidas. Muitos programadores e bibliotecas têm abordagens diferentes e funções utilitárias de manipulação de classes para encobrir algumas das partes mais feias da linguagem.

O resultado é que, em empresas mistas, você terá uma mistura de metaclasses, todos se comportando de maneira um pouco diferente. O que é pior, a maioria dos materiais de tutoriais sobre JavaScript é terrível e serve como uma espécie de compromisso intermediário para cobrir todas as bases, deixando você muito confuso. (Provavelmente o autor também está confuso. O modelo de objeto do JavaScript é muito diferente da maioria das linguagens de programação e, em muitos lugares, mal projetado.)

Vamos começar com o protótipo . Este é o mais nativo do JavaScript que você pode obter: há um mínimo de código adicional e instanceof funcionará com instâncias desse tipo de objeto.

function Shape(x, y) {
    this.x= x;
    this.y= y;
}

Podemos adicionar métodos à instância criada new Shapeescrevendo-os na prototypepesquisa dessa função construtora:

Shape.prototype.toString= function() {
    return 'Shape at '+this.x+', '+this.y;
};

Agora, para subclassificá-lo, o máximo que você pode chamar de JavaScript faz subclassificação. Fazemos isso substituindo completamente essa prototypepropriedade mágica estranha :

function Circle(x, y, r) {
    Shape.call(this, x, y); // invoke the base class's constructor function to take co-ords
    this.r= r;
}
Circle.prototype= new Shape();

antes de adicionar métodos a ele:

Circle.prototype.toString= function() {
    return 'Circular '+Shape.prototype.toString.call(this)+' with radius '+this.r;
}

Este exemplo funcionará e você verá o código semelhante em muitos tutoriais. Mas cara, isso new Shape()é feio: estamos instanciando a classe base, mesmo que nenhuma Forma real seja criada. Isso acontece com o trabalho neste caso simples, porque o JavaScript é tão desleixada: permite zero de argumentos a serem passados em, caso em que xe ytornam-se undefinede são atribuídos ao protótipo de this.xe this.y. Se a função construtora estivesse fazendo algo mais complicado, ela cairia de cara no chão.

Portanto, o que precisamos fazer é encontrar uma maneira de criar um objeto protótipo que contenha os métodos e outros membros que desejamos no nível da classe, sem chamar a função construtora da classe base. Para fazer isso, teremos que começar a escrever o código auxiliar. Esta é a abordagem mais simples que conheço:

function subclassOf(base) {
    _subclassOf.prototype= base.prototype;
    return new _subclassOf();
}
function _subclassOf() {};

Isso transfere os membros da classe base em seu protótipo para uma nova função construtora que não faz nada e depois usa esse construtor. Agora podemos escrever simplesmente:

function Circle(x, y, r) {
    Shape.call(this, x, y);
    this.r= r;
}
Circle.prototype= subclassOf(Shape);

em vez do new Shape()erro. Agora temos um conjunto aceitável de primitivas para as classes construídas.

Existem alguns refinamentos e extensões que podemos considerar nesse modelo. Por exemplo, aqui está uma versão sintática do açúcar:

Function.prototype.subclass= function(base) {
    var c= Function.prototype.subclass.nonconstructor;
    c.prototype= base.prototype;
    this.prototype= new c();
};
Function.prototype.subclass.nonconstructor= function() {};

...

function Circle(x, y, r) {
    Shape.call(this, x, y);
    this.r= r;
}
Circle.subclass(Shape);

Qualquer uma das versões tem a desvantagem de que a função construtora não pode ser herdada, como ocorre em muitos idiomas. Portanto, mesmo que sua subclasse não adicione nada ao processo de construção, lembre-se de chamar o construtor de base com quaisquer argumentos que a base desejasse. Isso pode ser um pouco automatizado apply, mas você ainda precisa escrever:

function Point() {
    Shape.apply(this, arguments);
}
Point.subclass(Shape);

Portanto, uma extensão comum é dividir o material de inicialização em sua própria função, e não no próprio construtor. Esta função pode herdar da base muito bem:

function Shape() { this._init.apply(this, arguments); }
Shape.prototype._init= function(x, y) {
    this.x= x;
    this.y= y;
};

function Point() { this._init.apply(this, arguments); }
Point.subclass(Shape);
// no need to write new initialiser for Point!

Agora, temos o mesmo padrão de função de construtor para cada classe. Talvez possamos colocar isso em sua própria função auxiliar, para que não tenhamos que continuar digitando, por exemplo, em vez de Function.prototype.subclassgirá-lo e deixar que a Função da classe base cuspa subclasses:

Function.prototype.makeSubclass= function() {
    function Class() {
        if ('_init' in this)
            this._init.apply(this, arguments);
    }
    Function.prototype.makeSubclass.nonconstructor.prototype= this.prototype;
    Class.prototype= new Function.prototype.makeSubclass.nonconstructor();
    return Class;
};
Function.prototype.makeSubclass.nonconstructor= function() {};

...

Shape= Object.makeSubclass();
Shape.prototype._init= function(x, y) {
    this.x= x;
    this.y= y;
};

Point= Shape.makeSubclass();

Circle= Shape.makeSubclass();
Circle.prototype._init= function(x, y, r) {
    Shape.prototype._init.call(this, x, y);
    this.r= r;
};

... que está começando a parecer um pouco mais com outros idiomas, embora com sintaxe um pouco desajeitada. Você pode polvilhar alguns recursos extras, se quiser. Talvez você queira makeSubclasspegar e lembrar um nome de classe e fornecer um padrão toStringusando-o. Talvez você queira que o construtor detecte quando foi chamado acidentalmente sem o newoperador (o que normalmente resultaria em depuração muito irritante):

Function.prototype.makeSubclass= function() {
    function Class() {
        if (!(this instanceof Class))
            throw('Constructor called without "new"');
        ...

Talvez você queira passar todos os novos membros e makeSubclassadicioná-los ao protótipo, para poupar a necessidade de escrever Class.prototype...muito. Muitos sistemas de classe fazem isso, por exemplo:

Circle= Shape.makeSubclass({
    _init: function(x, y, z) {
        Shape.prototype._init.call(this, x, y);
        this.r= r;
    },
    ...
});

Existem muitos recursos em potencial que você pode considerar desejáveis ​​em um sistema de objetos e ninguém realmente concorda com uma fórmula específica.


O caminho do fechamento , então. Isso evita os problemas da herança baseada em protótipo do JavaScript, ao não usar a herança. Em vez de:

function Shape(x, y) {
    var that= this;

    this.x= x;
    this.y= y;

    this.toString= function() {
        return 'Shape at '+that.x+', '+that.y;
    };
}

function Circle(x, y, r) {
    var that= this;

    Shape.call(this, x, y);
    this.r= r;

    var _baseToString= this.toString;
    this.toString= function() {
        return 'Circular '+_baseToString(that)+' with radius '+that.r;
    };
};

var mycircle= new Circle();

Agora, cada instância única Shapeterá sua própria cópia do toStringmétodo (e quaisquer outros métodos ou outros membros da classe que adicionarmos).

O lado ruim de toda instância ter sua própria cópia de cada aluno é que é menos eficiente. Se você estiver lidando com um grande número de instâncias subclassificadas, a herança prototípica poderá atendê-lo melhor. Também chamar um método da classe base é um pouco irritante, como você pode ver: precisamos lembrar qual era o método antes que o construtor da subclasse o substituísse ou se perdesse.

[Também porque não há herança aqui, o instanceofoperador não funcionará; você precisaria fornecer seu próprio mecanismo de detecção de classe, se necessário. Embora você possa mexer nos objetos do protótipo de maneira semelhante à herança do protótipo, é um pouco complicado e realmente não vale a pena apenas para começar a instanceoftrabalhar.]

O bom de toda instância ter seu próprio método é que o método pode ser vinculado à instância específica que o possui. Isso é útil devido à maneira estranha de ligação do JavaScript thisnas chamadas de método, que tem o resultado de que, se você desanexar um método do proprietário:

var ts= mycircle.toString;
alert(ts());

então, thisdentro do método, a instância Circle não será a esperada (na verdade, será o windowobjeto global , causando um problema de depuração generalizado). Na realidade, isso normalmente acontece quando um método é usado e atribuído a um setTimeout, onclickou EventListenerem geral.

Com o protótipo, você deve incluir um fechamento para cada tarefa:

setTimeout(function() {
    mycircle.move(1, 1);
}, 1000);

ou, no futuro (ou agora, se você hackear Function.prototype), você também poderá fazê-lo com function.bind():

setTimeout(mycircle.move.bind(mycircle, 1, 1), 1000);

se suas instâncias são feitas da maneira de fechamento, a ligação é feita gratuitamente pelo fechamento da variável de instância (geralmente chamada thatou self, embora pessoalmente eu recomendaria contra essa última, selfjá que ela tem outro significado diferente em JavaScript). No 1, 1entanto, você não obtém os argumentos do snippet acima gratuitamente, portanto ainda precisaria de outro fechamento ou a, bind()se precisar fazer isso.

Existem muitas variantes no método de fechamento também. Você pode preferir omitir thiscompletamente, criando um novo thate retornando-o em vez de usar o newoperador:

function Shape(x, y) {
    var that= {};

    that.x= x;
    that.y= y;

    that.toString= function() {
        return 'Shape at '+that.x+', '+that.y;
    };

    return that;
}

function Circle(x, y, r) {
    var that= Shape(x, y);

    that.r= r;

    var _baseToString= that.toString;
    that.toString= function() {
        return 'Circular '+_baseToString(that)+' with radius '+r;
    };

    return that;
};

var mycircle= Circle(); // you can include `new` if you want but it won't do anything

Qual o caminho é "adequado"? Ambos. Qual é melhor"? Isso depende da sua situação. FWIW: Eu tendem a criar protótipos para herança de JavaScript real quando estou fazendo coisas fortemente com OO e fechamentos para simples efeitos de página descartáveis.

Mas ambas as formas são bastante contra-intuitivas para a maioria dos programadores. Ambos têm muitas variações confusas em potencial. Você encontrará ambos (assim como muitos esquemas intermediários e geralmente interrompidos) se usar o código / biblioteca de outras pessoas. Não existe uma resposta geralmente aceita. Bem-vindo ao maravilhoso mundo dos objetos JavaScript.

[Isso foi parte 94 de Por que o JavaScript não é minha linguagem de programação favorita.]

bobince
fonte
13
Passo gradual muito bom da definição da "classe" para a instanciação do objeto. E toque agradável em ignorar new.
Crescent Fresh
8
Parece que o JavaScript não é seu idioma favorito porque você deseja usá-lo como se tivesse aulas.
23611 Jonathan Feinberg
59
É claro que sim, todo mundo também: o modelo de classe e instância é o mais natural para a maioria dos problemas comuns que os programadores enfrentam atualmente. Concordo que, em uma base teórica, a herança baseada em protótipo pode oferecer uma maneira mais flexível de trabalhar, mas o JavaScript não cumpre totalmente essa promessa. Seu sistema desajeitado de função de construtor nos dá o pior dos dois mundos, dificultando a herança de classe e fornecendo nenhum dos protótipos de flexibilidade ou simplicidade que poderia oferecer. Em suma, é cocô.
bobince
4
Bob Eu acho que essa é uma resposta impressionante - eu tenho lidado com esses dois padrões há um tempo e acho que você codificou algo de forma mais concisa que um Resig e explicou com mais discernimento que um Crockford. Não há elogios mais altos que eu possa pensar ....
James Westgate
4
Sempre me pareceu que representar graficamente paradigmas clássicos de herança em linguagens prototípicas como o javascript é um pino quadrado e um furo redondo. Há momentos em que isso é realmente necessário ou é apenas uma maneira de as pessoas confundirem o idioma da maneira que desejam, em vez de simplesmente usar o idioma para o que é?
SLF
90

Uso esse padrão com bastante frequência - descobri que ele me dá uma quantidade enorme de flexibilidade quando preciso. Em uso, é bastante semelhante às classes no estilo Java.

var Foo = function()
{

    var privateStaticMethod = function() {};
    var privateStaticVariable = "foo";

    var constructor = function Foo(foo, bar)
    {
        var privateMethod = function() {};
        this.publicMethod = function() {};
    };

    constructor.publicStaticMethod = function() {};

    return constructor;
}();

Isso usa uma função anônima chamada na criação, retornando uma nova função construtora. Como a função anônima é chamada apenas uma vez, é possível criar variáveis ​​estáticas privadas nela (elas estão dentro do fechamento, visíveis para os outros membros da classe). A função construtora é basicamente um objeto Javascript padrão - você define atributos privados dentro dela e atributos públicos são anexados à thisvariável.

Basicamente, essa abordagem combina a abordagem Crockfordian com objetos Javascript padrão para criar uma classe mais poderosa.

Você pode usá-lo como faria com qualquer outro objeto Javascript:

Foo.publicStaticMethod(); //calling a static method
var test = new Foo();     //instantiation
test.publicMethod();      //calling a method
ShZ
fonte
4
Isso parece interessante, porque é muito próximo do meu "território doméstico", que é o C #. Eu também acho que eu começo a entender por que privateStaticVariable é realmente privada (como é definido no âmbito de uma função e mantido vivo desde que há referências a ele?)
Michael Stum
Como não está sendo usado this, ele ainda precisa ser instanciado new?
Jordan Parmer
Na verdade, this é usado na constructorfunção, que se torna Foono exemplo.
SHZ
4
O problema aqui é que cada objeto obtém sua própria cópia de todas as funções públicas e privadas.
virtualnobi
2
@virtualnobi: Este padrão não impedi-lo de escrever métodos protytpe: constructor.prototype.myMethod = function () { ... }.
Nicolas Le Thierry d'Ennequin
25

Douglas Crockford discute esse tópico extensivamente em The Good Parts . Ele recomenda evitar que o novo operador crie novos objetos. Em vez disso, ele propõe criar construtores personalizados. Por exemplo:

var mammal = function (spec) {     
   var that = {}; 
   that.get_name = function (  ) { 
      return spec.name; 
   }; 
   that.says = function (  ) { 
      return spec.saying || ''; 
   }; 
   return that; 
}; 

var myMammal = mammal({name: 'Herb'});

Em Javascript, uma função é um objeto e pode ser usada para construir objetos em conjunto com o novo operador. Por convenção, as funções destinadas a serem usadas como construtores começam com uma letra maiúscula. Você costuma ver coisas como:

function Person() {
   this.name = "John";
   return this;
}

var person = new Person();
alert("name: " + person.name);**

Caso você se esqueça de usar o novo operador enquanto instancia um novo objeto, o que você recebe é uma chamada de função comum, e isso é vinculado ao objeto global e não ao novo objeto.

Diego Pino
fonte
5
Sou eu ou acho que Crockford não faz absolutamente nenhum sentido com o golpe do novo operador?
meder omuraliev
3
@meder: Não é só você. Pelo menos, acho que não há nada errado com o novo operador. E há um implícito newde var that = {};qualquer maneira.
Tim Down
17
Crockford é um velho irritadiço e eu discordo muito dele, mas ele está pelo menos promovendo uma análise crítica do JavaScript e vale a pena ouvir o que ele tem a dizer.
21909 bobince
2
@bince: Concordo. Seus escritos sobre fechamento abriram meus olhos para muitas coisas cerca de cinco anos atrás, e ele incentiva uma abordagem ponderada.
Tim Down
20
Eu concordo com Crockford. O problema com o novo operador é que o JavaScript tornará o contexto "isso" muito diferente do que ao chamar uma função. Apesar da convenção apropriada, existem problemas que surgem em bases de código maiores, pois os desenvolvedores esquecem de usar novos, esquecem de capitalizar etc. Para ser pragmático, você pode fazer tudo o que precisa sem a nova palavra-chave. introduzir mais pontos de falha no código? JS é uma linguagem prototípica, não baseada em classe. Então, por que queremos que ele atue como uma linguagem estática? Eu certamente não.
Joshua Ramirez
13

Para continuar fora da resposta de bobince

Agora, no es6, você pode criar um class

Então agora você pode fazer:

class Shape {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }

    toString() {
        return `Shape at ${this.x}, ${this.y}`;
    }
}

Portanto, estenda para um círculo (como na outra resposta) que você pode:

class Circle extends Shape {
    constructor(x, y, r) {
        super(x, y);
        this.r = r;
    }

    toString() {
        let shapeString = super.toString();
        return `Circular ${shapeString} with radius ${this.r}`;
    }
}

Termina um pouco mais limpo no es6 e um pouco mais fácil de ler.


Aqui está um bom exemplo disso em ação:

Naftali tcp Neal
fonte
6

Você também pode fazer isso dessa maneira, usando estruturas:

function createCounter () {
    var count = 0;

    return {
        increaseBy: function(nb) {
            count += nb;
        },
        reset: function {
            count = 0;
        }
    }
}

Então :

var counter1 = createCounter();
counter1.increaseBy(4);
Eino Gourdin
fonte
6
Não gosto dessa maneira, porque o espaço em branco é importante. O encaracolado após o retorno deve estar na mesma linha para compatibilidade entre navegadores.
geowa4
5

Outra maneira seria http://jsfiddle.net/nnUY4/ (não sei se esse tipo de manipulação de funções de criação e revelação de objetos segue algum padrão específico)

// Build-Reveal

var person={
create:function(_name){ // 'constructor'
                        //  prevents direct instantiation 
                        //  but no inheritance
    return (function() {

        var name=_name||"defaultname";  // private variable

        // [some private functions]

        function getName(){
            return name;
        }

        function setName(_name){
            name=_name;
        }

        return {    // revealed functions
            getName:getName,    
            setName:setName
        }
    })();
   }
  }

  // … no (instantiated) person so far …

  var p=person.create(); // name will be set to 'defaultname'
  p.setName("adam");        // and overwritten
  var p2=person.create("eva"); // or provide 'constructor parameters'
  alert(p.getName()+":"+p2.getName()); // alerts "adam:eva"
Fluchtpunkt
fonte
4

Quando alguém usa o truque para fechar "this" durante uma chamada de construtor, é para escrever uma função que pode ser usada como retorno de chamada por algum outro objeto que não deseja chamar um método em um objeto. Não está relacionado a "tornar o escopo correto".

Aqui está um objeto JavaScript baunilha:

function MyThing(aParam) {
    var myPrivateVariable = "squizzitch";

    this.someProperty = aParam;
    this.useMeAsACallback = function() {
        console.log("Look, I have access to " + myPrivateVariable + "!");
    }
}

// Every MyThing will get this method for free:
MyThing.prototype.someMethod = function() {
    console.log(this.someProperty);
};

Você pode ler muito o que Douglas Crockford tem a dizer sobre JavaScript. John Resig também é brilhante. Boa sorte!

Jonathan Feinberg
fonte
1
Fechar thistem tudo a ver com "tornar o escopo correto".
Roatin Marth 20/10/2009
3
Jonathan está certo. O escopo de uma função js é o que você projetar. O truque self = this é uma maneira de vinculá-lo a uma instância específica, para que não mude quando chamado em outro contexto. Mas às vezes é isso que você realmente quer. Depende do contexto.
20409 Marco Marco
Acho que todos estão dizendo a mesma coisa, na verdade. self=thisembora não force thisa persistir, permite facilmente o escopo "correto" por meio de um fechamento.
Crescent Fresh
2
A razão pela qual você faz isso = isso é conceder às funções aninhadas acesso ao escopo disso, como existe na função construtora. Quando funções aninhadas estão dentro de funções construtoras, seu escopo "this" é revertido para o escopo global.
30912 Joshua Ramirez
4

Closureé versátil. bobince resumiu bem as abordagens protótipo versus fechamento ao criar objetos. No entanto, você pode imitar alguns aspectos do OOPuso do fechamento de uma maneira de programação funcional. Lembre-se de funções são objetos em JavaScript ; então use a função como objeto de uma maneira diferente.

Aqui está um exemplo de encerramento:

function outer(outerArg) {
    return inner(innerArg) {
        return innerArg + outerArg; //the scope chain is composed of innerArg and outerArg from the outer context 
    }
}

Há um tempo atrás, me deparei com o artigo da Mozilla sobre fechamento. Aqui está o que pulo aos meus olhos: "Um fechamento permite associar alguns dados (o ambiente) a uma função que opera nesses dados. Isso tem paralelos óbvios com a programação orientada a objetos, onde objetos nos permitem associar alguns dados (as propriedades do objeto ) com um ou mais métodos ". Foi a primeira vez que li um paralelismo entre o fechamento e o POO clássico, sem referência ao protótipo.

Quão?

Suponha que você queira calcular o IVA de alguns itens. É provável que o IVA permaneça estável durante a vida útil de um aplicativo. Uma maneira de fazer isso no OOP (pseudo código):

public class Calculator {
    public property VAT { get; private set; }
    public Calculator(int vat) {
        this.VAT = vat;
    }
    public int Calculate(int price) {
        return price * this.VAT;
    }
}

Basicamente, você transmite um valor de IVA ao seu construtor e seu método de cálculo pode operar com ele através do fechamento . Agora, em vez de usar uma classe / construtor, passe seu IVA como argumento para uma função. Como o único material de seu interesse é o próprio cálculo, retorna uma nova função, que é o método de cálculo:

function calculator(vat) {
    return function(item) {
        return item * vat;
    }
}
var calculate = calculator(1.10);
var jsBook = 100; //100$
calculate(jsBook); //110

No seu projeto, identifique valores de nível superior que sejam bons candidatos ao cálculo do IVA. Como regra geral, sempre que você passa os mesmos argumentos, há uma maneira de aprimorá-lo usando o fechamento. Não há necessidade de criar objetos tradicionais.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures

Roland
fonte
3

Criando um objeto

A maneira mais fácil de criar um objeto em JavaScript é usar a seguinte sintaxe:

var test = {
  a : 5,
  b : 10,
  f : function(c) {
    return this.a + this.b + c;
  }
}

console.log(test);
console.log(test.f(3));

Isso funciona muito bem para armazenar dados de maneira estruturada.

Para casos de uso mais complexos, no entanto, geralmente é melhor criar instâncias de funções:

function Test(a, b) {
  this.a = a;
  this.b = b;
  this.f = function(c) {
return this.a + this.b + c;
  };
}

var test = new Test(5, 10);
console.log(test);
console.log(test.f(3));

Isso permite criar vários objetos que compartilham o mesmo "blueprint", semelhante à maneira como você usa as classes, por exemplo. Java.

No entanto, isso ainda pode ser feito com mais eficiência usando um protótipo.

Sempre que diferentes instâncias de uma função compartilham os mesmos métodos ou propriedades, você pode movê-las para o protótipo desse objeto. Dessa forma, toda instância de uma função tem acesso a esse método ou propriedade, mas não precisa ser duplicada para cada instância.

No nosso caso, faz sentido mover o método fpara o protótipo:

function Test(a, b) {
  this.a = a;
  this.b = b;
}

Test.prototype.f = function(c) {
  return this.a + this.b + c;
};

var test = new Test(5, 10);
console.log(test);
console.log(test.f(3));

Herança

Uma maneira simples, porém eficaz, de fazer herança em JavaScript, é usar o seguinte recurso de duas linhas:

B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;

Isso é semelhante a fazer isso:

B.prototype = new A();

A principal diferença entre ambos é que o construtor de Anão é executado ao usar Object.create, o que é mais intuitivo e mais semelhante à herança baseada em classe.

Você sempre pode optar por executar opcionalmente o construtor Aao criar uma nova instância de B, adicionando-a ao construtor de B:

function B(arg1, arg2) {
    A(arg1, arg2); // This is optional
}

Se você deseja passar todos os argumentos de Bpara A, também pode usar Function.prototype.apply():

function B() {
    A.apply(this, arguments); // This is optional
}

Se você deseja misturar outro objeto na cadeia de construtores de B, você pode combinar Object.createcom Object.assign:

B.prototype = Object.assign(Object.create(A.prototype), mixin.prototype);
B.prototype.constructor = B;

Demo

function A(name) {
  this.name = name;
}

A.prototype = Object.create(Object.prototype);
A.prototype.constructor = A;

function B() {
  A.apply(this, arguments);
  this.street = "Downing Street 10";
}

B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;

function mixin() {

}

mixin.prototype = Object.create(Object.prototype);
mixin.prototype.constructor = mixin;

mixin.prototype.getProperties = function() {
  return {
    name: this.name,
    address: this.street,
    year: this.year
  };
};

function C() {
  B.apply(this, arguments);
  this.year = "2018"
}

C.prototype = Object.assign(Object.create(B.prototype), mixin.prototype);
C.prototype.constructor = C;

var instance = new C("Frank");
console.log(instance);
console.log(instance.getProperties());


Nota

Object.createpode ser usado com segurança em todos os navegadores modernos, incluindo o IE9 +. Object.assignnão funciona em nenhuma versão do IE nem em alguns navegadores móveis. Recomenda-se policar Object.create e / ouObject.assign se você deseja usá-los e oferecer suporte a navegadores que não os implementam.

Você pode encontrar um polyfill para Object.create aqui e outro para Object.assign aqui .

John Slegers
fonte
0
var Person = function (lastname, age, job){
this.name = name;
this.age = age;
this.job = job;
this.changeName = function(name){
this.lastname = name;
}
}
var myWorker = new Person('Adeola', 23, 'Web Developer');
myWorker.changeName('Timmy');

console.log("New Worker" + myWorker.lastname);
adeola olanrewaju
fonte
4
O que isso acrescenta às numerosas respostas extensas já oferecidas?
blm
Gosto dessa resposta, pois é concisa e mostra as três partes da implementação: 1) Defina o objeto, 2) Instancia uma instância do objeto, 3) Use a instância - mostra tudo rapidamente, em vez de fazer a análise através de todas as detalhado respostas acima (que, é claro, são todos extremamente boas respostas com todos os detalhes pertinentes que se gostaria) - uma espécie de resumo simples aqui
G-Man
0

Além da resposta aceita de 2009. Se você pode segmentar navegadores modernos, pode-se usar o Object.defineProperty .

O método Object.defineProperty () define uma nova propriedade diretamente em um objeto ou modifica uma propriedade existente em um objeto e retorna o objeto. Fonte: Mozilla

var Foo = (function () {
    function Foo() {
        this._bar = false;
    }
    Object.defineProperty(Foo.prototype, "bar", {
        get: function () {
            return this._bar;
        },
        set: function (theBar) {
            this._bar = theBar;
        },
        enumerable: true,
        configurable: true
    });
    Foo.prototype.toTest = function () {
        alert("my value is " + this.bar);
    };
    return Foo;
}());

// test instance
var test = new Foo();
test.bar = true;
test.toTest();

Para ver uma lista de compatibilidade com computadores e dispositivos móveis, consulte a lista de compatibilidade do navegador Mozilla . Sim, o IE9 + é compatível com o Safari Mobile.

Alex Nolasco
fonte
0

Você também pode tentar isso

    function Person(obj) {
    'use strict';
    if (typeof obj === "undefined") {
        this.name = "Bob";
        this.age = 32;
        this.company = "Facebook";
    } else {
        this.name = obj.name;
        this.age = obj.age;
        this.company = obj.company;
    }

}

Person.prototype.print = function () {
    'use strict';
    console.log("Name: " + this.name + " Age : " + this.age + " Company : " + this.company);
};

var p1 = new Person({name: "Alex", age: 23, company: "Google"});
p1.print();
G Naga Subrahmanyam
fonte
0
Um padrão que me serve bem
var Klass = function Klass() {
    var thus = this;
    var somePublicVariable = x
      , somePublicVariable2 = x
      ;
    var somePrivateVariable = x
      , somePrivateVariable2 = x
      ;

    var privateMethod = (function p() {...}).bind(this);

    function publicMethod() {...}

    // export precepts
    this.var1 = somePublicVariable;
    this.method = publicMethod;

    return this;
};

Primeiro, você pode alterar sua preferência de adicionar métodos à instância em vez do prototypeobjeto do construtor . Quase sempre declaro métodos dentro do construtor porque uso o seqüestro de construtor muita frequência para fins de Herança e Decoradores.

Aqui está como eu decido onde as declarações serão escritas:

  • Nunca declare um método diretamente no objeto de contexto ( this)
  • Permita que as vardeclarações tenham precedência sobrefunction declarações
  • Permita que as primitivas tenham precedência sobre os objetos ( {}e[] )
  • Permita que as publicdeclarações tenham precedência sobreprivate declarações
  • Prefere Function.prototype.bindsobre thus, self, vm,etc
  • Evite declarar uma classe dentro de outra classe, a menos que:
    • Deveria ser óbvio que os dois são inseparáveis
    • A classe Inner implementa o padrão de comando
    • A classe Inner implementa o Padrão Singleton
    • A classe interna implementa o padrão de estado
    • A Classe Interna implementa outro Padrão de Design que garante esse
  • Sempre retorne thisde dentro do escopo léxico do espaço de fechamento.

Aqui está o porquê destes ajudarem:

Seqüestro de construtor
var Super = function Super() {
    ...
    this.inherited = true;
    ...
};
var Klass = function Klass() {
    ...
    // export precepts
    Super.apply(this);  // extends this with property `inherited`
    ...
};
Model Design
var Model = function Model(options) {
    var options = options || {};

    this.id = options.id || this.id || -1;
    this.string = options.string || this.string || "";
    // ...

    return this;
};
var model = new Model({...});
var updated = Model.call(model, { string: 'modified' });
(model === updated === true);  // > true
Padrões de design
var Singleton = new (function Singleton() {
    var INSTANCE = null;

    return function Klass() {
        ...
        // export precepts
        ...

        if (!INSTANCE) INSTANCE = this;
        return INSTANCE;
    };
})();
var a = new Singleton();
var b = new Singleton();
(a === b === true);  // > true

Como você pode ver, eu realmente não preciso disso, thusjá que prefiro Function.prototype.bind(ou .callou .apply)thus . Em nossa Singletonclasse, nem o nomeamos, thusporque INSTANCEtransmite mais informações. Para Model, retornamos thispara que possamos chamar o Construtor usando .callpara retornar a instância que passamos para ele. Redundantemente, nós a atribuímos à variável updated, embora seja útil em outros cenários.

Além disso, prefiro construir literais de objetos usando o método new palavra-chave em vez de {colchetes}:

Preferido
var klass = new (function Klass(Base) {
    ...
    // export precepts
    Base.apply(this);  //
    this.override = x;
    ...
})(Super);
Não preferido
var klass = Super.apply({
    override: x
});

Como você pode ver, o último não tem capacidade de substituir a propriedade "substituir" da sua superclasse.

Se eu adicionar métodos ao prototypeobjeto da classe , prefiro um literal de objeto - com ou sem o uso da newpalavra-chave:

Preferido
Klass.prototype = new Super();
// OR
Klass.prototype = new (function Base() {
    ...
    // export precepts
    Base.apply(this);
    ...
})(Super);
// OR
Klass.prototype = Super.apply({...});
// OR
Klass.prototype = {
    method: function m() {...}
};
Não preferido
Klass.prototype.method = function m() {...};
Cody
fonte
0

Gostaria de mencionar que podemos usar um Título ou uma String para declarar um Objeto.
Existem diferentes maneiras de chamar cada tipo deles. Ver abaixo:

var test = {

  useTitle : "Here we use 'a Title' to declare an Object",
  'useString': "Here we use 'a String' to declare an Object",
  
  onTitle : function() {
    return this.useTitle;
  },
  
  onString : function(type) {
    return this[type];
  }
  
}

console.log(test.onTitle());
console.log(test.onString('useString'));

Chetabahana
fonte
-1

Basicamente, não existe um conceito de classe no JS; portanto, usamos a função como construtor de classe relevante para os padrões de design existentes.

//Constructor Pattern
function Person(name, age, job){
 this.name = name;
 this.age = age;
 this.job = job;
 this.doSomething = function(){
    alert('I am Happy');
}
}

Até agora, JS não tem idéia de que você deseja criar um objeto, então aqui vem a nova palavra-chave.

var person1 = new Person('Arv', 30, 'Software');
person1.name //Arv

Ref: JS profissional para desenvolvedores web - Nik Z

Airwind711
fonte
O voto negativo foi aceito: com um motivo válido, teria sido mais informativo e teria servido de oportunidade para melhorar.
Airwind711
Não é o conceito de um classem JS, como você mencionou em seu título usando a functionpalavra-chave. É não um padrão de design, mas uma característica intencional da linguagem. Não votei contra você nisso, mas parece que alguém o fez por causa da discrepância e quase irrelevância para a pergunta. Espero que esse feedback ajude.
Cody