2012-02-28 17 views
7

Arriva a Javascript da uno sfondo in Python e Smalltalk e apprezzo la linear di Self e Lisp nella lingua. Con ECMAScript5, volevo provare la mia mano a OO prototipo senza il nuovo operatore.OO prototipo con Object.create e costruttori con nome

Vincoli:

  • nuovo operatore opzionale per creare classi
  • prototipo catena deve essere corretto per instanceof
  • costruttori chiamato per WebInspector supporto per il debugging
  • alloc() init() sequenza di creazione simili. Objective-C e Python

Ecco il mio tentativo di implementazione per soddisfare t lui criteri:

function subclass(Class, Base) { 
    "use strict"; 
    function create(self, args) { 
     if (!(self instanceof this)) 
      self = Object.create(this.prototype); 
     var init = self.__init__; 
     return init ? init.apply(self, args) : self; 
    } 
    if (Base instanceof Function) Base = Base.prototype; 
    else if (Base===undefined) Base = Object.prototype; 

    Class.prototype = Object.create(Base); 
    Class.prototype.constructor = Class; 
    Class.create = create; 

    Class.define = function define(name, fn) { return Class.prototype[name] = fn; }; 
    Class.define('__name__', Class.name); 
    return Class; 
} 

e sembra di lavorare in un semplice mockup:

function Family(){return Family.create(this, arguments)} 
subclass(Family, Object); 
Family.define('__init__', function __init__(n){this.name=n; return this;}); 

function Tribe(){return Tribe.create(this, arguments)} 
subclass(Tribe, Family); 
function Genus(){return Genus.create(this, arguments)} 
subclass(Genus, Tribe); 
function Species(){return Species.create(this, arguments)} 
subclass(Species, Genus); 

Uso della classe come una funzione di fabbrica:

var dog = Species('dog'); 
console.assert(dog instanceof Object); 
console.assert(dog instanceof Family); 
console.assert(dog instanceof Tribe); 
console.assert(dog instanceof Genus); 
console.assert(dog instanceof Species); 

o utilizzando l'operatore new:

var cat = new Species('cat'); 
console.assert(cat instanceof Object); 
console.assert(cat instanceof Family); 
console.assert(cat instanceof Tribe); 
console.assert(cat instanceof Genus); 
console.assert(cat instanceof Species); 

console.assert(Object.getPrototypeOf(dog) === Object.getPrototypeOf(cat)) 

Ho trascurato necessario caratteristiche di OO prototipo nella mia implementazione? Ci sono convenzioni o interazioni Javascript per cui dovrei apportare modifiche? In sintesi, quali sono i "gotcha" qui, e ci sono miglioramenti evidenti da apportare?

Volevo essere DRYer con le definizioni del costruttore, ma ho trovato che la proprietà del nome di una funzione non è scrivibile, e questo è ciò che supporta i nomi degli oggetti di WebKit Inspector. Sono stato in grado di costruire una valutazione per realizzare ciò che volevo, ma ... schifo.

+0

wow, non ho mai visto utilizzando JavaScript in questo modo ... –

+0

eval è pericoloso se quando si elabora dati "non attendibili", ma non vedo il motivo per cui non si dovrebbe usare eval, infatti quando si esegue questa operazione [ astring] stai usando qualche tipo di valutazione, in quanto non ci sono array associativi in ​​js. – mpm

+1

@camus yes, e ho anche usato un regexp '/ \ w +/per assicurarmi che fosse solo un nome di metodo valido. Ma sembra che usare un martello per installare una virata - Sarebbe molto più elegante se fosse permesso di impostare la proprietà del nome di una funzione. Javascript è un linguaggio prototipo, dopotutto ... –

risposta

8

Modifica: Oh Vedo la domanda ora. Risposta: no, hai capito bene. L'unico modo per impostare il nome di una funzione è quando si usano le dichiarazioni di una funzione, il che significa al momento della valutazione. Di conseguenza è necessario averlo nel codice sorgente (che eval è la porta posteriore in). Ho già risposto ad una domanda più semplice ma con lo stesso succo: Minor drawback with Crockford Prototypical Inheritance. Un'altra risorsa su questo argomento è http://kangax.github.com/nfe/

