Eredità prototipica – redigere

Quindi ho questi 2 esempi, da javascript.info:

Esempio 1:

var animal = { eat: function() { alert( "I'm full" ) this.full = true } } var rabbit = { jump: function() { /* something */ } } rabbit.__proto__ = animal rabbit.eat() 

Esempio 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 (!??) 

Partire dall’esempio 2: quando il codice raggiunge speedy.found , non trova alcuna proprietà found in modo speedy , quindi sale al prototipo e lo modifica lì. Ecco perché food.length è uguale per entrambi i criceti, in altre parole hanno lo stesso stomaco.

Da questo capisco, che scrivendo e aggiungendo una nuova proprietà che non esiste, l’interprete salirà la catena del prototipo finché non troverà la proprietà, e POI la cambierà.

MA nell’Esempio 1 succede qualcos’altro:
eseguiamo rabbit.eat , che cambia rabbit.full . full proprietà full non si trova da nessuna parte, quindi dovrebbe risalire la catena del prototipo in (object) e, beh, non sono sicuro di cosa accada qui. In questo esempio viene creata e modificata la proprietà full di rabbit , mentre nel primo esempio sale alla catena del prototipo perché non riesce a trovare la proprietà.

Sono confuso e non riesco a capire perché questo accada.

Introduzione alla funzione del costruttore

È ansible utilizzare una funzione come costruttore per creare oggetti, se la funzione di costruzione è denominata Persona, quindi gli oggetti creati con tale costruttore sono istanze di Persona.

 var Person = function(name){ this.name = name; }; Person.prototype.walk=function(){ this.step().step().step(); }; var bob = new Person("Bob"); 

La persona è la funzione di costruzione. Quando crei un’istanza usando Person, devi usare la nuova parola chiave:

 var bob = new Person("Bob");console.log(bob.name);//=Bob var ben = new Person("Ben");console.log(ben.name);//=Ben 

Il name proprietà / membro è specifico dell’istanza, è diverso per Bob e Ben

Il membro walk è parte di Person.prototype ed è condiviso per tutte le istanze bob e ben sono istanze di Person in modo che condividano il membro walk (bob.walk === ben.walk).

 bob.walk();ben.walk(); 

Poiché non è ansible trovare walk () su bob direttamente, JavaScript lo cercherà nel Person.prototype in quanto questo è il costruttore di bob. Se non può essere trovato lì, cercherà su Object.prototype. Questo è chiamato la catena del prototipo. La parte del prototipo dell’eredità viene fatta allungando questa catena; ad esempio bob => Employee.prototype => Person.prototype => Object.prototype (ulteriori informazioni sull’ereditarietà successiva).

Anche se bob, ben e tutte le altre condivisioni di persone create condividono il cammino, la funzione si comporterà in modo diverso, ad esempio perché nella funzione walk lo usa. Il valore di this sarà l’object invocante; per ora diciamo che è l’istanza attuale quindi per bob.walk() “questo” sarà bob. (più su “questo” e l’object che invoca in seguito).

Se Ben stava aspettando una luce rossa e Bob era a una luce verde; poi invocherai walk () su entrambi e ovviamente ovviamente qualcosa di diverso sarebbe successo a Ben e Bob.

I membri Shadowing si verificano quando facciamo qualcosa come ben.walk=22 , anche se la condivisione di bob e ben walk l’ assegnazione di 22 a ben.walk non avrà effetto su bob.walk. Questo perché tale affermazione creerà un membro chiamato walk on ben direttamente e assegnerà un valore di 22. Ci saranno 2 membri di walk differenti: ben.walk e Person.prototype.walk.

Quando chiedi bob.walk otterrai la funzione Person.prototype.walk perché non è stato ansible trovare a walk bob. Chiedere comunque per ben.walk ti darà il valore 22 perché il membro walk è stato creato su ben e dato che JavaScript ha trovato su walk non apparirà nel Person.prototype.

Quando si utilizza Object.create con 2 argomenti, Object.defineProperty o Object.defineProperties funziona in modo un po ‘diverso. Maggiori informazioni su questo qui .

Ulteriori informazioni sul prototipo

