Come "correttamente" creare un oggetto personalizzato in JavaScript?


471

Mi chiedo quale sia il modo migliore per creare un oggetto JavaScript con proprietà e metodi.

Ho visto esempi in cui la persona ha usato var self = thise quindi utilizza self.tutte le funzioni per assicurarsi che l'ambito sia sempre corretto.

Poi ho visto degli esempi sull'utilizzo .prototypeper aggiungere proprietà, mentre altri lo fanno in linea.

Qualcuno può darmi un esempio corretto di un oggetto JavaScript con alcune proprietà e metodi?


13
Non esiste un modo "migliore".
Trittico

Non è selfuna parola riservata? In caso contrario, dovrebbe essere; poiché selfè una variabile predefinita che si riferisce alla finestra corrente. self === window
Shaz,

2
@Shaz: non è una parola riservata più di altre proprietà del windowBrowser Object Model come documento frames; puoi sicuramente riutilizzare l'identificatore come nome di variabile. Anche se, stilisticamente, preferisco var that= thisevitare ogni possibile confusione. Anche se alla window.selffine è inutile, quindi raramente c'è motivo di toccarlo.
bobince

7
Quando JS è minimizzato, l'assegnazione thisa una variabile locale (ad es. self) Riduce le dimensioni del file.
Patrick Fisher,

Nuovo link Classjs: github.com/divio/classjs
Nikola

Risposte:


889

Esistono due modelli per l'implementazione di classi ed istanze in JavaScript: il modo di prototipazione e il modo di chiusura. Entrambi hanno vantaggi e svantaggi e ci sono molte varianti estese. Molti programmatori e librerie hanno approcci diversi e funzioni di utilità di gestione delle classi per scrivere su alcune delle parti più brutte del linguaggio.