c'è movimento di utilizzare la proprietà displayName al fine di separare il nome immutabile di una funzione dal suo aspetto debugger. Questo è implementato in Firefox e alcune altre cose, ed è uno strawman per l'inclusione in es6 ma non ancora parte delle specifiche: http://wiki.ecmascript.org/doku.php?id=strawman:name_property_of_functions

Ecco un documento di alcune persone che lavorano su Chrome sull'argomento delle funzioni di denominazione http://querypoint-debugging.googlecode.com/files/NamingJSFunctions.pdf

E qui è il problema di cromo discutere perché non è ancora implementato: http://code.google.com/p/chromium/issues/detail?id=17356

Onto risposta originale:

Cosa si inizia a realizzare, hai fatto bene.Un paio di esempi di roba tipo simile ho fatto:

La prima è una semplice funzione 'ereditarie', che ti permette di fare cose come:

var MyObjCtor = heritable({ 
    constructor: function MyObj(){ /* ctor stuff */}, 
    super: SomeCtor, 
    prop1: val, 
    prop2: val, 
    /**etc..*/ 
}); 


function heritable(definition){ 
    var ctor = definition.constructor; 
    Object.defineProperty(ctor, 'super', { 
    value: definition.super, 
    configurable: true, 
    writable: true 
    }); 
    ctor.prototype = Object.create(ctor.super.prototype); 
    delete definition.super; 

    Object.keys(definition).forEach(function(prop){ 
    var desc = Object.getOwnPropertyDescriptor(definition, prop); 
    desc.enumerable = false; 
    Object.defineProperty(ctor.prototype, prop, desc); 
    }); 

    function construct(){ 
    var obj = new (ctor.bind.apply(ctor, [].concat.apply([null], arguments))); 
    ctor.super.call(obj); 
    return obj; 
    } 

    construct.prototype = ctor.prototype; 

    return construct; 
} 

L'altro è nel creare le strutture per l'utilizzo con libffi (node-FFI). Essenzialmente qui si ha sia l'ereditarietà del costruttore che l'ereditarietà del prototipo. Si creano costruttori che ereditano dai costruttori, che creano istanze di strutture. https://github.com/Benvie/node-ffi-tools/blob/master/lib/Struct.js

L'utilizzo di eval è necessario per creare una funzione con nome, è così che se è necessario un costruttore denominato. Non ho remore a usarlo dove necessario.

+0

i collegamenti combinati, gli esempi di codice e la revisione del codice sono immensamente utili - grazie. –

4

Shane Posso dirti che vieni dal tuo background Javascript si comporta in modo diverso da quello che stai cercando di fare. Prima di odiare immediatamente la risposta continua a leggere ...

Amo OOP e provengo da uno sfondo molto OOP. Mi ci vuole un po 'per avvolgere la mia mente intorno agli oggetti prototipo (pensa ai metodi prototipo come funzione statica ... che funzionano solo su oggetti inizializzati). Molte cose che vengono fatte in javascript si sentono molto "sbagliate" in un linguaggio che usa buona sicurezza e incapsulamento.

L'unico vero motivo per utilizzare l'operatore "nuovo" è se si creano più istanze o si tenta di impedire l'esecuzione di qualche tipo di logica finché non viene attivato un evento.

So che questa non è una risposta ... era semplicemente troppo per digitare i commenti. Per avere una prospettiva migliore dovresti creare oggetti e oggetti e vedere il tuo DOM ... ti renderai conto che puoi letteralmente andare da qualsiasi contesto a causa di ciò. Ho trovato these articles molto ingegnoso per la compilazione di dettagli.

Buona fortuna.

UPDATE

Quindi, se avete bisogno di un metodo factory per creare diverse istanze dello stesso oggetto, ecco alcuni consigli vorrei aver sentito:

  • favore, per favore, si prega di dimentica tutte le regole che hai appreso dall'incapsulazione OO che conosci. Dico questo perché quando si usano gli oggetti proto, gli oggetti letterali e gli oggetti istanza insieme ci saranno tanti modi per accedere alle cose in un modo che non sarà naturale per te.
  • Se si prevede di utilizzare qualsiasi tipo di astrazione o eredità, suggerirei di giocare con DOM. Crea alcuni oggetti e cercali nel DOM e vedi cosa sta succedendo. Puoi simulare la tua eredità usando i prototipi esistenti (mi ha ucciso non avendo interfacce e classi astratte da estendere!).
  • Sappi che tutto in javascript è un oggetto ... questo ti permette di fare cose molto interessanti (passare le funzioni in giro, restituire funzioni, creare metodi di callback facili). Guarda le chiamate javascript ".call()", ".apply()" e ".bind()".