Un object può ereditare da un altro object attraverso l’uso del prototipo. È ansible impostare il prototipo di qualsiasi object con qualsiasi altro object utilizzando Object.create . Nella introduzione della funzione di costruzione abbiamo visto che se un membro non può essere trovato sull’object, JavaScript cercherà nella catena del prototipo.

Nella parte precedente abbiamo visto che l’assegnazione dei membri che provengono dal prototipo di un’istanza (ben.walk) ombreggia quel membro (creare walk on ben piuttosto che modificare Person.prototype.walk).

Cosa succederebbe se non venissimo assegnati ma mutassimo il membro? La mutazione è (ad esempio) la modifica delle proprietà secondarie di un object o il richiamo di funzioni che modificano il valore dell’object. Per esempio:

 var o = []; var a = o; a.push(11);//mutate a, this will change o a[1]=22;//mutate a, this will change o 

Il codice seguente dimostra la differenza tra i membri prototipo e i membri di istanza da membri mutanti.

 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"] 

Il codice sopra mostra che ben e bob condividono i membri di persona. C’è solo una persona, è impostata come prototipo di bob e ben (la persona viene utilizzata come primo object nella catena del prototipo per cercare i membri richiesti che non esistono sull’istanza). Il problema con il codice di cui sopra è che Bob e Ben dovrebbero avere il proprio membro del food . È qui che entra in gioco la funzione di costruzione. Viene utilizzata per creare membri specifici dell’istanza. È anche ansible passare argomenti ad esso per impostare i valori di questi membri specifici dell’istanza.

Il prossimo codice mostra un altro modo per implementare la funzione di costruzione, la syntax è diversa ma l’idea è la stessa:

  1. Definisci un object che ha membri che saranno uguali per molte istanze (la persona è un progetto per bob e ben e può essere per jilly, marie, clair …)
  2. Definisci membri specifici dell’istanza che dovrebbero essere unici per le istanze (bob e ben).
  3. Creare un’istanza che esegue il codice nel passaggio 2.

Con le funzioni di costruzione imposterai il prototipo nel passo 2 nel seguente codice che impostiamo il prototipo nel passaggio 3.

In questo codice ho rimosso il nome dal prototipo e dal cibo perché è molto probabile che lo ombreggiate quasi immediatamente quando create comunque un’istanza. Il nome è ora un membro specifico dell’istanza con un valore predefinito impostato nella funzione di costruzione. Anche se il membro dell’alimento viene spostato da un prototipo a un membro specifico dell’istanza, non influirà su bob.food quando aggiunge cibo 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);//=[] 

Potresti imbatterti in modelli simili che sono più robusti per aiutare con la creazione dell’object e la definizione dell’object.

Eredità

Il codice seguente mostra come ereditare. I compiti sono fondamentalmente gli stessi del codice prima con un piccolo extra

  1. Definisci membri specifici di un’istanza di un object (funzioni Hamster e RussionMini).
  2. Imposta la parte prototipo dell’ereditarietà (RussionMini.prototype = Object.create (Hamster.prototype))
  3. Definire i membri che possono essere condivisi tra le istanze. (Hamster.prototype e RussionMini.prototype)
  4. Creare un’istanza che esegue il codice nel passaggio 1 e per gli oggetti che ereditano averli eseguire anche il codice padre (Hamster.apply (this, arguments);)

Usando un modello, alcuni chiamerebbero “ereditarietà classica”. Se sei confuso dalla syntax, saremo lieti di spiegarne altri o di fornire modelli diversi.

 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 per impostare la parte del prototipo dell’ereditarietà

Ecco la documentazione su Object.create , in pratica restituisce il secondo argomento (non supportato nel polyfil) con il primo argomento come prototipo dell’object restituito.

Se non viene fornito alcun secondo argomento, restituirà un object vuoto con il primo argomento da utilizzare come prototipo dell’object restituito (il primo object da utilizzare nella catena di prototipi dell’object restituito).

