2009-09-10 14 views
6

Sto riscrivendo un progetto JavaScript e voglio essere in grado di utilizzare le metodologie orientate agli oggetti per organizzare il pasticcio del codice corrente. La preoccupazione principale è che questo JavaScript debba funzionare come un widget all'interno di siti Web di terze parti e non posso farlo in conflitto con altre librerie JavaScript che altri siti Web potrebbero utilizzare.Quale sarà un buon metodo di ereditarietà Javascript minimalista?

Quindi sto cercando un modo per scrivere eredità "class-like" in JavaScript che ha i seguenti requisiti:

  1. Nessun librerie o cose che sarebbero in conflitto con una libreria esterna esterni (che preclude copia & incolla da una libreria esterna).
  2. Minimalista - Non voglio che il codice di supporto sia più grande di alcune righe di codice e non voglio che gli sviluppatori abbiano bisogno di molte piastre della caldaia ogni volta che definiscono una nuova classe o metodi.
  3. Dovrebbe consentire l'estensione dinamica degli oggetti padre in modo che gli oggetti figlio visualizzino le modifiche (prototipo).
  4. Dovrebbe consentire la concatenazione del costruttore.
  5. Dovrebbe consentire chiamate di tipo super.
  6. Dovrebbe ancora sentire JavaScript-ish.

Inizialmente ho cercato di lavorare con semplice prototipo concatenamento:

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

    this.draw = function() { 
    throw new Error("Arbitrary shapes cannot be drawn"); 
    } 
} 

function Square(x,y,side) { 
    this.x = x; 
    this.y = y; 
    this.side = side; 

    this.draw = function() { 
    gotoXY(this.x,this.y); lineTo(this.x+this.side, this.y); ... 
    } 
} 
Square.prototype = new Shape(); 

E che risolve i requisiti 1, 2 e 6, ma id non consente le chiamate super (nuove funzioni sostituiscono funzioni madri), costruttore di concatenamento e estendere dinamicamente un genitore non fornisce i nuovi metodi a una classe figlia.

Qualsiasi suggerimento sarà benvenuto.

+0

puoi chiamare super metodi. Se definisci il disegno in Triangolo puoi ottenere un super disegno da Triangle.prototype.draw –

+0

Il problema è che il metodo "super" non ha accesso ai campi dell'oggetto corrente - "questo" è il prototipo e non il "questo" di disegnare. Posso fare 'Square.prototype.draw.apply (this, arguments)' ma quello è clanky e normalmente non mi piacciono i metodi per chiamare il loro contenitore class per nome (dovrebbero usare 'this' ovunque). – Guss

+0

e cosa dire di this.constructor.prototype.draw.apply (questo, argomenti) –

risposta

5

Io suggerirei il seguente schema che fa uso di un clone function di ereditare dai CAMPIONATURE e non casi:

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

Shape.prototype.draw = function() { 
    throw new Error('Arbitrary shapes cannot be drawn'); 
}; 

function Square(x,y,side) { 
    Shape.call(this, x, y); // call super constructor 
    this.side = side; 
} 

// inherit from `Shape.prototype` and *not* an actual instance: 
Square.prototype = clone(Shape.prototype); 

// override `draw()` method 
Square.prototype.draw = function() { 
    gotoXY(this.x,this.y); lineTo(this.x+this.side, this.y); // ... 
}; 

E 'importante che i metodi risiedono nel prototipo (che è come dovrebbe essere in ogni caso per motivi di prestazioni) in modo da poter chiamare i metodi di una classe super-via

SuperClass.prototype.aMethod.call(this, arg1, arg2); 

Con un po 'syntactic sugar, è possibile effettuare JS apparire come un linguaggio basato su classi classica:

+0

PS: invece di una funzione personalizzata 'clone()', puoi usare 'Object.create()' se la tua implementazione supporta ES5 – Christoph

+0

I mi piace, anche se probabilmente non implementerò la cosa sintattica sullo zucchero. Una domanda però - perché usare clone() e non ereditare da un'istanza? mi sembra che funzioni allo stesso modo ma usare clone() è meno leggibile. – Guss

+0

@Guss: a mio parere, utilizzare le istanze per l'ereditarietà è una cattiva pratica perché eseguirà il costruttore di super-classe; a seconda di ciò che effettivamente fa il costruttore, questo può avere effetti collaterali indesiderati; chiamare il costruttore della classe super esplicitamente nel costruttore della classe child è più pulito in quanto eseguirà il codice di inizializzazione solo quando si vuole effettivamente creare una nuova istanza. – Christoph