Il risultato è che in società miste avrai un miscuglio di metaclassi, che si comportano tutti in modo leggermente diverso. Quel che è peggio, la maggior parte del materiale tutorial di JavaScript è terribile e offre una sorta di compromesso intermedio per coprire tutte le basi, lasciandoti molto confuso. (Probabilmente anche l'autore è confuso. Il modello a oggetti di JavaScript è molto diverso dalla maggior parte dei linguaggi di programmazione, e in molti punti è stato progettato male.)

Cominciamo con il prototipo . Questo è il più nativo di JavaScript che puoi ottenere: c'è un minimo di codice ambientale e instanceof funzionerà con istanze di questo tipo di oggetto.

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

Possiamo aggiungere metodi all'istanza creata new Shapescrivendoli nella prototypericerca di questa funzione di costruzione:

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

Ora per sottoclassarlo, in quanto è possibile chiamare ciò che JavaScript esegue la sottoclasse. Lo facciamo sostituendo completamente quella strana prototypeproprietà magica :

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

prima di aggiungere metodi ad esso:

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

Questo esempio funzionerà e vedrai il codice simile in molti tutorial. Ma amico, new Shape()è brutto: stiamo istanziando la classe base anche se non si deve creare una forma reale. Succede in questo semplice caso perché JavaScript è così sciatto: consente di passare zero argomenti, nel qual caso xe ydiventare undefinede sono assegnati al prototipo this.xe this.y. Se la funzione di costruzione stesse facendo qualcosa di più complicato, sarebbe caduta piatta sulla sua faccia.

Quindi quello che dobbiamo fare è trovare un modo per creare un oggetto prototipo che contenga i metodi e gli altri membri che desideriamo a livello di classe, senza chiamare la funzione di costruzione della classe base. Per fare questo dovremo iniziare a scrivere il codice helper. Questo è l'approccio più semplice che conosco:

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

Questo trasferisce i membri della classe base nel suo prototipo in una nuova funzione di costruzione che non fa nulla, quindi usa quel costruttore. Ora possiamo scrivere semplicemente:

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

invece new Shape()dell'errore. Ora abbiamo un insieme accettabile di primitive per le classi costruite.

Ci sono alcuni perfezionamenti ed estensioni che possiamo prendere in considerazione in questo modello. Ad esempio, ecco una versione a zucchero sintattico:

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

Entrambe le versioni hanno lo svantaggio che la funzione di costruzione non può essere ereditata, come in molte lingue. Quindi, anche se la tua sottoclasse non aggiunge nulla al processo di costruzione, deve ricordare di chiamare il costruttore di base con qualunque argomento desiderasse la base. Questo può essere leggermente automatizzato usando apply, ma devi ancora scrivere:

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

Quindi un'estensione comune è quella di suddividere l'inizializzazione nella propria funzione piuttosto che nel costruttore stesso. Questa funzione può quindi ereditare bene dalla base:

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!

Ora abbiamo appena la stessa funzione di costruttore per ogni classe. Forse possiamo spostarlo nella sua funzione helper in modo da non dover continuare a digitarlo, ad esempio invece di Function.prototype.subclasscapovolgerlo e lasciare che la funzione della classe base sputi sottoclassi:

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

... che sta cominciando a sembrare un po 'più simile ad altre lingue, anche se con una sintassi leggermente più maldestra. Se lo desideri, puoi aggiungere alcune funzionalità extra. Forse vuoi makeSubclassprendere e ricordare un nome di classe e fornire un valore predefinito toStringutilizzandolo. Forse vuoi fare in modo che il costruttore rilevi quando è stato accidentalmente chiamato senza l' newoperatore (che altrimenti comporterebbe spesso un fastidioso debug):

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

Forse vuoi passare tutti i nuovi membri e makeSubclassaggiungerli al prototipo, per Class.prototype...evitare di dover scrivere abbastanza. Molti sistemi di classe lo fanno, ad esempio:

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

Ci sono molte potenziali funzionalità che potresti considerare desiderabili in un sistema a oggetti e nessuno è veramente d'accordo su una formula particolare.


Il modo di chiusura , quindi. Ciò evita i problemi dell'ereditarietà basata sui prototipi di JavaScript, non usando l'ereditarietà. Anziché:

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

Ora ogni singola istanza di Shapeavrà la propria copia del toStringmetodo (e tutti gli altri metodi o altri membri della classe che aggiungiamo).

La cosa negativa di ogni istanza che ha la propria copia di ogni membro della classe è che è meno efficiente. Se hai a che fare con un gran numero di istanze di sottoclassi, l'ereditarietà prototipica potrebbe aiutarti meglio. Anche vedere un metodo della classe base è leggermente fastidioso come puoi vedere: dobbiamo ricordare quale era il metodo prima che il costruttore della sottoclasse lo sovrascrivesse, o si perde.

[Anche perché qui non c'è eredità, l' instanceofoperatore non funzionerà; dovresti fornire il tuo meccanismo per annusare la classe se ne hai bisogno. Mentre si potrebbe perdere tempo gli oggetti prototipo in un modo simile a quello con l'ereditarietà di prototipi, è un po 'complicato e non vale davvero la pena solo per ottenere instanceoflavoro.]

La cosa buona di ogni istanza che ha il suo metodo è che il metodo può quindi essere associato all'istanza specifica che lo possiede. Ciò è utile a causa dello strano modo JavaScript di associare le thischiamate ai metodi, che ha come risultato che se si stacca un metodo dal suo proprietario:

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

quindi thisall'interno del metodo non ci sarà l'istanza Circle come previsto (in realtà sarà l' windowoggetto globale , causando guai di debug diffusi). In realtà ciò accade in genere quando un metodo viene preso e assegnato a un setTimeout, onclicko EventListenerin generale.

Con il prototipo, devi includere una chiusura per ogni incarico del genere:

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

oppure, in futuro (o ora se si hackera Function.prototype) è possibile farlo anche con function.bind():

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

se le tue istanze vengono eseguite in modo chiusura, l'associazione viene eseguita gratuitamente dalla chiusura sulla variabile di istanza (di solito chiamata thato self, sebbene personalmente consiglierei contro quest'ultima poiché selfha già un altro significato diverso in JavaScript). 1, 1Tuttavia, non ottieni gli argomenti nello snippet gratuito, quindi avrai comunque bisogno di un'altra chiusura o di un bind()se dovessi farlo.

Ci sono molte varianti anche sul metodo di chiusura. Puoi preferire omettere thiscompletamente, creando un nuovo thate restituendolo invece di utilizzare l' newoperatore:

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

In che modo è "corretto"? Tutti e due. Qual è il "migliore"? Dipende dalla tua situazione. FWIW Tendo alla prototipazione per la vera eredità JavaScript quando sto facendo cose fortemente OO e chiusure per semplici effetti di pagina usa e getta.

Ma entrambi i modi sono abbastanza intuitivi per la maggior parte dei programmatori. Entrambi hanno molte potenziali variazioni disordinate. Incontrerai entrambi (così come molti schemi intermedi e generalmente rotti) se usi codice / librerie di altre persone. Non esiste una risposta generalmente accettata. Benvenuti nel meraviglioso mondo degli oggetti JavaScript.

[Questa è stata la parte 94 del motivo per cui JavaScript non è il mio linguaggio di programmazione preferito.]


13
Passaggio graduale molto gradevole dalla definizione di "classe" all'istanza di oggetti. E un bel tocco sull'esclusione new.
Crescent Fresh,

8
Sembra che JavaScript non sia la tua lingua preferita perché vuoi usarlo come se avesse delle classi.
Jonathan Feinberg,

59
Certo che lo faccio, lo stesso vale per tutti: il modello di classe e istanza è il più naturale per la maggior parte dei problemi comuni che i programmatori affrontano oggi. Sono d'accordo sul fatto che, su base teorica, l'ereditarietà basata sui prototipi possa potenzialmente offrire un modo più flessibile di lavorare, ma JavaScript non mantiene pienamente questa promessa. Il suo goffo sistema di funzioni di costruzione ci offre il peggio di entrambi i mondi, rendendo difficile l'eredità di classe pur non fornendo la flessibilità o la semplicità che i prototipi potrebbero offrire. In breve, è cacca.
Bobince,

4
Bob, penso che questa sia una risposta fantastica - mi sto cimentando con questi due schemi per un po 'e penso che tu abbia codificato qualcosa di più conciso di un Resig e spiegato con più intuizione di un Crockford. Non posso pensare ad elogi più alti ...
James Westgate,

4
Mi è sempre sembrato che rappresentare graficamente paradigmi di eredità classici su linguaggi prototipici come javascript sia un piolo quadrato e un foro circolare. Ci sono momenti in cui questo è veramente necessario o è solo un modo per le persone di impedire che la lingua sia come vogliono piuttosto che semplicemente usare la lingua per quello che è?
slf

90

Uso questo schema abbastanza frequentemente - ho scoperto che mi dà una quantità abbastanza grande di flessibilità quando ne ho bisogno. In uso è piuttosto simile alle classi in stile 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;
}();