Alcuni potrebbero impostare il prototipo di RussionMini su un’istanza di Hamster (RussionMini.prototype = new Hamster ()). Questo non è desiderabile perché anche se ha lo stesso risultato (il prototipo di RussionMini.prototype è Hamster.prototype), imposta anche i membri dell’istanza di Hamster come membri di RussionMini.prototype. Quindi RussionMini.prototype.food esisterà ma è un membro condiviso (ricordate bob e ben in “Ulteriori informazioni sul prototipo”?). Il membro del cibo verrà ombreggiato durante la creazione di un RussionMini perché il codice Hamster viene eseguito con Hamster.apply(this,arguments); che a sua volta gestisce this.food = [] ma qualsiasi membro di Hamster sarà ancora membro di RussionMini.prototype.

Un altro motivo potrebbe essere che per creare un criceto si devono fare molti calcoli complicati sugli argomenti passati che potrebbero non essere ancora disponibili, ancora si potrebbe passare in argomenti fittizi ma potrebbe complicare inutilmente il codice.

Estensione e sovrascrittura delle funzioni parent

A volte i children bisogno di estendere le funzioni parent .

Vuoi che il ‘bambino’ (= RussionMini) faccia qualcosa in più. Quando RussionMini può chiamare il codice Hamster per fare qualcosa e poi fare qualcosa in più, non è necessario copiare e incollare il codice Hamster su RussionMini.