4

Douglas Crockford ha buoni articoli sia sull'eredità classical che su prototypal in Javascript, che dovrebbe essere un buon punto di partenza.

+3

Ho familiarità con gli articoli di Crockford e non mi piacciono. Richiede molto codice della piastra della caldaia (date un'occhiata al primo esempio di costruzione di un oggetto nel vostro link di eredità prototipale) o richiede diverse dozzine di linee di codice per lo "zucchero". O entrambi. E il risultato non sembra affatto di JavaScript con tutti i tipi di cose 'uber' e' beget' - lo scopo è che uno sviluppatore JavaScript addestrato possa guardare il codice e capire immediatamente che cosa sta succedendo. – Guss

+0

È difficile non ritrovarsi con un piatto di caldaia, ma provare a implementare la funzionalità che si desidera. E .. Per essere onesti: Ogni sviluppatore JavaScript addestrato deve avere dimestichezza con il lavoro di Crockford :) – roosteronacid

-1

Anche ispirato a Crockford, ma ho avuto esperienze positive con quello che lui chiama "ereditarietà funzionale" usando "funzioni di costruzione". YMMV.

UPDATE: Siamo spiacenti, ho dimenticato: è ancora necessario per aumentare Oggetto con un metodo superior per ottenere piacevole l'accesso a un metodo eccellente. Non è adatto a te, probabilmente.

var makeShape = function (x, y) { 
    that = {}; 
    that.x = x; 
    that.y = y; 
    that.draw = function() { 
     throw new Error("Arbitrary shapes cannot be drawn"); 
    } 
    return that; 
}; 

var makeSquare = function (x, y, side) { 
    that = makeShape(x, y); 
    that.side = side; 
    that.draw = function() { 
     gotoXY(that.x,that.y); lineTo(that.x+that.side, that.y); ... 
    } 
    return that; 
}; 
+0

Troppa piastra, ma grazie per aver provato :-) – Guss

1

OK, il trucco alla replica di un sistema di classe/istanza-style in JavaScript è che è possibile utilizzare solo l'ereditarietà di prototipi sulle istanze. Quindi è necessario essere in grado di creare un'istanza 'non-istanza' che viene utilizzata solo per l'ereditarietà e avere un metodo di inizializzazione separato dalla funzione di costruzione stessa.

Questo è il sistema minimo utilizzare (prima di aggiungere fronzoli), passando un valore speciale una tantum nel costruttore di averlo costruire un oggetto senza inizializzazione è:

Function.prototype.subclass= function() { 
    var c= new Function(
     'if (!(this instanceof arguments.callee)) throw(\'Constructor called without "new"\'); '+ 
     'if (arguments[0]!==Function.prototype.subclass._FLAG && this._init) this._init.apply(this, arguments); ' 
    ); 
    if (this!==Object) 
     c.prototype= new this(Function.prototype.subclass._FLAG); 
    return c; 
}; 
Function.prototype.subclass._FLAG= {}; 

L'uso di new Function() è un modo per evitare di chiudere inutilmente la sottoclasse(). Puoi sostituirlo con un'espressione più carina function() {...} se preferisci.

L'uso è relativamente pulito, e in generale come Python stile solo oggetti con sintassi leggermente goffo:

var Shape= Object.subclass(); 
Shape.prototype._init= function(x, y) { 
    this.x= x; 
    this.y= y; 
}; 
Shape.prototype.draw= function() { 
    throw new Error("Arbitrary shapes cannot be drawn"); 
}; 

var Square= Shape.subclass(); 
Square.prototype._init= function(x, y, side) { 
    Shape.prototype._init.call(this, x, y); 
    this.side= side; 
}; 
Square.prototype.draw= function() { 
    gotoXY(this.x, this.y); 
    lineTo(this.x+this.side, this.y); // ... 
}; 

scimmia-patching un builtin (Funzione) è un po 'discutibile, ma lo rende piacevole da leggere, e nessuno potrebbe voler for...in su una funzione.

+0

Perché costruttore 'Function'? – kangax

+0

'function() {...}' esegue una chiusura sull'ambito della funzione in cui viene utilizzata, lasciando riferimenti a argomenti e variabili locali in quella funzione. 'new Function()' lo evita. Anche se non importa in alcun modo, perché non ci sarà nulla di pesante nella chiusura. – bobince

+1

Sì, certo. Non sono sicuro del motivo per cui hai usato "pesante" 'Funzione' dove è sufficiente' function() {} '. – kangax

