2010-03-14 12 views
12

En Douglas Crockford's JavaScript: The Good Parts recomienda que usemos la herencia funcional. He aquí un ejemplo:JavaScript herencia funcional con prototipos

var mammal = function(spec, my) { 
    var that = {}; 
    my = my || {}; 

    // Protected 
    my.clearThroat = function() { 
     return "Ahem"; 
    }; 

    that.getName = function() { 
     return spec.name; 
    }; 

    that.says = function() { 
     return my.clearThroat() + ' ' + spec.saying || ''; 
    }; 

    return that; 
}; 

var cat = function(spec, my) { 
    var that = {}; 
    my = my || {}; 

    spec.saying = spec.saying || 'meow'; 
    that = mammal(spec, my); 

    that.purr = function() { 
     return my.clearThroat() + " purr"; 
    }; 

    that.getName = function() { 
     return that.says() + ' ' + spec.name + ' ' + that.says(); 
    }; 

    return that; 
}; 

var kitty = cat({name: "Fluffy"}); 

El principal problema que tengo con esto es que cada vez que haga una mammal o cat el intérprete de JavaScript tiene que volver a compilar todas las funciones en el mismo. Es decir, no puedes compartir el código entre instancias.

Mi pregunta es: ¿cómo puedo hacer que este código sea más eficiente? Por ejemplo, si estaba haciendo miles de objetos cat, ¿cuál es la mejor manera de modificar este patrón para aprovechar el objeto prototype?

+0

"* tiene que volver a compilar todas las funciones en él. Es decir, no puede compartir el código entre instancias * "- No. El código es compartido, solo se deben crear diferentes objetos de función con diferentes valores de ámbito. No es una gran sobrecarga. – Bergi

Respuesta

8

Bueno, simplemente no puede hacerlo de esa manera si planea hacer un montón de mammal o cat. En cambio, hágalo a la manera antigua (prototipo) y herede por propiedad. Todavía puede hacer los constructores de la manera que tiene arriba, pero en lugar de that y my usa el implícito this y alguna variable que representa la clase base (en este ejemplo, this.mammal).

cat.prototype.purr = function() { return this.mammal.clearThroat() + "purr"; } 

que haría uso de otro nombre de my de acceso a la base y lo almacenan en this en el cat constructor. En este ejemplo utilicé mammal, pero esto podría no ser el mejor si desea tener estática acceso al objeto global mammal. Otra opción es nombrar la variable base.

+0

El problema al vincular el código 'my'objeto a través de' this.mammal' es que perdería la privacidad del mismo, ya que alguien podría hacer 'cat.mammal.clearThroat()' y acceder al método protegido. – cdmckay

+2

Es cierto. Si quieres privacidad luego debe hacerlo por convención, por ejemplo, precediendo sus métodos protegidos con guiones bajos. Si eso parece demasiado flojo, considere que incluso este patrón de "herencia funcional" es en sí mismo una convención. Javascript no es un lenguaje "aseado", es mucho más "rudo y listo". Haciéndolo funcionar de una manera totalmente extraña cuesta una penalización en el rendimiento, como descubriste. Sin embargo, no hagas demasiado daño: se necesitan decenas de miles de creaciones de objeto para impactar de manera significativa su velocidad de carrera general, dada su e xample. – Plynx

+2

"alguien podría hacer' cat.mammal.clearThroat() 'y acceder al método protegido" - no todos los problemas deben resolverse con código. Con el mayor respeto posible, no está escribiendo Windows. Si alguien usa mal tu código, es increíble, porque significa que alguien está usando tu código. Mucho mejor para enfocarse en hacer un código que sea útil y fácil de usar correctamente, en lugar de protegerse contra problemas hipotéticos. –

0

si quieres privacidad y no te gusta protyping que puede o-no como este enfoque:

(Obs .: se utiliza jQuery.extend)

var namespace = namespace || {}; 

// virtual base class 
namespace.base = function (sub, undefined) { 

    var base = { instance: this }; 

    base.hierarchy = []; 

