es6 - object.create javascript
Herencia prototípica-escribiendo (2)
Esta pregunta ya tiene una respuesta aquí:
Así que tengo estos 2 ejemplos, de javascript.info:
Ejemplo 1:
var animal = {
eat: function() {
alert( "I''m full" )
this.full = true
}
}
var rabbit = {
jump: function() { /* something */ }
}
rabbit.__proto__ = animal
rabbit.eat()
Ejemplo 2:
function Hamster() { }
Hamster.prototype = {
food: [],
found: function(something) {
this.food.push(something)
}
}
// Create two speedy and lazy hamsters, then feed the first one
speedy = new Hamster()
lazy = new Hamster()
speedy.found("apple")
speedy.found("orange")
alert(speedy.food.length) // 2
alert(lazy.food.length) // 2 (!??)
Comience desde el ejemplo 2: cuando el código alcanza el valor de speedy.found
, no encuentra ninguna propiedad found
en speedy
, por lo que sube al prototipo y lo cambia allí. Es por eso que food.length
es igual para ambos hamsters, en otras palabras, tienen el mismo estómago.
De esto entiendo, que al escribir y agregar una nueva propiedad que no existe, el intérprete subirá la cadena del prototipo hasta que encuentre la propiedad, y ENTONCES la cambie.
PERO en el Ejemplo 1 sucede algo más:
ejecutamos rabbit.eat
, que cambia rabbit.full
. full
propiedad full
no se encuentra en ninguna parte, por lo que debería subir la cadena del prototipo a (¿objetar?), y bueno, no estoy seguro de lo que sucede aquí. En este ejemplo, la propiedad full
de rabbit
se crea y cambia, mientras que en el primer ejemplo sube la cadena de prototipos porque no puede encontrar la propiedad.
Estoy confundido y no puedo ver por qué sucede esto.
Los prototipos NO se instancian para cada instancia de un objeto.
Hamster.prototype.food = []
Cada instancia de Hamster compartirá esa matriz
Si necesita (y lo hace en este caso) instancias separadas de colecciones de alimentos para cada Hámster, debe crear la propiedad en la instancia. Por ejemplo:
function Hamster() {
this.food = [];
}
Para responder a su pregunta sobre el Ejemplo 1, si no encuentra la propiedad en ninguna parte de la cadena de prototipos, crea la propiedad en el objeto de destino.
Introducción a la función Constructor
Puede usar una función como constructor para crear objetos, si la función del constructor se llama Person entonces los objetos creados con ese constructor son instancias de Person.
var Person = function(name){
this.name = name;
};
Person.prototype.walk=function(){
this.step().step().step();
};
var bob = new Person("Bob");
Persona es la función constructora. Cuando crea una instancia usando Person, debe usar la nueva palabra clave:
var bob = new Person("Bob");console.log(bob.name);//=Bob
var ben = new Person("Ben");console.log(ben.name);//=Ben
El name
propiedad / miembro es específico de la instancia, es diferente para bob y ben
El walk
miembro es parte de Person.prototype y se comparte para todas las instancias bob y ben son instancias de Person, por lo que comparten el miembro walk (bob.walk === ben.walk).
bob.walk();ben.walk();
Debido a que walk () no se puede encontrar en bob directamente JavaScript lo buscará en Person.prototype ya que este es el constructor de bob. Si no se puede encontrar allí, se verá en Object.prototype. Esto se llama cadena de prototipos. El prototipo de la herencia se hace alargando esta cadena; por ejemplo bob => Employee.prototype => Person.prototype => Object.prototype (más sobre la herencia más adelante).
Aunque bob, ben y todas las demás instancias de Person creadas comparten walk, la función se comportará de manera diferente por instancia porque en la función walk usa this
. El valor de this
será el objeto invocado; por ahora digamos que es la instancia actual así que para bob.walk()
"this" será bob. (más sobre "esto" y el objeto invocado más adelante).
Si Ben estaba esperando una luz roja y Bob estaba en una luz verde; entonces invocarás a walk () tanto en ben como en bob, obviamente algo diferente le pasaría a ben y bob.
Los miembros de sombreado ocurren cuando hacemos algo como ben.walk=22
, aunque bob y ben comparten la asignación de 22 a ben.walk no afectará a bob.walk. Esto se debe a que esa declaración creará un miembro llamado walk
on ben directamente y le asignará un valor de 22. Habrá 2 miembros diferentes para caminar: ben.walk y Person.prototype.walk.
Cuando solicite bob.walk, obtendrá la función Person.prototype.walk porque no se pudo encontrar walk
en bob. Sin embargo, al solicitar ben.walk obtendremos el valor 22 porque la caminata miembro se ha creado en ben y como JavaScript se encuentra en ben, no se verá en el perfil de persona.
Al utilizar Object.create con 2 argumentos, el sombreado Object.defineProperty o Object.defineProperties funciona de forma un poco diferente. Más información sobre eso here .
Más sobre el prototipo
Un objeto puede heredar de otro objeto mediante el uso de un prototipo. Puede establecer el prototipo de cualquier objeto con cualquier otro objeto utilizando Object.create
. En la introducción de la función de constructor hemos visto que si no se puede encontrar un miembro en el objeto, JavaScript lo buscará en la cadena de prototipos.
En la parte anterior, hemos visto que la reasignación de miembros que provienen del prototipo de una instancia (ben.walk) sombreará ese miembro (create walk on ben en lugar de cambiar Person.prototype.walk).
¿Qué ocurre si no asignamos de nuevo, sino que modificamos el miembro? La mutación es (por ejemplo) cambiar las sub propiedades de un Objeto o invocar funciones que cambiarán el valor del objeto. Por ejemplo:
var o = [];
var a = o;
a.push(11);//mutate a, this will change o
a[1]=22;//mutate a, this will change o
El siguiente código demuestra la diferencia entre los miembros del prototipo y los miembros de la instancia al mutar los miembros.
var person = {
name:"default",//immutable so can be used as default
sayName:function(){
console.log("Hello, I am "+this.name);
},
food:[]//not immutable, should be instance specific
// not suitable as prototype member
};
var ben = Object.create(person);
ben.name = "Ben";
var bob = Object.create(person);
console.log(bob.name);//=default, setting ben.name shadowed the member
// so bob.name is actually person.name
ben.food.push("Hamburger");
console.log(bob.food);//=["Hamburger"], mutating a shared member on the
// prototype affects all instances as it changes person.food
console.log(person.food);//=["Hamburger"]
El código anterior muestra que ben y bob comparten miembros de una persona. Solo hay una persona, se establece como el prototipo de bob y ben (la persona se utiliza como el primer objeto en la cadena de prototipos para buscar los miembros solicitados que no existen en la instancia). El problema con el código anterior es que bob y ben deberían tener su propio miembro de food
. Aquí es donde entra la función constructora. Se usa para crear miembros específicos de la instancia. También podría pasarle argumentos para establecer los valores de estos miembros específicos de la instancia.
El siguiente código muestra otra forma de implementar la función constructora, la sintaxis es diferente, pero la idea es la misma:
- Defina un objeto que tenga miembros que serán iguales para muchas instancias (la persona es un modelo para bob y ben y puede ser para jilly, marie, clair ...)
- Defina miembros específicos de la instancia que deberían ser únicos para las instancias (bob y ben).
- Crea una instancia ejecutando el código en el paso 2.
Con las funciones de constructor, configurará el prototipo en el paso 2 del siguiente código, configuraremos el prototipo en el paso 3.
En este código eliminé el nombre del prototipo y de la comida porque es muy probable que vayas a sombrear esto casi de inmediato al crear una instancia de todos modos. El nombre ahora es un miembro específico de la instancia con un valor predeterminado establecido en la función constructora. Debido a que el miembro de la comida también se mueve de un prototipo a un miembro específico de la instancia, no afectará a bob.food al agregar alimentos a ben.
var person = {
sayName:function(){
console.log("Hello, I am "+this.name);
},
//need to run the constructor function when creating
// an instance to make sure the instance has
// instance specific members
constructor:function(name){
this.name = name || "default";
this.food = [];
return this;
}
};
var ben = Object.create(person).constructor("Ben");
var bob = Object.create(person).constructor("Bob");
console.log(bob.name);//="Bob"
ben.food.push("Hamburger");
console.log(bob.food);//=[]
Puede encontrar patrones similares que sean más robustos para ayudar con la creación de objetos y la definición de objetos.
Herencia
El siguiente código muestra cómo heredar. Las tareas son básicamente las mismas que en el código anterior con un poco más
- Defina miembros específicos de instancia de un objeto (funciones Hamster y RussionMini).
- Establecer el prototipo como parte de la herencia (RussionMini.prototype = Object.create (Hamster.prototype))
- Defina miembros que puedan compartirse entre instancias. (Hamster.prototype y RussionMini.prototype)
- Cree una instancia ejecutando el código en el paso 1 y para los objetos que heredan haga que ejecuten también el código principal (Hamster.apply (this, arguments);)
Usando un patrón, algunos llamarían "herencia clásica". Si está confundido por la sintaxis, me complacerá explicar más o proporcionar diferentes patrones.
function Hamster(){
this.food=[];
}
function RussionMini(){
//Hamster.apply(this,arguments) executes every line of code
//in the Hamster body where the value of "this" is
//the to be created RussionMini (once for mini and once for betty)
Hamster.apply(this,arguments);
}
//setting RussionMini''s prototype
RussionMini.prototype=Object.create(Hamster.prototype);
//setting the built in member called constructor to point
// to the right function (previous line has it point to Hamster)
RussionMini.prototype.constructor=RussionMini;
mini=new RussionMini();
//this.food (instance specic to mini)
// comes from running the Hamster code
// with Hamster.apply(this,arguments);
mini.food.push("mini''s food");
//adding behavior specific to Hamster that will still be
// inherited by RussionMini because RussionMini.prototype''s prototype
// is Hamster.prototype
Hamster.prototype.runWheel=function(){console.log("I''m running")};
mini.runWheel();//=I''m running
Object.create para establecer el prototipo como parte de la herencia
Aquí está la documentación sobre Object.create , básicamente devuelve el segundo argumento (no admitido en el polyfil) con el primer argumento como el prototipo del objeto devuelto.
Si no se proporcionó un segundo argumento, devolverá un objeto vacío con el primer argumento para ser utilizado como el prototipo del objeto devuelto (el primer objeto que se utilizará en la cadena de prototipos del objeto devuelto).
Algunos establecerían el prototipo de RussionMini en una instancia de Hamster (RussionMini.prototype = new Hamster ()). Esto no es deseable porque a pesar de que logra lo mismo (el prototipo de RussionMini.prototype es Hamster.prototype) también establece a los miembros de la instancia de Hamster como miembros de RussionMini.prototype. Así que RussionMini.prototype.food existirá, pero es un miembro compartido (recuerde bob y ben en "Más sobre el prototipo"). El miembro de comida se sombreará al crear un RussionMini porque el código de Hamster se ejecuta con Hamster.apply(this,arguments);
que a su vez ejecuta this.food = []
pero cualquier miembro de Hamster seguirá siendo miembro de RussionMini.prototype.
Otra razón podría ser que para crear un hámster se necesitan hacer muchos cálculos complicados sobre argumentos pasados que pueden no estar disponibles todavía, de nuevo se podrían pasar argumentos ficticios pero podría complicar innecesariamente tu código.
Extender y anular las funciones principales
A veces los children
necesitan extender parent
funciones parent
.
Desea que el ''niño'' (= RussionMini) haga algo extra. Cuando RussionMini puede llamar al código Hamster para hacer algo y luego hacer algo extra, no es necesario copiar y pegar el código Hamster en RussionMini.
En el siguiente ejemplo suponemos que un Hamster puede correr 3 km por hora, pero un Russion mini solo puede correr la mitad de rápido. Podemos codificar duro 3/2 en RussionMini, pero si este valor cambiara, tenemos varios lugares en el código donde es necesario cambiar. Así es como usamos Hamster.prototype para obtener la velocidad padre (Hamster).
var Hamster = function(name){
if(name===undefined){
throw new Error("Name cannot be undefined");
}
this.name=name;
}
Hamster.prototype.getSpeed=function(){
return 3;
}
Hamster.prototype.run=function(){
//Russionmini does not need to implement this function as
//it will do exactly the same as it does for Hamster
//But Russionmini does need to implement getSpeed as it
//won''t return the same as Hamster (see later in the code)
return "I am running at " +
this.getSpeed() + "km an hour.";
}
var RussionMini=function(name){
Hamster.apply(this,arguments);
}
//call this before setting RussionMini prototypes
RussionMini.prototype = Object.create(Hamster.prototype);
RussionMini.prototype.constructor=RussionMini;
RussionMini.prototype.getSpeed=function(){
return Hamster.prototype
.getSpeed.call(this)/2;
}
var betty=new RussionMini("Betty");
console.log(betty.run());//=I am running at 1.5km an hour.
La desventaja es que codifica Hamster.prototype. Puede haber patrones que le den la ventaja de super
como en Java.
La mayoría de los patrones que he visto se romperán cuando el nivel de herencia sea de más de 2 niveles (Child => Parent => GrandParent) o usarán más recursos al implementar super a través de closures .
Para anular un método Parent (= Hamster), haz lo mismo pero no hagas Hamster.prototype.parentMethod.call (esto, ....
este.constructor
La propiedad constructora está incluida en el prototipo por JavaScript, puede cambiarla pero debe apuntar a la función constructora. Entonces Hamster.prototype.constructor
debería apuntar a Hamster.
Si después de configurar el prototipo como parte de la herencia, debe hacer que apunte de nuevo a la función correcta.
var Hamster = function(){};
var RussionMinni=function(){
// re use Parent constructor (I know there is none there)
Hamster.apply(this,arguments);
};
RussionMinni.prototype=Object.create(Hamster.prototype);
console.log(RussionMinni.prototype.constructor===Hamster);//=true
RussionMinni.prototype.haveBaby=function(){
return new this.constructor();
};
var betty=new RussionMinni();
var littleBetty=betty.haveBaby();
console.log(littleBetty instanceof RussionMinni);//false
console.log(littleBetty instanceof Hamster);//true
//fix the constructor
RussionMinni.prototype.constructor=RussionMinni;
//now make a baby again
var littleBetty=betty.haveBaby();
console.log(littleBetty instanceof RussionMinni);//true
console.log(littleBetty instanceof Hamster);//true
"Herencia múltiple" con mezcla de ins
Algunas cosas son mejores para no ser heredadas, si un gato puede moverse y luego un gato no debe heredar de Movable. Un gato no es móvil, sino que un gato puede moverse. En un lenguaje basado en clases, Cat debería implementar Movable. En JavaScript podemos definir Movable y definir la implementación aquí, Cat puede anular, extender o nosotros su implementación predeterminada.
Para Movable tenemos miembros específicos de instancia (como location
). Y tenemos miembros que no son específicos de la instancia (como la función move ()). Los miembros específicos de instancia se establecerán llamando a mxIns (agregado por la función mixin helper) al crear una instancia. Los miembros prototipo se copiarán uno por uno en el prototipo Cat de Movable.prototype utilizando la función mixin helper.
var Mixin = function Mixin(args){
if(this.mixIns){
i=-1;len=this.mixIns.length;
while(++i<len){
this.mixIns[i].call(this,args);
}
}
};
Mixin.mix = function(constructor, mix){
var thing
,cProto=constructor.prototype
,mProto=mix.prototype;
//no extending, if multiple prototypes
// have members with the same name then use
// the last
for(thing in mProto){
if(Object.hasOwnProperty.call(mProto, thing)){
cProto[thing]=mProto[thing];
}
}
//instance intialisers
cProto.mixIns = cProto.mixIns || [];
cProto.mixIns.push(mix);
};
var Movable = function(args){
args=args || {};
//demo how to set defaults with truthy
// not checking validaty
this.location=args.location;
this.isStuck = (args.isStuck===true);//defaults to false
this.canMove = (args.canMove!==false);//defaults to true
//speed defaults to 4
this.speed = (args.speed===0)?0:(args.speed || 4);
};
Movable.prototype.move=function(){
console.log(''I am moving, default implementation.'');
};
var Animal = function(args){
args = args || {};
this.name = args.name || "thing";
};
var Cat = function(args){
var i,len;
Animal.call(args);
//if an object can have others mixed in
// then this is needed to initialise
// instance members
Mixin.call(this,args);
};
Cat.prototype = Object.create(Animal.prototype);
Cat.prototype.constructor = Cat;
Mixin.mix(Cat,Movable);
var poochie = new Cat({
name:"poochie",
location: {x:0,y:22}
});
poochie.move();
Lo anterior es una implementación simple que reemplaza las mismas funciones nombradas con cualquier mezcla que se mezcle en último lugar.
La esta variable
En todo el código de ejemplo, verá this
refiriéndose a la instancia actual.
Esta variable realmente se refiere al objeto que invoca, se refiere al objeto que vino antes de la función.
Para aclarar vea el siguiente código:
theInvokingObject.thefunction();
Las instancias en las que esto se referiría al objeto incorrecto suelen ser cuando se adjuntan escuchas de eventos, devoluciones de llamada o tiempos de espera e intervalos. En las siguientes 2 líneas de código, pass
la función, no la invocamos. Pasar la función es: someObject.aFunction
e invocarlo es: someObject.aFunction()
. this
valor no se refiere al objeto en el que se declaró la función, sino al objeto que lo invokes
.
setTimeout(someObject.aFuncton,100);//this in aFunction is window
somebutton.onclick = someObject.aFunction;//this in aFunction is somebutton
Para hacer this
en los casos anteriores, refiérase a algún objeto, puede pasar un closures lugar de la función directamente:
setTimeout(function(){someObject.aFuncton();},100);
somebutton.onclick = function(){someObject.aFunction();};
Me gusta definir las funciones que devuelven una función para closures en el prototipo para tener un control preciso sobre las variables que se incluyen en el ámbito de closures .
var Hamster = function(name){
var largeVariable = new Array(100000).join("Hello World");
// if I do
// setInterval(function(){this.checkSleep();},100);
// then largeVariable will be in the closure scope as well
this.name=name
setInterval(this.closures.checkSleep(this),1000);
};
Hamster.prototype.closures={
checkSleep:function(hamsterInstance){
return function(){
console.log(typeof largeVariable);//undefined
console.log(hamsterInstance);//instance of Hamster named Betty
hamsterInstance.checkSleep();
};
}
};
Hamster.prototype.checkSleep=function(){
//do stuff assuming this is the Hamster instance
};
var betty = new Hamster("Betty");
Pasando argumentos (constructor)
Cuando Child llama a un padre ( Hamster.apply(this,arguments);
) asumimos que Hamster usa los mismos argumentos que RussionMini en el mismo orden. Para las funciones que llaman otras funciones usualmente uso otra forma de pasar argumentos.
Normalmente paso un objeto a una función y hago que esa función mute lo que necesita (establece valores predeterminados), luego esa función la pasará a otra función que hará lo mismo y así sucesivamente y así sucesivamente. Aquí hay un ejemplo:
//helper funciton to throw error
function thowError(message){
throw new Error(message)
};
var Hamster = function(args){
//make sure args is something so you get the errors
// that make sense to you instead of "args is undefined"
args = args || {};
//default value for type:
this.type = args.type || "default type";
//name is not optional, very simple truthy check f
this.name = args.name || thowError("args.name is not optional");
};
var RussionMini = function(args){
//make sure args is something so you get the errors
// that make sense to you instead of "args is undefined"
args = args || {};
args.type = "Russion Mini";
Hamster.call(this,args);
};
var ben = new RussionMini({name:"Ben"});
console.log(ben);// Object { type="Russion Mini", name="Ben"}
var betty = new RussionMini();//Error: args.name is not optional
Esta forma de pasar argumentos en una cadena de funciones es útil en muchos casos. Cuando trabaje en un código que calcule un total de algo y más adelante desee factorizar el total de ese elemento en una determinada moneda, es posible que tenga que cambiar muchas funciones para pasar el valor de la moneda. Podría window.currency=''USD''
valor de una moneda (incluso a global como window.currency=''USD''
), pero esa es una mala forma de resolverlo.
Al pasar un objeto, puede agregar moneda a args
siempre que esté disponible en la cadena de funciones y mutar / usar cada vez que lo necesite sin cambiar las otras funciones (explícitamente tiene que pasarlo en las llamadas a funciones).
Variables privadas
JavaScript no tiene un modificador privado.
Estoy de acuerdo con lo siguiente: http://blog.millermedeiros.com/a-case-against-private-variables-and-functions-in-javascript/ y personalmente no los he usado.
Puede indicar a otros programadores que un miembro debe ser privado al nombrarlo _aPrivate
o poniendo todas las variables privadas en una variable de objeto llamada _
.
Puede implementar miembros privados a través de closures pero las funciones que no están en el prototipo solo pueden acceder a miembros privados específicos de la instancia.
No implementar partes privadas ya que los cierres perderían la implementación y permitiría que usted o los usuarios amplíen su código para usar miembros que no sean parte de su API pública. Esto puede ser bueno y malo.
Es bueno porque te permite a ti y a los demás burlarse de ciertos miembros para probarlos fácilmente. Le da a los demás la oportunidad de mejorar (parchear) fácilmente su código, pero esto también es malo porque no hay garantía de que una próxima versión de su código tenga la misma implementación y / o miembros privados.
Al utilizar cierres, no le da a los demás una opción y al usar la convención de nomenclatura con la documentación que usted tiene. Esto no es específico de JavaScript, en otros idiomas puede decidir no usar miembros privados ya que confía en otros para saber lo que están haciendo y les da la opción de hacer lo que quieran (con los riesgos involucrados).
Si todavía insistes en partes privadas, el following patrón puede ayudar. No implementa embargo privado pero implementa protegido.