0

È possibile utilizzare il modello funzionale proposto da Crockford nel suo libro "JavaScript le parti buone". L'idea è usare closore per creare campi privati, e usare la funzione previlegata per accedere a questi campi. Ecco una delle soluzioni che soddisfano le vostre esigenze 6:

var people = function (info) { 
    var that = {}; 
    // private 
    var name = info.name; 
    var age = info.age; 
    // getter and setter 
    that.getName = function() { 
     return name; 
    }; 
    that.setName = function (aName) { 
     name = aName; 
    }; 
    that.getAge = function() { 
     return age; 
    }; 
    that.setAge = function (anAge) { 
     age = anAge; 
    }; 
    return that; 
}; 

var student = function (info) { 
    // super 
    var that = people(info); 
    // private 
    var major = info.major; 
    that.getMajor = function() { 
     return major; 
    }; 
    that.setMajor = function (aMajor) { 
     major = aMajor; 
    }; 
    return that; 
}; 

var itStudent = function (info) { 
    // super 
    var that = student(info); 
    var language = info.language; 
    that.getLanguage = function() { 
     return language; 
    }; 
    that.setLanguage = function (aLanguage) { 
     language = aLanguage; 
    }; 
    return that; 
}; 

var p = person({name : "Alex", age : 24}); 
console.debug(p.age); // undefined 
console.debug(p.getAge()); // 24 

var s = student({name : "Alex", age : 24, major : "IT"}); 
console.debug(s.getName()); // Alex 
console.debug(s.getMajor()); // IT 

var i = itStudent({name : "Alex", age : 24, major : "IT", language : "js"}); 
console.debug(i.language); // Undefined 
console.debug(i.getName()); // Alex 
console.debug(i.getMajor()); // IT 
console.debug(i.getLanguage()); // js 
+0

Non mi piace questa sintassi funzionale perché da un lato non sembra un codice orientato agli oggetti (quindi dannoso quando si cerca di far sì che i programmatori formati in OO classico lavorino con voi) e d'altro canto non si t ottenere il vantaggio dell'ereditarietà prototipica in cui è possibile utilizzare il modello mix-in. – Guss

1

Il modello più comune che ho trovato quando la ricerca di questa domanda è descritto sul Mozilla Developer Network. Ho aggiornato il loro esempio per includere una chiamata ad un metodo della superclasse e di mostrare il registro in un messaggio di avviso:

// Shape - superclass 
 
function Shape() { 
 
    this.x = 0; 
 
    this.y = 0; 
 
} 
 

 
// superclass method 
 
Shape.prototype.move = function(x, y) { 
 
    this.x += x; 
 
    this.y += y; 
 
    log += 'Shape moved.\n'; 
 
}; 
 

 
// Rectangle - subclass 
 
function Rectangle() { 
 
    Shape.call(this); // call super constructor. 
 
} 
 

 
// subclass extends superclass 
 
Rectangle.prototype = Object.create(Shape.prototype); 
 
Rectangle.prototype.constructor = Rectangle; 
 

 
// Override method 
 
Rectangle.prototype.move = function(x, y) { 
 
    Shape.prototype.move.call(this, x, y); // call superclass method 
 
    log += 'Rectangle moved.\n'; 
 
} 
 

 
var log = ""; 
 
var rect = new Rectangle(); 
 

 
log += ('Is rect an instance of Rectangle? ' + (rect instanceof Rectangle) + '\n'); // true 
 
log += ('Is rect an instance of Shape? ' + (rect instanceof Shape) + '\n'); // true 
 
rect.move(1, 1); // Outputs, 'Shape moved.' 
 
alert(log);

  1. Nei cinque anni da quando lei ha chiesto a questa domanda, sembra che il supporto del browser per l'ereditarietà sia migliorato, quindi non penso che tu abbia bisogno di una libreria esterna.
  2. Questa è la tecnica più minimale che abbia mai visto, non so se la consideri troppo calda.
  3. Utilizza il prototipo, come richiesto, quindi l'aggiunta di nuovi metodi al genitore dovrebbe fornire anche gli oggetti figlio.
  4. È possibile visualizzare il concatenamento del costruttore nell'esempio.
  5. Anche le chiamate di tipo super sono presenti nell'esempio.
  6. Non sono sicuro che se si sente JavaScript-ish, dovrai decidere tu stesso.
+0

Sembra bello, e si sente JavaScript-ish, ma un bel po 'di codice: -/ancora, una buona soluzione. – Guss

Problemi correlati