    base.fn = { 

     // check to see if base is of a certain class (must be delegated) 
     is: function (constr) { 

      return (this.hierarchy[this.hierarchy.length - 1] === constr); 
     }, 

     // check to see if base extends a certain class (must be delegated) 
     inherits: function (constr) { 

      for (var i = 0; i < this.hierarchy.length; i++) { 

       if (this.hierarchy[i] == constr) return true; 
      } 
      return false; 
     }, 

     // extend a base (must be delegated) 
     extend: function (sub) { 

      this.hierarchy.push(sub.instance.constructor); 

      return $.extend(true, this, sub); 
     }, 

     // delegate a function to a certain context 
     delegate: function (context, fn) { 

      return function() { return fn.apply(context, arguments); } 
     }, 

     // delegate a collection of functions to a certain context 
     delegates: function (context, obj) { 

      var delegates = {}; 

      for (var fn in obj) { 

       delegates[fn] = base.fn.delegate(context, obj[fn]); 
      } 

      return delegates; 
     } 
    }; 

    base.public = { 
     is: base.fn.is, 
     inherits: base.fn.inherits 
    }; 

    // extend a sub-base 
    base.extend = base.fn.delegate(base, base.fn.extend); 

    return base.extend(sub); 
}; 

namespace.MyClass = function (params) { 

    var base = { instance: this }; 

    base.vars = { 
     myVar: "sometext" 
    } 

    base.fn = { 
     init: function() { 

      base.vars.myVar = params.myVar; 
     }, 

     alertMyVar: function() { 

      alert(base.vars.myVar); 
     } 

    }; 

    base.public = { 
     alertMyVar: base.fn.alertMyVar 
    }; 

    base = namespace.base(base); 

    base.fn.init(); 

    return base.fn.delegates(base,base.public); 
}; 

newMyClass = new namespace.MyClass({myVar: 'some text to alert'}); 
newMyClass.alertMyVar(); 

el único inconveniente es que debido del ámbito de privacidad solo puede ampliar las clases virtuales y no las clases instanciables.

aquí hay un ejemplo de cómo extiendo el namespace.base, para vincular/desvincular/disparar eventos personalizados.

// virtual base class for controls 
namespace.controls.base = function (sub) { 

    var base = { instance: this }; 

    base.keys = { 
     unknown: 0, 
     backspace: 8, 
     tab: 9, 
     enter: 13, 
     esc: 27, 
     arrowUp: 38, 
     arrowDown: 40, 
     f5: 116 
    } 

    base.fn = { 

     // bind/unbind custom events. (has to be called via delegate) 
     listeners: { 

      // bind custom event 
      bind: function (type, fn) { 

       if (fn != undefined) { 

        if (this.listeners[type] == undefined) { 
         throw (this.type + ': event type \'' + type + '\' is not supported'); 
        } 

        this.listeners[type].push(fn); 
       } 

       return this; 
      }, 

      // unbind custom event 
      unbind: function (type) { 

       if (this.listeners[type] == undefined) { 
        throw (this.type + ': event type \'' + type + '\' is not supported'); 
       } 

       this.listeners[type] = []; 

       return this; 
      }, 

      // fire a custom event 
      fire: function (type, e) { 

       if (this.listeners[type] == undefined) { 
        throw (this.type + ': event type \'' + type + '\' does not exist'); 
       } 

       for (var i = 0; i < this.listeners[type].length; i++) { 

        this.listeners[type][i](e); 
       } 

       if(e != undefined) e.stopPropagation(); 
      } 
     } 
    }; 

    base.public = { 
     bind: base.fn.listeners.bind, 
     unbind: base.fn.listeners.unbind 
    }; 

    base = new namespace.base(base); 

    base.fire = base.fn.delegate(base, base.fn.listeners.fire); 

    return base.extend(sub); 
}; 
1

Deja que te presente a la herencia clásica que nunca usa prototype. Este es un ejercicio de codificación malo, pero le enseñará el verdadero Herencia Clásica que siempre se compara con herencia de prototipos:

Hacer un custructor:

function Person(name, age){ 
    this.name = name; 
    this.age = age; 
    this.sayHello = function(){return "Hello! this is " + this.name;} 
} 

realice otra cunstructor que herede de ella:

function Student(name, age, grade){ 
    Person.apply(this, [name, age]); 
    this.grade = grade 
} 

¡Muy simple! Student llamadas (se aplica) Person en sí mismo con name y age argumentos se ocupa de grade argumentos por sí mismo.

Ahora hagamos una instancia de Student.

var pete = new Student('Pete', 7, 1); 

Fuera pete objeto contendrá ahora name, age, grade y sayHello propiedades. Es posee todas esas propiedades. No están vinculados al Person a través del prototipo. Si cambiamos Person a esto:

function Person(name, age){ 
    this.name = name; 
    this.age = age; 
    this.sayHello = function(){ 
    return "Hello! this is " + this.name + ". I am " this.age + " years old"; 
    } 
} 

se pete sin Recieve la actualización. Si llamamos al pete.sayHello, ti devolverá Hello! this is pete. No recibirá la nueva actualización.

0

Para uso adecuado herencia basada Javascript-prototipo que podría utilizar fastClasshttps://github.com/dotnetwise/Javascript-FastClass

Usted tiene el más simple inheritWith sabor:

var Mammal = function (spec) { 
    this.spec = spec; 
}.define({ 
    clearThroat: function() { return "Ahem" }, 
    getName: function() { 
     return this.spec.name; 
    }, 
    says: function() { 
     return this.clearThroat() + ' ' + spec.saying || ''; 
    } 
}); 

var Cat = Mammal.inheritWith(function (base, baseCtor) { 
    return { 
     constructor: function(spec) { 
      spec = spec || {}; 
      baseCtor.call(this, spec); 
     }, 
     purr: function() { 
      return this.clearThroat() + " purr"; 
     }, 
     getName: function() { 
      return this.says() + ' ' + this.spec.name + this.says(); 
     } 
    } 
}); 

var kitty = new Cat({ name: "Fluffy" }); 
kitty.purr(); // Ahem purr 
kitty.getName(); // Ahem Fluffy Ahem 

Y si se está muy preocupado por el rendimiento entonces usted tiene el sabor fastClass:

var Mammal = function (spec) { 
    this.spec = spec; 
}.define({ 
    clearThroat: function() { return "Ahem" }, 
    getName: function() { 
     return this.spec.name; 
    }, 
    says: function() { 
     return this.clearThroat() + ' ' + spec.saying || ''; 
    } 
}); 

var Cat = Mammal.fastClass(function (base, baseCtor) { 
    return function() { 
     this.constructor = function(spec) { 
      spec = spec || {}; 
      baseCtor.call(this, spec); 
     }; 
     this.purr = function() { 
      return this.clearThroat() + " purr"; 
     }, 
     this.getName = function() { 
      return this.says() + ' ' + this.spec.name + this.says(); 
     } 
    } 
}); 

var kitty = new Cat({ name: "Fluffy" }); 
kitty.purr(); // Ahem purr 
kitty.getName(); // Ahem Fluffy Ahem 

Por cierto, su código inicial no hace ningún sentido pero lo he respetado literalmente.

fastClass utilidad:

Function.prototype.fastClass = function (creator) { 
    var baseClass = this, ctor = (creator || function() { this.constructor = function() { baseClass.apply(this, arguments); } })(this.prototype, this) 

    var derrivedProrotype = new ctor(); 

    if (!derrivedProrotype.hasOwnProperty("constructor")) 
     derrivedProrotype.constructor = function() { baseClass.apply(this, arguments); } 

    derrivedProrotype.constructor.prototype = derrivedProrotype; 
    return derrivedProrotype.constructor; 
}; 

inheritWith utilidad:

Function.prototype.inheritWith = function (creator, makeConstructorNotEnumerable) { 
    var baseCtor = this; 
    var creatorResult = creator.call(this, this.prototype, this) || {}; 
    var Derrived = creatorResult.constructor || 
    function defaultCtor() { 
     baseCtor.apply(this, arguments); 
    }; 
    var derrivedPrototype; 
    function __() { }; 
    __.prototype = this.prototype; 
    Derrived.prototype = derrivedPrototype = new __; 

    for (var p in creatorResult) 
     derrivedPrototype[p] = creatorResult[p]; 

    if (makeConstructorNotEnumerable && canDefineNonEnumerableProperty) //this is not default as it carries over some performance overhead 
     Object.defineProperty(derrivedPrototype, 'constructor', { 
      enumerable: false, 
      value: Derrived 
     }); 

    return Derrived; 
}; 

define utilidad:

Function.prototype.define = function (prototype) { 
    var extendeePrototype = this.prototype; 
    if (prototype) 
     for (var p in prototype) 
      extendeePrototype[p] = prototype[p]; 
    return this; 
} 

[* Nota, yo soy el autor del paquete de código abierto y los nombres de los mismos métodos podrían ser renombrados en future` *]

Cuestiones relacionadas