2012-02-28 8 views
7

Vengo a Javascript desde un fondo en Python y Smalltalk, y aprecio el linage de Self y Lisp en el idioma. Con ECMAScript5, quería probar mi mano en OO prototípico sin el nuevo operador.Prototypal OO con constructores Object.create y named

Restricciones:

  • opcional operador new para crear clases
  • cadena
  • prototipo debe ser correcta para instanceof
  • constructores llevan el nombre de WebInspector compatibilidad de depuración
  • alloc init()() Secuencia de creación similares. Objective-C y Python

Aquí está mi intento de la implementación para cumplir t él criterios:

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

Y parece que funciona de una forma sencilla maqueta:

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

Utilización de la clase como una función de la fábrica:

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 utilizar el nuevo operador:

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

¿Se me pasó por alto? características de Prototypal OO en mi implementación? ¿Hay convenciones o interacciones de JavaScript para las que debería hacer cambios? En resumen, ¿qué son los "gotcha" aquí y hay mejoras obvias que hacer?

Yo quería ser DRYer con las definiciones de constructor, pero encontré que la propiedad del nombre de una función no es escribible, y eso es lo que admite los nombres de los objetos del Inspector WebKit. Pude construir una evaluación para lograr lo que quería, pero ... asco.

+0

wow, nunca he visto usando javascript esta manera ... –

+0

eval es peligroso si al procesar datas más "no confiables", pero no veo por qué no se debería utilizar eval, de hecho, cuando se hace esto [ astring] está utilizando algún tipo de evaluación, ya que no hay matrices asociativas en js. – mpm

+1

@camus sí, e incluso usé una expresión regular '/ \ w + /' para asegurarme de que solo era un nombre de método válido. Pero parece que usar un martillo para instalar una tachuela: sería mucho más elegante si se le permitiera establecer la propiedad del nombre de una función. Javascript es un lenguaje prototipo, después de todo ... –

Respuesta

8

Editar: Oh, ya veo la pregunta. Respuesta: no, lo tienes exactamente correcto. La única forma de establecer el nombre de una función es cuando se utilizan declaraciones de funciones, lo que significa en el momento de la evaluación. Por lo tanto, debe tenerlo en el código fuente (que eval es la puerta de atrás). Respondí una pregunta más simple previamente pero con la misma idea: Minor drawback with Crockford Prototypical Inheritance. Otro recurso sobre este tema es http://kangax.github.com/nfe/

Existe un movimiento para usar la propiedad displayName con el fin de divorciar el nombre inmutable de una función de su apariencia de depurador. Esto se implementa en Firefox y algunas otras cosas, y es un hombre de paja para su inclusión en es6 pero hasta el momento no es parte de la especificación provisional: http://wiki.ecmascript.org/doku.php?id=strawman:name_property_of_functions

Aquí es un papel de algunas personas que trabajan en cromo sobre el tema de las funciones de nomenclatura http://querypoint-debugging.googlecode.com/files/NamingJSFunctions.pdf

Y aquí es la cuestión de cromo discutir por qué no se ha implementado todavía: http://code.google.com/p/chromium/issues/detail?id=17356

Onto respuesta original:

lo que se propuso llevar a cabo, has hecho bien.Un par de ejemplos de cosas tipo similar que he hecho:

En primer lugar es una función simple 'heredable', que le permite hacer cosas como:

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

La otra es en la creación de estructuras para su uso con libffi (node-ffi). Esencialmente, aquí tienes herencia de constructor y herencia de prototipo. Crea constructores que heredan de los constructores, que crean instancias de estructuras. https://github.com/Benvie/node-ffi-tools/blob/master/lib/Struct.js

El uso de eval se requiere para crear una función con nombre, por lo que si necesita un constructor con nombre. No tengo reparos en usarlo donde sea necesario.

+0

los enlaces combinados, muestras de código y revisión de código son inmensamente útiles, gracias. –

4

Shane Te puedo decir que vienes de tu fondo Javascript se comporta de una manera diferente a lo que estás tratando de hacer. Antes de odiar de inmediato esa respuesta, sigue leyendo ...

Me encanta OOP y vengo de un entorno amigable con OOP. Me ha llevado un tiempo rodear los objetos de Prototype (piense en los métodos de prototipos como funciones estáticas ... que solo funcionan en objetos inicializados). Muchas de las cosas que se hacen en javascript se sienten muy "incorrectas" y provienen de un lenguaje que usa una buena seguridad y encapsulación.