Nell’esempio seguente assumiamo che un criceto possa correre 3 km all’ora, ma un mini Russion può correre solo a metà della velocità. Siamo in grado di codificare hard in 3/2 su RussionMini, ma se questo valore dovesse cambiare avremo più posti nel codice in cui è necessario cambiare. Ecco come usiamo Hamster.prototype per ottenere la velocità genitore (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. 

Lo svantaggio è che il codice è duro Hamster.prototype. Ci possono essere schemi che ti daranno il vantaggio di super come in Java.

La maggior parte degli schemi che ho visto si interromperà quando il livello di ereditarietà è superiore a 2 livelli (Child => Parent => GrandParent) o utilizzare più risorse implementando super-through closures .

Per sovrascrivere un metodo Parent (= Hamster) fai lo stesso, ma non fare Hamster.prototype.parentMethod.call (questo, ….

this.constructor

La proprietà del costruttore è inclusa nel prototipo da JavaScript, puoi cambiarla ma dovrebbe puntare alla funzione di costruzione. Quindi Hamster.prototype.constructor dovrebbe indicare Hamster.

Se dopo aver impostato la parte del prototipo dell’ereditarietà dovresti farlo puntare nuovamente alla funzione corretta.

 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 

“Multiple inheritance” con mix ins

Alcune cose sono meglio non essere ereditate, se un gatto può muoversi e quindi un gatto non dovrebbe ereditare da Movable. Un gatto non è un mobile ma piuttosto un gatto può muoversi. In un linguaggio basato sulla class, Cat avrebbe dovuto implementare Movable. In JavaScript possiamo definire Movable e definire l’implementazione qui, Cat può ignorarlo, estenderlo o noi è l’implementazione predefinita.

Per Movable abbiamo membri specifici dell’istanza (come la location ). E abbiamo membri che non sono specifici dell’istanza (come la funzione move ()). I membri specifici dell’istanza verranno impostati chiamando mxIns (aggiunto dalla funzione helper mixin) durante la creazione di un’istanza. I membri di Prototype verranno copiati uno per uno su Cat.prototype da Movable.prototype utilizzando la funzione di aiuto mixin.

 var Mixin = function Mixin(args){ if(this.mixIns){ i=-1;len=this.mixIns.length; while(++i 

Quanto sopra è una semplice implementazione che sostituisce le stesse funzioni con qualsiasi mix in cui sia stato mixato per ultimo.

Questa variabile

In tutto il codice di esempio vedrai this facendo riferimento all'istanza corrente.

Questa variabile si riferisce effettivamente all'object che invoca, si riferisce all'object che precede la funzione.

Per chiarire vedi il seguente codice:

 theInvokingObject.thefunction(); 

Le istanze in cui ciò si riferirebbe all'object sbagliato sono solitamente quando si collegano listener di eventi, callback o timeout e intervalli. Nelle prossime 2 righe di codice pass la funzione, non la invochiamo. Passare la funzione è: someObject.aFunction e invocarlo è: someObject.aFunction() . this valore non si riferisce all'object su cui è stata dichiarata la funzione ma sull'object che lo invokes .

 setTimeout(someObject.aFuncton,100);//this in aFunction is window somebutton.onclick = someObject.aFunction;//this in aFunction is somebutton 

Per fare this nei casi sopra riportati fai riferimento a someObject puoi passare una chiusura invece della funzione direttamente:

 setTimeout(function(){someObject.aFuncton();},100); somebutton.onclick = function(){someObject.aFunction();}; 

Mi piace definire le funzioni che restituiscono una funzione per le chiusure sul prototipo per avere un controllo preciso sulle variabili incluse nello scope di chiusura .

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

Passare argomenti (costruttore)

Quando il bambino chiama un genitore ( Hamster.apply(this,arguments); ) assumiamo che Hamster usi gli stessi argomenti di RussionMini nello stesso ordine. Per le funzioni che chiamano altre funzioni, di solito uso un altro modo per passare argomenti.

Di solito passo un object a una funzione e faccio in modo che la funzione muti tutto ciò di cui ha bisogno (imposta i valori predefiniti), quindi quella funzione la passerà a un'altra funzione che farà lo stesso e così via e così via. Ecco un esempio:

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

Questo modo di passare argomenti in una catena di funzioni è utile in molti casi. Quando stai lavorando su un codice che calcola un totale di qualcosa e in un secondo momento desideri ridimensionare il totale di quel qualcosa in una certa valuta, potresti dover cambiare molte funzioni per passare il valore per la valuta. Potresti aumentare l'ambito di un valore di valuta (anche a livello globale come window.currency='USD' ) ma questo è un brutto modo per risolverlo.

Con il passaggio di un object è ansible aggiungere valuta a args ogni volta che è disponibile nella catena di funzioni e mutare / usarlo ogni volta che lo si desidera senza modificare le altre funzioni (è necessario passarlo esplicitamente nelle chiamate di funzione).

Variabili private

JavaScript non ha un modificatore privato.

Sono d'accordo con quanto segue: http://blog.millermedeiros.com/a-case-against-private-variables-and-functions-in-javascript/ e personalmente non li ho usati.

Puoi indicare ad altri programmatori che un membro deve essere privato nominandolo _aPrivate o mettendo tutte le variabili private in una variabile object chiamata _ .

È ansible implementare membri privati ​​tramite le chiusure, ma è ansible accedere a membri privati ​​specifici dell'istanza solo da funzioni non presenti nel prototipo.

Non implementare i privati ​​come chiusure perderebbe l'implementazione e consentirebbe a te o agli utenti di estendere il tuo codice per utilizzare membri che non fanno parte della tua API pubblica. Questo può essere sia buono sia cattivo.

È buono perché consente a te e agli altri di prendere in giro determinati membri per testarli facilmente. Dà agli altri la possibilità di migliorare facilmente (patch) il tuo codice, ma questo è anche un male perché non c'è alcuna garanzia che una versione successiva del tuo codice abbia la stessa implementazione e / o membri privati.

Usando chiusure non si dà agli altri una scelta e usando la convenzione di denominazione con la documentazione che si fa. Questo non è specifico di JavaScript, in altre lingue puoi decidere di non utilizzare membri privati ​​quando ti fidi degli altri per sapere cosa stanno facendo e dare loro la possibilità di fare ciò che vogliono (con rischi).

Se insisti ancora sui privati, il seguente schema potrebbe aiutarti. Non implementa però il privato ma implementa le protezioni.

I prototipi NON sono istanziati per ogni istanza di un object.

 Hamster.prototype.food = [] 

Ogni istanza di Hamster condividerà quell’array

Se hai bisogno (e fai in questo caso) di istanze separate di raccolte di cibo per ogni Criceto, devi creare la proprietà sull’istanza. Per esempio:

 function Hamster() { this.food = []; } 

Per rispondere alla tua domanda sull’Esempio 1, se non trova la proprietà in nessuna parte della catena di prototipi, crea la proprietà sull’object di destinazione.