Come vengono inseriti i parametri di input nelle catene di metodi javascript?

Sto cercando di capire veramente i dettagli di come funziona JavaScript. Durante il concatenamento del metodo, a volte un metodo restituisce un altro metodo che ha un parametro di input denominato.

Ad esempio, in D3, il modello ha il seguente aspetto:

d3.select("body").selectAll("p") .data(dataset) .enter() .append("p") .text(function(d) { return d; }); //what does this d refer to? How is it filled? 

In jQuery, il modello è simile a questo:

 $.ajax({ ... }) .done(function( data ) { //what does this data refer to? How is it filled? 

So dal codice pratico che il nome di questi parametri di input può essere qualsiasi cosa. Ma da dove provengono i dati che file il parametro di input? Fa semplicemente riferimento ai dati restituiti dal metodo precedente nella catena?

Due argomenti diversi, quindi li spiegherò separatamente:

Utilizzo delle funzioni come parametri del metodo

Innanzitutto, una correzione: gli esempi che stai dando non sono esempi in cui “un metodo restituisce un altro metodo che ha un parametro di input denominato”. Sono esempi in cui viene fornita una funzione come parametro di input per un altro metodo.

Per chiarire, ti darò un esempio in cui il valore di ritorno di una funzione viene utilizzato come input per un’altra.

 var a = "Hello ", b = "World!"; var c = a.concat( b.toUpperCase() ); //c = "Hello WORLD!" 

Per creare c , accade quanto segue, nell’ordine:

  1. Il browser inizia ad analizzare il metodo concat , ma deve capire quale parametro dargli.
  2. Quel parametro include una chiamata al metodo, quindi viene eseguito il metodo toUpperCase() di b , restituendo la stringa “WORLD!”.
  3. La stringa restituita diventa il parametro per a metodo concat() che ora può essere eseguito.

Per quanto riguarda il metodo concat() , il risultato è lo stesso di se hai scritto c = a.concat("WORLD!") – non importa che la stringa “WORLD!” è stato creato da un’altra funzione.

Si può dire che il valore restituito di b.toUpperCase() viene passato come parametro e non la funzione stessa, a causa delle parentesi alla fine del nome della funzione. Le parentesi dopo il nome di una funzione indicano al browser di eseguire tale funzione, non appena ha rilevato i valori di qualsiasi parametro per quella funzione. Senza le parentesi, la funzione viene considerata come qualsiasi altro object e può essere passata come parametro o variabile senza effettivamente fare nulla.

Quando un object funzione, non eseguito, viene utilizzato come parametro per un’altra funzione, ciò che accade dipende interamente dalle istruzioni all’interno di tale seconda funzione. Se si passa l’object funzione a console.log() , la rappresentazione di stringa della funzione verrà stampata sulla console senza mai eseguire la funzione passata. Tuttavia, la maggior parte dei metodi che accettano un’altra funzione come input sono progettati per chiamare tale funzione con parametri specificati.

Un esempio è il metodo map() degli array. Il metodo map crea una nuova matrice in cui ogni elemento è il risultato dell’esecuzione della funzione di mapping sull’elemento corrispondente dell’array originale.

 var stringArray = ["1", "2!", "3.0", "?"]; var numberArray = stringArray.map(parseFloat); //numberArray = [1, 2, 3, NaN] 

La funzione parseFloat() è una funzione built-in che accetta una stringa e tenta di parseFloat() un numero. Nota che quando lo passo alla funzione mappa, lo sto passando come nome di variabile, non eseguendolo con parentesi alla fine. Viene eseguito dalla funzione mappa, ed è la funzione mappa che decide quali parametri ottiene. I risultati di ogni chiamata a parseFloat vengono assegnati dalla funzione map alla loro posizione nell’array dei risultati.

In particolare, la funzione map esegue parseFloat con tre parametri: l’elemento dalla matrice, l’indice di quell’elemento nella matrice e l’array nel suo complesso. La funzione parseFloat utilizza solo un parametro, tuttavia, quindi il secondo e il terzo parametro vengono ignorati. (Se si tenta di fare la stessa cosa con parseInt , tuttavia, si otterranno risultati imprevisti perché parseInt usa un secondo parametro – che tratta come radix (“base”) dell’intero.)

La funzione mappa non si preoccupa di quanti parametri la funzione passata si aspetta e sicuramente non si cura dei nomi delle variabili utilizzati all’interno di quella funzione. Se stessimo scrivendo la funzione della mappa da soli, sarebbe simile a questa:

 Array.prototype.myMap = function(f) { var result = []; for (var i = 0, n=this.length; i 

La funzione f viene chiamata, e dati i parametri, senza sapere nulla su cosa sia o cosa faccia. I parametri sono forniti in un ordine specifico: elemento, indice, matrice, ma non sono collegati a nessun particolare nome di parametro.

Ora, una limitazione di qualcosa come il metodo map è che ci sono pochissime funzioni Javascript che possono essere chiamate direttamente, semplicemente passando un valore come parametro. La maggior parte delle funzioni sono metodi di un object specifico. Ad esempio, non è ansible utilizzare il metodo toUpperCase come parametro per map , poiché toUpperCase esiste solo come metodo di un object stringa e agisce solo su quel particolare object stringa, non su qualsiasi parametro che la funzione mappa potrebbe dargli. Per mappare una serie di stringhe in maiuscolo, è necessario creare una propria funzione che funzioni nel modo in cui la funzione mappa lo utilizzerà.

 var stringArray = ["hello", "world", "again"]; function myUpperCase(s, i, array) { return s.toUpperCase(); //call the passed-in string's method } var uppercaseStrings = stringArray.map( myUpperCase ); //uppercaseStrings = ["HELLO", "WORLD", "AGAIN"] 

Tuttavia, se utilizzerai la funzione myUpperCase una volta, non dovrai dichiararlo separatamente e assegnargli un nome. Puoi usarlo direttamente come una funzione anonima .

 var stringArray = ["hello", "world", "again"]; var uppercaseStrings = stringArray.map( function(s,i,array) { return s.toUpperCase(); } ); //uppercaseStrings still = ["HELLO", "WORLD", "AGAIN"] 

Iniziando a sembrare familiare? Questa è la struttura usata da così tante funzioni d3 e JQuery - si passa una funzione, sia come nome di funzione o come funzione anonima, e il metodo d3 / JQuery chiama la propria funzione su ogni elemento di una selezione, passando in valori specificati come primo, secondo e forse terzo parametro.

Quindi, per quanto riguarda i nomi dei parametri? Come hai detto, possono essere tutto ciò che vuoi. Avrei potuto usare nomi di parametri molto lunghi e descrittivi nella mia funzione:

 function myUpperCase(stringElementFromArray, indexOfStringElementInArray, ArrayContainingStrings) { return stringElementFromArray.toUpperCase(); } 

I valori che vengono passati alla funzione saranno gli stessi, basati esclusivamente sull'ordine in cui la funzione mappa li inoltra. Infatti, poiché non uso mai il parametro index o il parametro array, posso lasciarli fuori dalla mia dichiarazione di funzione e basta usare il primo parametro. Gli altri valori vengono comunque passati dalla map , ma vengono ignorati. Tuttavia, se volessi utilizzare il secondo o il terzo parametro trasmesso dalla map , dovrei dichiarare un nome per il primo parametro, solo per mantenere la numerazione corretta:

 function indirectUpperCase (stringIDontUse, index, array) { return array[index].toUpperCase(); } 

Ecco perché, se vuoi usare il numero indice in d3, devi scrivere la function(d,i){return i;} . Se hai appena fatto function(i){return i;} il valore di i sarebbe l'object dati, perché è ciò che le funzioni d3 passano sempre come primo parametro, indipendentemente da come lo chiami. È la funzione esterna che trasferisce i valori dei parametri. I nomi dei parametri esistono solo all'interno della funzione interna.


Avvertenze ed eccezioni richieste:

  • Ho detto che la funzione mappa non interessa quanti parametri si aspetta una funzione passata. È vero, ma altre funzioni esterne potrebbero utilizzare la proprietà .length della funzione .length per capire quanti parametri sono previsti e passare di conseguenza i diversi valori dei parametri.

  • Non è necessario assegnare un nome agli argomenti per una funzione per accedere ai valori dei parametri passati. Puoi anche accedervi utilizzando l'elenco degli arguments all'interno di quella funzione. Quindi un altro modo di scrivere la funzione di mapping maiuscola sarebbe:

     function noParamUpperCase() { return arguments[0].toUpperCase(); } 

    Tuttavia, si noti che se una funzione esterna utilizza il numero di parametri per determinare quali valori passare alla funzione interna, questa funzione sembrerà non accettare alcun argomento.


Metodo di concatenamento

Noterai che da nessuna parte ho menzionato il metodo di concatenamento. Questo perché è un pattern di codice completamente separato, che sembra essere usato molto anche in d3 e JQuery.

Torniamo al primo esempio, che ha creato "Hello WORLD!" di "Ciao" e "Mondo!". E se volessi creare "Ciao mondo!" anziché? Potresti farlo

 var a = "Hello ", b = "World!"; var c = a.toUpperCase().concat( b ); //c = "HELLO World!" 

La prima cosa che accade nella creazione di c è la a.toUpperCase() metodo a.toUpperCase() . Questo metodo restituisce una stringa ("HELLO"), che come tutte le altre stringhe ha un metodo .concat() . Quindi .concat(b) viene ora chiamato come metodo di quella stringa restituita , non della stringa originale a . Il risultato è che b viene concatenato alla fine della versione maiuscola di a : "HELLO World!".

In quel caso, il valore restituito era un nuovo object dello stesso tipo dell'object iniziale. In altri casi, potrebbe essere un tipo di dati completamente diverso.

 var numberArray = [5, 15]; var stringArray = numberArray.toString().split(","); //stringArray = ["5", "15"] 

Iniziamo con una serie di numeri, [5,15] . Chiamiamo il metodo toString() dell'array, che produce una versione di stringa dell'array ben formattata: "5,15". Questa stringa ora ha tutti i suoi metodi di stringa disponibili, incluso .split() , che divide una stringa in una matrice di sottostringhe attorno a un carattere di divisione specificato, in questo caso la virgola.

Si potrebbe chiamare questo un tipo di metodo concatenato, chiamando un metodo di un valore restituito da un altro metodo. Tuttavia, quando il concatenamento del metodo viene utilizzato per descrivere una funzionalità di una libreria Javascript, l'aspetto chiave è che il valore restituito del metodo è lo stesso object che ha chiamato il metodo .

Quindi quando lo fai

 d3.select("body").style("background", "green"); 

Il d3.select("body") crea un object di selezione d3. Quell'object ha un metodo style() . Se usi il metodo style per impostare uno stile, non hai davvero bisogno di alcuna informazione da esso. Potrebbe essere stato progettato per non restituire alcun valore. Invece, restituisce l'object a cui appartiene il metodo (l'object). Quindi ora puoi chiamare un altro metodo per quell'object. O potresti assegnarlo a una variabile. O entrambi.

 var body = d3.select("body").style("background", "green") .style("max-width", "20em"); 

Tuttavia, devi sempre essere consapevole dei metodi che non restituiscono lo stesso object. Ad esempio, in molti esempi di codice d3 vedi

 var svg = d3.select("svg").attr("height", "200") .attr("width", "200") .append("g") .attr("transform", "translate(20,20)"); 

Ora, il metodo append("g") non restituisce la stessa selezione dell'elemento . Restituisce una nuova selezione costituita dall'elemento , a cui viene quindi assegnato un attributo di trasformazione. Il valore che viene assegnato alla variabile svg è l' ultimo valore restituito dalla catena. Quindi nel codice successivo, dovresti ricordare che la variabile svg realtà non si riferisce a una selezione dell'elemento , ma a . Che trovo confuso, quindi cerco di evitare di utilizzare mai il nome di variabile svg per una selezione che non è in realtà un elemento .

Ovviamente, uno qualsiasi di quei .attr() d3 .attr() o .style() avrebbe potuto assumere una funzione come secondo parametro anziché come stringa. Ma questo non avrebbe cambiato il modo in cui il concatenamento del metodo funziona.

Se capisco correttamente

a cosa si riferiscono questi dati? Come è riempito?

Intendi come funziona? Dipende da come viene richiamato il callback. Per esempio:

 function Lib() {} Lib.prototype.text = function(callback) { var data = 'hello world'; callback(data); // `data` is the first parameter return this; // chain }; var lib = new Lib(); lib.text(function(data){ console.log(data); //=> "hello world" }); 

La d corrisponde a un singolo elemento dell’insieme di dati trasmessi per la prima volta da .data(data).enter() nella chiamata di metodo precedente. Quello che sta succedendo è che d3 fa un loop in modo implicito sugli elementi dom corrispondenti che si associano ai dati, quindi l’intera nozione di documenti guidati dai dati o d3.

Cioè, per ogni elemento nei tuoi dati, quando chiami un selectAll() devi aspettarti di vedere lo stesso numero di elementi aggiunti al dom quanti ce ne sono nel tuo set di dati. Quindi, quando si .attr() una chiamata .attr() , il singolo dato che corrisponde a un elemento nella dom viene passato alla funzione. Quando stai leggendo

 .append("rect").attr("width",function(datum){})... 

dovresti leggerlo come una singola iterazione su un elemento dell’intero insieme di dati che è vincolato alla tua selezione.

Sono un po ‘insicuro di aver risposto alla tua domanda in quanto sembra essere sia una combinazione di domande sulla programmazione dichiarativa / funzionale che sul metodo di concatenamento. La risposta sopra è rispetto al modo dichiarativo / funzionale di d3. La risposta che segue è in riferimento al concatenamento del metodo.

Ecco un ottimo esempio che offre maggiori informazioni sul metodo di concatenamento delineato da Mike Bostock nel suo meraviglioso articolo http://bost.ocks.org/mike/chart/ .

Ti consiglio di leggere la d3 API che trovi qui ( https://github.com/mbostock/d3/wiki/API-Reference ) per capire meglio su cosa sta funzionando ogni funzione. Ad esempio selection.filter Inoltre, ti raccomando caldamente di entrare in un esempio di gioco come quello che hai dato per capire meglio cosa sta succedendo.

Metodo di concatenamento rispetto ai grafici riutilizzabili d3.

 function SomeChart() { var someProperty = "default value of some sort"; function chartObject() { } chartObject.setSomeProperty(propertyValue) { if (!arguments.length) return someProperty; someProperty = property; return chartObject; } return chartObject; } var chart = SomehChart(); 

Cos’è il grafico a questo punto?

 chart = chart.setSomeProperty("some special value"); 

Cos’è il grafico a questo punto?

 chart = chart.setSomeProperty(); 

Cos’è il grafico a questo punto?

Qual è la differenza tra quanto segue?

 var chart = SomeChart().setSomeProperty("some special value"); 

e

 var chart = SomeChart(); chart.setSomeProperty(); 

La risposta è che sono tutti uguali tranne quando chart = chart.setSomeProperty(); .

in d3 d3.select (“body”) restituirà qualcosa {search_element:document.body,selectAll:function,...} così con la notazione dot si chiama la funzione disponibile dell’object precedente. Forse restituisce semplicemente la class stessa. Quindi tutti i metodi sono disponibili in qualsiasi ordine con il prossimo punto. Ma in Ajax, alcune funzioni devono essere chiamate in modo da riempire i parametri importanti dell’object utilizzati dalla funzione done .