Questo utilizza una funzione anonima che viene chiamata al momento della creazione, restituendo una nuova funzione di costruzione. Poiché la funzione anonima viene chiamata una sola volta, è possibile creare variabili statiche private (sono all'interno della chiusura, visibili agli altri membri della classe). La funzione di costruzione è fondamentalmente un oggetto Javascript standard: si definiscono gli attributi privati ​​al suo interno e gli attributi pubblici sono collegati alla thisvariabile.

Fondamentalmente, questo approccio combina l'approccio crockfordiano con oggetti Javascript standard per creare una classe più potente.

Puoi usarlo come faresti con qualsiasi altro oggetto Javascript:

Foo.publicStaticMethod(); //calling a static method
var test = new Foo();     //instantiation
test.publicMethod();      //calling a method

4
Sembra interessante, perché è piuttosto vicino al mio "tappeto erboso" che è C #. Penso anche di iniziare a capire perché privateStaticVariable è davvero privato (poiché è definito nell'ambito di una funzione e mantenuto in vita finché ci sono riferimenti ad essa?)
Michael Stum

Dal momento che non sta usando thisdeve ancora essere istanziato con new?
Jordan Parmer,

In realtà, this non abituarsi in constructorfunzione, che diventa Foonell'esempio.
ShZ

4
Il problema qui è che ogni oggetto ottiene la propria copia di tutte le funzioni private e pubbliche.
virtualnobi,

2
@virtualnobi: Questo modello non impedisce di scrivere metodi protytpe: constructor.prototype.myMethod = function () { ... }.
Nicolas Le Thierry d'Ennequin,

25

Douglas Crockford discute ampiamente questo argomento in The Good Parts . Raccomanda di evitare che il nuovo operatore crei nuovi oggetti. Invece propone di creare costruttori personalizzati. Per esempio:

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

In Javascript una funzione è un oggetto e può essere utilizzata per costruire oggetti insieme al nuovo operatore. Per convenzione, le funzioni destinate ad essere utilizzate come costruttori iniziano con una lettera maiuscola. Vedi spesso cose come:

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

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

Nel caso in cui si dimentichi di utilizzare il nuovo operatore mentre si crea un'istanza di un nuovo oggetto, ciò che si ottiene è una normale chiamata di funzione e questo è associato all'oggetto globale anziché al nuovo oggetto.


5
Sono io o penso che Crockford non abbia assolutamente senso con la sua sorpresa del nuovo operatore?
meder omuraliev,

3
@meder: non solo tu. Almeno, penso che non ci sia nulla di sbagliato nel nuovo operatore. E c'è un implicito newin var that = {};ogni caso.
Tim Down,

17
Crockford è un vecchio irritabile e non sono molto d'accordo con lui, ma almeno promuove dando un'occhiata critica a JavaScript e vale la pena ascoltare ciò che ha da dire.
Bobince,

2
@bobince: concordato. La sua scrittura sulle chiusure mi ha aperto gli occhi su molte cose circa 5 anni fa e incoraggia un approccio ponderato.
Tim Down,

20
Sono d'accordo con Crockford. Il problema con il nuovo operatore è che JavaScript renderà il contesto di "questo" molto diverso rispetto a quando altrimenti si chiama una funzione. Nonostante la giusta convenzione sul caso, ci sono problemi che sorgono su basi di codice più grandi quando gli sviluppatori dimenticano di usare nuovi, dimenticano di capitalizzare, ecc. Per essere pragmatici, puoi fare tutto il necessario per fare a meno della nuova parola chiave, quindi perché usarlo e introdurre più punti di errore nel codice? JS è un linguaggio prototipo, non di classe. Quindi perché vogliamo che si comporti come un linguaggio tipicamente statico? Certamente no.
Joshua Ramirez,

13

Per continuare fuori dalla risposta di Bobince

In es6 ora puoi effettivamente creare un class

Quindi ora puoi fare:

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

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

Quindi estendi a una cerchia (come nell'altra risposta) puoi fare:

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

Finisce un po 'più pulito in es6 e un po' più facile da leggere.


Ecco un buon esempio di esso in azione:


6

Puoi anche farlo in questo modo, usando le strutture:

function createCounter () {
    var count = 0;

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

Poi :

var counter1 = createCounter();
counter1.increaseBy(4);

6
Non mi piace in questo modo perché gli spazi bianchi sono importanti. Il riccio dopo il ritorno deve essere sulla stessa riga per la compatibilità tra browser.
geowa4,

5

Un altro modo sarebbe http://jsfiddle.net/nnUY4/ (non so se questo tipo di gestione della creazione di oggetti e delle funzioni rivelatrici seguono uno schema specifico)

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

4

Quando si usa il trucco di chiudere "questo" durante una chiamata del costruttore, è per scrivere una funzione che può essere utilizzata come richiamata da qualche altro oggetto che non vuole invocare un metodo su un oggetto. Non è correlato a "rendere l'ambito corretto".

Ecco un oggetto JavaScript vaniglia:

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

Puoi leggere molto ciò che Douglas Crockford ha da dire su JavaScript. Anche John Resig è brillante. In bocca al lupo!


1
Uh, chiudersi thisha tutto a che fare con "rendere l'ambito corretto".
Roatin Marth,

3
Jonathan ha ragione. Lo scopo di una funzione js è qualunque cosa tu abbia progettato per essere. L'auto = questo trucco è un modo per legarlo a un'istanza particolare in modo che non cambi quando viene chiamato in un altro contesto. Ma a volte è quello che vuoi davvero. Dipende dal contesto.
Marco,

Penso che in realtà stiate tutti dicendo la stessa cosa. self=thissebbene non costringa thisa persistere, consente facilmente l'ambito "corretto" tramite una chiusura.
Crescent Fresh,

2
Il motivo per cui lo fai = this è quello di dare alle funzioni nidificate l'accesso all'ambito di questo come esiste nella funzione di costruzione. Quando le funzioni nidificate si trovano all'interno di funzioni di costruzione, il loro ambito "this" ritorna all'ambito globale.
Joshua Ramirez,

4

Closureè versatile. bobince ha riassunto bene gli approcci prototipo vs. chiusura durante la creazione di oggetti. Tuttavia, è possibile imitare alcuni aspetti OOPdell'utilizzo della chiusura in un modo di programmazione funzionale. Ricorda che le funzioni sono oggetti in JavaScript ; quindi usa la funzione come oggetto in modo diverso.

Ecco un esempio di chiusura:

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

Qualche tempo fa mi sono imbattuto nell'articolo di Mozilla su Closure. Ecco cosa salta ai miei occhi: "Una chiusura consente di associare alcuni dati (l'ambiente) a una funzione che opera su tali dati. Ciò ha evidenti parallelismi con la programmazione orientata agli oggetti, in cui gli oggetti ci consentono di associare alcuni dati (proprietà dell'oggetto ) con uno o più metodi ". Era la prima volta che leggevo un parallelismo tra chiusura e OOP classico senza alcun riferimento al prototipo.

Come?

Supponiamo di voler calcolare l'IVA di alcuni articoli. È probabile che l'IVA rimanga stabile durante la vita di un'applicazione. Un modo per farlo in OOP (pseudo codice):

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

Fondamentalmente passi un valore IVA nel tuo costruttore e il tuo metodo di calcolo può operare su di esso tramite la chiusura . Ora invece di usare una classe / costruttore, passa la tua IVA come argomento in una funzione. Poiché l'unica cosa che ti interessa è il calcolo stesso, restituisce una nuova funzione, che è il metodo di calcolo:

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

Nel tuo progetto identifica i valori di livello superiore che sono buoni candidati di ciò che l'IVA è per il calcolo. Come regola empirica ogni volta che si passano gli stessi argomenti, c'è un modo per migliorarlo usando la chiusura. Non è necessario creare oggetti tradizionali.

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


3

Creare un oggetto

Il modo più semplice per creare un oggetto in JavaScript è utilizzare la sintassi seguente:

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

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

Funziona alla grande per l'archiviazione dei dati in modo strutturato.

Per casi d'uso più complessi, tuttavia, è spesso meglio creare istanze di funzioni:

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

Ciò ti consente di creare più oggetti che condividono lo stesso "progetto", in modo simile a come usi le classi in es. Giava.

Questo può ancora essere fatto in modo più efficiente, tuttavia, utilizzando un prototipo.

Ogni volta che istanze diverse di una funzione condividono gli stessi metodi o proprietà, è possibile spostarle sul prototipo dell'oggetto. In questo modo, ogni istanza di una funzione ha accesso a quel metodo o proprietà, ma non è necessario duplicarla per ogni istanza.

Nel nostro caso, ha senso spostare il metodo fsul prototipo:

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

Eredità

Un modo semplice ma efficace per eseguire l'ereditarietà in JavaScript consiste nell'utilizzare la seguente linea doppia:

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

Questo è simile al fare questo:

B.prototype = new A();

La differenza principale tra entrambi è che il costruttore di Anon viene eseguito durante l'utilizzo Object.create, che è più intuitivo e più simile all'eredità basata su classi.

Puoi sempre scegliere di eseguire facoltativamente il costruttore di Aquando crei una nuova istanza di Baggiungendola al costruttore di B:

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

Se vuoi passare tutti gli argomenti di Ba A, puoi anche usare Function.prototype.apply():

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

Se vuoi mescolare un altro oggetto nella catena del costruttore di B, puoi combinare Object.createcon Object.assign:

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

dimostrazione

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.createpuò essere tranquillamente utilizzato in tutti i browser moderni, incluso IE9 +. Object.assignnon funziona in nessuna versione di IE né in alcuni browser mobili. Si consiglia di effettuare il polyfill Object.create e / o Object.assignse si desidera utilizzarli e supportare browser che non li implementano.

Puoi trovare un polyfill per Object.create qui e uno per Object.assign qui .


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

4
Cosa si aggiunge alle numerose ampie risposte già offerte?
BLM

Mi piace questa risposta poiché è concisa e mostra le tre parti dell'implementazione: 1) Definisci l'oggetto, 2) Crea un'istanza di un'istanza dell'oggetto, 3) Usa l'istanza: mostra tutto a colpo d'occhio invece di avere l'analisi attraverso tutte le risposte dettagliate sopra (che, ovviamente, sono tutte risposte estremamente buone con tutti i dettagli pertinenti che uno vorrebbe) - una specie di semplice sommario qui
G-Man

0

Oltre alla risposta accettata dal 2009. Se puoi indirizzare i browser moderni, puoi utilizzare Object.defineProperty .

Il metodo Object.defineProperty () definisce una nuova proprietà direttamente su un oggetto o modifica una proprietà esistente su un oggetto e restituisce l'oggetto. 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();

Per visualizzare un elenco di compatibilità desktop e mobile, vedere Elenco di compatibilità browser Mozilla . Sì, IE9 + lo supporta così come Safari mobile.


0

Puoi anche provare questo

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

0
Un modello che mi serve bene
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;
};

Innanzitutto, è possibile modificare la preferenza di aggiunta di metodi all'istanza anziché prototypeall'oggetto del costruttore . Dichiaro quasi sempre metodi all'interno del costruttore perché uso molto spesso il dirottamento del costruttore per scopi riguardanti eredità e decoratori.

Ecco come decido dove sono scritte le dichiarazioni:

  • Non dichiarare mai un metodo direttamente sull'oggetto context ( this)
  • Lascia che le vardichiarazioni abbiano la precedenza sulle functiondichiarazioni
  • Lascia che le primitive abbiano la precedenza sugli oggetti ( {}e [])
  • Lascia che le publicdichiarazioni abbiano la precedenza sulle privatedichiarazioni
  • Preferire Function.prototype.bindsopra thus, self, vm,etc
  • Evitare di dichiarare una Classe all'interno di un'altra Classe, a meno che:
    • Dovrebbe essere ovvio che i due sono inseparabili
    • La classe interna implementa il modello di comando
    • La classe Inner implementa The Singleton Pattern
    • La classe interna implementa il modello di stato
    • La classe interna implementa un altro modello di progettazione che lo garantisce
  • Ritorna sempre thisdall'ambito lessicale dello spazio di chiusura.

Ecco perché questi aiutano:

Dirottamento del costruttore
var Super = function Super() {
    ...
    this.inherited = true;
    ...
};
var Klass = function Klass() {
    ...
    // export precepts
    Super.apply(this);  // extends this with property `inherited`
    ...
};
Design del modello
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
Modelli di progettazione
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

Come puoi vedere, non ne ho davvero bisogno thuspoiché preferisco Function.prototype.bind(o .callo .apply) rispetto athus . Nella nostra Singletonclasse, non lo nominiamo nemmeno thusperché INSTANCEtrasmette più informazioni. Perché Model, torniamo in thismodo da poter invocare il Costruttore usando .callper restituire l'istanza in cui ci siamo passati. In modo ridondante, l'abbiamo assegnato alla variabile updated, sebbene sia utile in altri scenari.

Accanto, preferisco costruire oggetti letterali usando la newparola chiave su {parentesi}:

preferito
var klass = new (function Klass(Base) {
    ...
    // export precepts
    Base.apply(this);  //
    this.override = x;
    ...
})(Super);
Non preferito
var klass = Super.apply({
    override: x
});

Come puoi vedere, quest'ultimo non ha la possibilità di sovrascrivere la proprietà "override" della sua Superclass.

Se aggiungo metodi prototypeall'oggetto Class , preferisco un oggetto letterale, con o senza usare la newparola chiave:

preferito
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() {...}
};
Non preferito
Klass.prototype.method = function m() {...};

0

Vorrei menzionare che possiamo usare un titolo o una stringa per dichiarare un oggetto.
Esistono diversi modi per chiamare ogni tipo di essi. Vedi sotto:

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


-1

Bascialmente non esiste un concetto di classe in JS, quindi usiamo la funzione come costruttore di classe che è rilevante con i modelli di progettazione esistenti.

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

Fino ad ora JS non ha idea di voler creare un oggetto, quindi ecco che arriva la nuova parola chiave.

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

Rif: JS professionale per sviluppatori web - Nik Z


Downvote accettato: con un motivo valido sarebbe stato più informativo e avrebbe offerto l'opportunità di migliorare.
Airwind711

V'è il concetto di una classin JS, come lei ha citato nella vostra rubrica utilizzando la functionparola chiave. E ' non è un modello di progettazione, ma una caratteristica intenzionale del linguaggio. Non ti ho votato per questo, ma sembra che qualcun altro lo abbia fatto a causa della terseness e quasi irrilevanza della domanda. Spero che questo feedback sia d'aiuto.
Cody,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.