La única razón real para usar el "nuevo" operador es si está creando instancias múltiples o tratando de evitar que se ejecute algún tipo de lógica hasta que se desencadene un evento.

Sé que esto no es una respuesta ... era demasiado para escribir en los comentarios. Para realmente obtener una mejor perspectiva, debes crear objetos y vars y ver tu DOM ... te darás cuenta de que puedes llegar literalmente a cualquier lugar y desde cualquier contexto gracias a esto. Encontré these articles muy ingenioso para completar los detalles.

Lo mejor de la suerte.

ACTUALIZACIÓN

Así que si usted no necesita un método de fábrica para crear diferentes instancias del mismo objeto aquí hay algunos consejos que desearía haber escuchado:

  • Por favor, por favor, por favor olvídate de todas las reglas que has aprendido de la encapsulación OO que conoces. Digo esto porque al usar sus protoobjetos, objetos literales y objetos de instancia juntos, habrá tantas maneras de acceder a las cosas de una manera que no será natural para usted.
  • Si planeas utilizar cualquier tipo de abstracción o herencia, te sugiero que juegues con DOM. Crea algunos objetos y búscalos en el DOM y observa lo que está sucediendo. Puedes simular tu herencia usando prototipos existentes (¡me ha costado no tener interfaces y clases abstractas para ampliar!).
  • Sepa que todo en javascript es un objeto ... esto le permite hacer cosas realmente interesantes (funciones de paso, funciones de retorno, métodos de devolución de llamada sencillos). Mire las llamadas javascript '.call()', '.apply()' y '.bind()'.

Conocer estas 2 cosas lo ayudará a resolver su problema. Para mantener su DOM limpio y seguir una buena práctica de desarrollo de JavaScript, debe mantener el espacio de nombres global limpio (referencia de ventana en el DOM). Esto también te hará sentir "mejor" sobre todas las cosas que sientes que están "equivocadas" con lo que estás haciendo. Y se constante.

Estas son cosas que aprendí por ensayo y error.Javascript es un lenguaje excelente y único si puede abarcar su diferencia con los lenguajes OO clásicos. ¡Buena suerte!

+0

Gracias por la información. Tengo en mente un caso de uso específico que necesita el estilo de método de fabricación de clases sin estilo de fábrica, pero que aún así sea compatible con las expectativas de JavaScript en otros lugares. –

+1

@ShaneHolloway Bueno, genial. Entonces deberías definitivamente usar la palabra clave 'nueva' para crear tus objetos. Actualizaré mi respuesta para ver si te ayuda. – jjNford

+0

gracias por la gran experiencia y consejos. Estoy de acuerdo en que la programación prototípica es un poco torpe para comenzar. –

1

No puede crear constructores con nombre sin eval. Todo lo que encontrará es un constructor con una propiedad oculta con el nombre si la biblioteca implementa espacios de nombres.

Respondiendo al resto de la pregunta, hay una gran cantidad de javascript bibliotecas de clases. Puedes escribir el tuyo pero es un poco desordenado para comprender e implementar un sistema de herencia correcto si quieres hacerlo de la manera correcta.

recientemente he escrito mi propia biblioteca de clases tratando de mejorar todas las bibliotecas: Classful JS. Creo que vale la pena echarle un vistazo porque su diseño simple, uso y algunas otras mejoras, como llamar a cualquier supermétodo de una subclase (no he visto ninguna biblioteca que pueda hacer eso, todos ellos solo pueden llamar al súper método que se está utilizando). primordial).

3

Puede que esto no responde a su pregunta y esto probablemente no hace todo lo que quiere, pero esto es otra manera de ir sobre OO JavaScript. Me gusta principalmente por la sintaxis; es más fácil para mí asimilar, pero no tengo tu experiencia.

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

Me gusta este uso de '.call' para inicializar una instancia, esto es muy limpio, pero ¿por qué usa nombres de métodos que comienzan con una letra mayúscula? De acuerdo con las convenciones de nombres en JavaScript, todos los nombres deben comenzar con una letra minúscula excepto nombres de constructores algunos nombres de objetos de host – Luc125

+1

No me gustan las convenciones de nomenclatura que he visto, donde casi todo es camelCase. PascalCase mis "clases" y "métodos públicos" y camelCase todo lo demás para ayudar con la legibilidad. – JoshNaro

+0

He visto este idioma de Javascript OOP en varios lugares; es esencialmente el ejemplo pseudoclásico de Crockford sin sus métodos de ayuda. Desafortunadamente, mi caso de uso requiere que las "clases" funcionen correctamente como métodos de fábrica y como constructores estándar de Javascript. –