Conoscere queste 2 cose ti aiuta a risolvere la soluzione. Per mantenere pulito il tuo DOM e seguire una buona pratica di sviluppo di javascript, tieni il dominio globale pulito (riferimento alla finestra nel DOM). Questo ti farà sentire "migliore" su tutte le cose che senti "sbagliate" con quello che stai facendo. Ed essere coerenti

Queste sono cose che ho imparato per tentativi ed errori.Javascript è un linguaggio eccezionale e unico se riesci a comprendere la sua differenza dai classici linguaggi OO. In bocca al lupo!

+0

Grazie per l'intuizione. Ho in mente un caso d'uso specifico che ha bisogno dello stile del metodo factory new-less per la creazione di classi, ma è comunque compatibile con le aspettative di javascript altrove. –

+1

@ShaneHolloway Ok, bello. Quindi dovresti usare la parola chiave 'new' per creare i tuoi oggetti. Aggiornerò la mia risposta per vedere se ti aiuta. – jjNford

+0

grazie per l'ottima esperienza e suggerimenti. Sono d'accordo sul fatto che la programmazione prototipale sia un po 'distorta per iniziare. –

1

Non è possibile creare costruttori denominati senza eval. Tutto quello che troverai è un costruttore con una proprietà nascosta con il nome se la libreria implementa gli spazi dei nomi.

Rispondendo al resto della domanda, ci sono molte librerie di classi javascript. Puoi scrivere il tuo ma è un po 'complicato capire e implementare un sistema di ereditarietà corretto se vuoi farlo nel modo giusto.

Ho recentemente scritto la mia libreria di classi cercando di migliorare tutte le librerie: Classful JS. Penso che valga la pena dare un'occhiata perché il suo design semplice, l'uso e altri miglioramenti come chiamare qualsiasi super metodo da una sottoclasse (non ho visto nessuna libreria in grado di farlo, tutti possono solo chiamare il metodo super che è prioritario).

3

Questo potrebbe non rispondere alla tua domanda e questo probabilmente non fa tutto ciò che vuoi, ma questo è un altro modo per andare su OO JavaScript. Mi piace soprattutto per la sintassi; è più facile per me lamentarmi, ma non ho il tuo background.

// ------- Base Animal class ----------------------- 
var Animal = function(name) { 
    this.name = name; 
}; 

Animal.prototype.GetName = function() { 
    return this.name; 
}; 
// ------- End Base Animal class ----------------------- 

// ------- Inherited Dog class ----------------------- 
var Dog = function(name, breed) { 
    Animal.call(this, name); 
    this.breed = breed; 
}; 

Dog.prototype = new Animal(); 
Dog.prototype.constructor = Dog; 

Dog.prototype.GetBreed = function() { 
    return this.breed; 
}; 

Dog.prototype.Bark = function() { 
    alert(this.GetName() + ' the ' + this.GetBreed() + ' says "Arf!"'); 
}; 
// ------- End Inherited Dog class ----------------------- 

// Usage 
var harper = new Dog('Harper', 'miniature pinscher'); 
harper.Bark(); 
+1

Mi piace questo uso di '.call' per inizializzare un'istanza, questo è molto pulito, ma perché usi i nomi dei metodi che iniziano con una lettera maiuscola - secondo le convenzioni di denominazione in JavaScript tutti i nomi devono iniziare con una lettera minuscola eccetto i nomi del costruttore e alcuni nomi di oggetti host – Luc125

+1

Non mi piacciono le convenzioni di denominazione che ho visto dove quasi tutto è CamelCase. I PascalCase le mie "classi" e "metodi pubblici" e camelCase tutto il resto per facilitare la leggibilità. – JoshNaro

+0

Ho visto questo idioma JavaScript OOP in diversi punti: è essenzialmente l'esempio pseudoclassico di Crockford senza i suoi metodi di supporto. Sfortunatamente, il mio caso d'uso richiede che le "classi" funzionino correttamente sia come metodi di fabbrica sia come costruttori Javascript standard. –

Problemi correlati