Passa le variabili per riferimento in Javascript


285

Come faccio a passare le variabili per riferimento in JavaScript? Ho 3 variabili a cui voglio eseguire diverse operazioni, quindi voglio inserirle in un ciclo for ed eseguire le operazioni su ognuna.

pseudo codice:

myArray = new Array(var1, var2, var3);
for (var x = 0; x < myArray.length; x++){
    //do stuff to the array
    makePretty(myArray[x]);
}
//now do stuff to the updated vars

Qual è il modo migliore per farlo?


31
Stai parlando di "passa per riferimento", ma non hai chiamate di funzione nel tuo esempio, quindi non c'è alcun passaggio nel tuo esempio. Per favore, chiarisci cosa stai cercando di fare.
jfriend00,

1
Dispiace per la confusione. Non avevo specificamente bisogno di scrivere una funzione, quindi "passare per riferimento" era una cattiva scelta di parole. Voglio solo essere in grado di eseguire alcune operazioni su variabili senza scrivere makePretty(var1); makePretty(var2); makePretty(var3); ...
BFTrick,

in base al tuo commento: arr = [var1, var2, var3]; for (var i = 0, len = arr.length; i < len; i++) { arr[i] = makePretty(arr[i]); }- devi solo memorizzare il valore restituito dallo makePrettyslot nella matrice.
dylnmc,

Risposte:


415

Non esiste un "pass per riferimento" disponibile in JavaScript. Puoi passare un oggetto (vale a dire, puoi passare per valore un riferimento a un oggetto) e quindi una funzione modificare il contenuto dell'oggetto:

function alterObject(obj) {
  obj.foo = "goodbye";
}

var myObj = { foo: "hello world" };

alterObject(myObj);

alert(myObj.foo); // "goodbye" instead of "hello world"

È possibile scorrere le proprietà di un array con un indice numerico e modificare ogni cella dell'array, se lo si desidera.

var arr = [1, 2, 3];

for (var i = 0; i < arr.length; i++) { 
    arr[i] = arr[i] + 1; 
}

È importante notare che "pass-by-reference" è un termine molto specifico. Non significa semplicemente che è possibile passare un riferimento a un oggetto modificabile. Invece, significa che è possibile passare una semplice variabile in modo tale da consentire a una funzione di modificare quel valore nel contesto chiamante . Così:

 function swap(a, b) {
   var tmp = a;
   a = b;
   b = tmp; //assign tmp to b
 }

 var x = 1, y = 2;
 swap(x, y);

 alert("x is " + x + ", y is " + y); // "x is 1, y is 2"

In un linguaggio come C ++, è possibile farlo perché quel linguaggio ha (tipo di) un riferimento pass-by.

modifica - questo recentemente (marzo 2015) è esploso di nuovo su Reddit su un post di blog simile al mio menzionato di seguito, anche se in questo caso su Java. Mi è venuto in mente mentre leggevo avanti e indietro nei commenti di Reddit che gran parte della confusione deriva dalla sfortunata collisione che coinvolge la parola "riferimento". La terminologia "passa per riferimento" e "passa per valore" precede il concetto di avere "oggetti" con cui lavorare nei linguaggi di programmazione. Non si tratta affatto di oggetti; riguarda i parametri delle funzioni, e in particolare il modo in cui i parametri delle funzioni sono "connessi" (o meno) all'ambiente chiamante. In particolare, e sembrerebbe praticamente esattamente come in JavaScript. Tuttavia, si sarebbe anche in grado di modificare il riferimento all'oggetto nell'ambiente chiamante, e questa è la cosa chiave che non si può fare in JavaScript. Un linguaggio pass-by-reference passerebbe non il riferimento stesso, ma un riferimento al riferimento .

modifica : ecco un post sul blog sull'argomento. (Nota il commento a quel post che spiega che il C ++ in realtà non ha riferimenti pass-by. Questo è vero. Ciò che ha C ++, tuttavia, è la capacità di creare riferimenti a variabili semplici, sia esplicitamente nel punto di funzione invocazione per creare un puntatore, o implicitamente quando si chiamano funzioni la cui firma del tipo di argomento richiede che ciò avvenga. Queste sono le cose chiave che JavaScript non supporta.)


8
È possibile passare un riferimento a un oggetto o una matrice che consente di modificare l'oggetto o la matrice originale di cui credo sia effettivamente l'OP.
jfriend00,

2
Bene, l'OP ha usato la terminologia ma il codice attuale non sembra implicare alcun "passaggio" :-) Non sono proprio sicuro di cosa stia cercando di fare.
Punta appuntita

5
Passare un riferimento per valore non equivale a passare per riferimento , anche se può apparire così in alcuni scenari come questo.
Travis Webb,

5
Il tuo blogger ha torto su C ++, che fa riferimento pass-by, e il suo post non ha senso. È irrilevante che non sia possibile modificare il valore di un riferimento dopo l'inizializzazione; non ha nulla a che fare con "passa per riferimento". La sua affermazione che "la semantica" passa per riferimento "può essere fatta risalire a C #" avrebbe dovuto suonare un campanello d'allarme.
EML

7
@Pointy Questa è una risposta orribile. Se avessi bisogno di aiuto, l'ultima cosa che vorrei è che qualcuno mi istruisse sulla semantica. "Pass-by-reference" significa semplicemente "la funzione, in una certa misura, può cambiare il valore della variabile che è stata passata come argomento". (nel contesto della domanda) In realtà non è così complicato come si immagina.
senza corona

110
  1. le variabili di tipo primitivo come stringhe e numeri vengono sempre passate per valore.
  2. Le matrici e gli oggetti vengono passati per riferimento o per valore in base a queste condizioni:

    • se si sta impostando il valore di un oggetto o di un array, è Passa per valore.

      object1 = {prop: "car"}; array1 = [1,2,3];

    • se si sta modificando un valore di proprietà di un oggetto o di un array, si tratta di Passa per riferimento.

      object1.prop = "car"; array1[0] = 9;

Codice

function passVar(obj1, obj2, num) {
    obj1.prop = "laptop"; // will CHANGE original
    obj2 = { prop: "computer" }; //will NOT affect original
    num = num + 1; // will NOT affect original
}

var object1 = {
    prop: "car"
};
var object2 = {
    prop: "bike"
};
var number1 = 10;

passVar(object1, object2, number1);
console.log(object1); //output: Object {item:"laptop"}
console.log(object2); //output: Object {item:"bike"}
console.log(number1); //ouput: 10


21
Questo non è il significato di "passa per riferimento". Il termine non ha davvero nulla a che fare con gli oggetti, ma con la relazione tra i parametri in una funzione chiamata e le variabili nell'ambiente chiamante.
Punta il

12
Tutto viene sempre passato per valore. Con i non primitivi, il valore che viene passato è un riferimento. L'assegnazione a un riferimento modifica tale riferimento - naturalmente, poiché è un valore - e quindi non può modificare l'altro oggetto a cui era stato fatto riferimento in origine. La mutazione dell'oggetto a cui punta un riferimento agisce esattamente come ci si aspetterebbe, mutando l'oggetto a punta. Entrambi gli scenari sono perfettamente coerenti e nessuno dei due modifica magicamente il funzionamento di JavaScript tornando indietro nel tempo e modificando il modo in cui sono stati passati alla funzione.
Matteo Leggi il

9
Questa risposta ha chiarito la precedente, che, sebbene abbia più voti (287 in questo momento in cui scrivo ed è anche quella accettata) non era abbastanza chiara su come passarla effettivamente per riferimento in JS. In breve, il codice in questa risposta ha funzionato, la risposta che ha raccolto più voti no.
Pap,

25

Soluzione alternativa per passare la variabile come per riferimento:

var a = 1;
inc = function(variableName) {
  window[variableName] += 1;
};

inc('a');

alert(a); // 2


MODIFICARE

sì, in realtà puoi farlo senza accesso globale

inc = (function () {
    var variableName = 0;

    var init = function () {
        variableName += 1;
        alert(variableName);
    }

    return init;
})();

inc();

@Phil è bene stare attenti ai valori globali / finestra, ma a un certo punto tutto ciò che stiamo facendo nel browser è figlio o discendente dell'oggetto finestra. In nodejs, tutto è un discendente di GLOBAL. Nei linguaggi di oggetti compilati, questo è un oggetto genitore implicito se non esplicito, perché altrimenti renderebbe la gestione dell'heap più complicata (e per cosa?).
dkloke,

1
@dkloke: Sì, eventualmente l'oggetto window deve essere toccato, come jQuery usa window. $ / window.jQuery e altri metodi sono sotto questo. Stavo parlando dell'inquinamento dello spazio dei nomi globale in cui si aggiungono molte variabili piuttosto che inserirli in uno spazio dei nomi unificante.
Phil

2
Non riesco a trovare buone parole per esprimere quanto sia negativo questo approccio; (
SOReader

Adoro l'ultima soluzione (versione modificata) anche se non passa la variabile per riferimento. Questo è un vantaggio perché puoi accedere direttamente alle variabili.
John Boe,

11

Oggetto semplice

var ref = { value: 1 };

function Foo(x) {
    x.value++;
}

Foo(ref);
Foo(ref);

alert(ref.value); // Alert: 3

Oggetto personalizzato

Oggetto rvar

function rvar (name, value, context) {
    if (this instanceof rvar) {
        this.value = value;
        Object.defineProperty(this, 'name', { value: name });
        Object.defineProperty(this, 'hasValue', { get: function () { return this.value !== undefined; } });
        if ((value !== undefined) && (value !== null))
            this.constructor = value.constructor;
        this.toString = function () { return this.value + ''; };
    } else {
        if (!rvar.refs)
            rvar.refs = {};
        if (!context)
            context = window;
        // Private
        rvar.refs[name] = new rvar(name, value);
        // Public
        Object.defineProperty(context, name, {
            get: function () { return rvar.refs[name]; },
            set: function (v) { rvar.refs[name].value = v; },
            configurable: true
        });

        return context[name];
    }
}

Dichiarazione variabile

rvar('test_ref');
test_ref = 5; // test_ref.value = 5

O:

rvar('test_ref', 5); // test_ref.value = 5

Codice test

rvar('test_ref_number');
test_ref_number = 5;
function Fn1 (v) { v.value = 100; }
console.log("rvar('test_ref_number');");
console.log("test_ref_number = 5;");
console.log("function Fn1 (v) { v.value = 100; }");
console.log('test_ref_number.value === 5', test_ref_number.value === 5);
console.log(" ");

Fn1(test_ref_number);
console.log("Fn1(test_ref_number);");
console.log('test_ref_number.value === 100', test_ref_number.value === 100);
console.log(" ");

test_ref_number++;
console.log("test_ref_number++;");
console.log('test_ref_number.value === 101', test_ref_number.value === 101);
console.log(" ");

test_ref_number = test_ref_number - 10;
console.log("test_ref_number = test_ref_number - 10;");
console.log('test_ref_number.value === 91', test_ref_number.value === 91);

console.log(" ");
console.log("---------");
console.log(" ");

rvar('test_ref_str', 'a');
console.log("rvar('test_ref_str', 'a');");
console.log('test_ref_str.value === "a"', test_ref_str.value === 'a');
console.log(" ");

test_ref_str += 'bc';
console.log("test_ref_str += 'bc';");
console.log('test_ref_str.value === "abc"', test_ref_str.value === 'abc');

Risultato della console di test

rvar('test_ref_number');
test_ref_number = 5;
function Fn1 (v) { v.value = 100; }
test_ref_number.value === 5 true

Fn1(test_ref_number);
test_ref_number.value === 100 true

test_ref_number++;
test_ref_number.value === 101 true

test_ref_number = test_ref_number - 10;
test_ref_number.value === 91 true

---------

rvar('test_ref_str', 'a');
test_ref_str.value === "a" true

test_ref_str += 'bc';
test_ref_str.value === "abc" true 

5

Ancora un altro approccio per passare qualsiasi variabile (locale, primitiva) per riferimento è avvolgendo la variabile con la chiusura "al volo" di eval. Questo funziona anche con "uso rigoroso". (Nota: essere consapevoli del fatto che evalnon è amichevole con gli ottimizzatori JS, anche la mancanza di virgolette attorno al nome della variabile può causare risultati senza ostacoli)

"use strict"

//return text that will reference variable by name (by capturing that variable to closure)
function byRef(varName){
    return "({get value(){return "+varName+";}, set value(v){"+varName+"=v;}})";
}

//demo

//assign argument by reference
function modifyArgument(argRef, multiplier){
    argRef.value = argRef.value * multiplier;
}

(function(){

var x = 10;

alert("x before: " + x);
modifyArgument(eval(byRef("x")), 42);
alert("x after: " + x);

})()

Esempio live https://jsfiddle.net/t3k4403w/


Questo è fantastico
hard_working_ant

3

In realtà c'è un bel sollution:

function updateArray(context, targetName, callback) {
    context[targetName] = context[targetName].map(callback);
}

var myArray = ['a', 'b', 'c'];
updateArray(this, 'myArray', item => {return '_' + item});

console.log(myArray); //(3) ["_a", "_b", "_c"]

3

Personalmente non mi piace la funzionalità "passa per riferimento" offerta da vari linguaggi di programmazione. Forse è perché sto solo scoprendo i concetti di programmazione funzionale, ma ottengo sempre la pelle d'oca quando vedo funzioni che causano effetti collaterali (come manipolare i parametri passati per riferimento). Personalmente abbraccio fortemente il principio della "responsabilità singola".

IMHO, una funzione dovrebbe restituire solo un risultato / valore usando la parola chiave return. Invece di modificare un parametro / argomento, vorrei solo restituire il valore del parametro / argomento modificato e lasciare tutte le riassegnazioni desiderate fino al codice chiamante.

Ma a volte (si spera molto raramente), è necessario restituire due o più valori di risultato dalla stessa funzione. In tal caso, sceglierei di includere tutti quei valori risultanti in una singola struttura o oggetto. Ancora una volta, l'elaborazione di qualsiasi riassegnazione dovrebbe essere all'altezza del codice chiamante.

Esempio:

Supponiamo che i parametri di passaggio siano supportati usando una parola chiave speciale come 'ref' nell'elenco degli argomenti. Il mio codice potrebbe assomigliare a questo:

//The Function
function doSomething(ref value) {
    value = "Bar";
}

//The Calling Code
var value = "Foo";
doSomething(value);
console.log(value); //Bar

Preferirei invece fare qualcosa del genere:

//The Function
function doSomething(value) {
    value = "Bar";
    return value;
}

//The Calling Code:
var value = "Foo";
value = doSomething(value); //Reassignment
console.log(value); //Bar

Quando avrei bisogno di scrivere una funzione che restituisca più valori, non userei neanche i parametri passati per riferimento. Quindi eviterei il codice in questo modo:

//The Function
function doSomething(ref value) {
    value = "Bar";

    //Do other work
    var otherValue = "Something else";

    return otherValue;
}

//The Calling Code
var value = "Foo";
var otherValue = doSomething(value);
console.log(value); //Bar
console.log(otherValue); //Something else

Invece, in realtà preferirei restituire entrambi i nuovi valori all'interno di un oggetto, in questo modo:

//The Function
function doSomething(value) {
    value = "Bar";

    //Do more work
    var otherValue = "Something else";

    return {
        value: value,
        otherValue: otherValue
    };
}

//The Calling Code:
var value = "Foo";
var result = doSomething(value);
value = result.value; //Reassignment
console.log(value); //Bar
console.log(result.otherValue);

Questi esempi di codice sono abbastanza semplificati, ma dimostrano approssimativamente come personalmente gestirò tali cose. Mi aiuta a mantenere varie responsabilità nel posto giusto.

Buona codifica. :)


Assegnare e riassegnare cose (anche senza alcun controllo del tipo) è ciò che mi dà la pelle d'oca. Per quanto riguarda la responsabilità, mi aspetto che doSomething (), faccia quella cosa, non faccia quella cosa più faccia un oggetto più e assegni le mie variabili alle proprietà. Supponiamo di avere un array che deve essere cercato. Vorrei che gli elementi corrispondenti fossero inseriti in un secondo array e vorrei sapere quanti ne sono stati trovati. Una soluzione standard sarebbe quella di chiamare una funzione come questa: 'var foundArray; if ((findStuff (inArray, e foundArray))> 1) {// process foundArray} '. Da nessuna parte in quello scenario è desiderabile o necessario un oggetto nuovo, sconosciuto.
Elise van Looij,

@ElisevanLooij Nel tuo caso di esempio, preferirei findStuffsemplicemente restituire l'array risultante. Anche voi dovete dichiarare una foundArrayvariabile, quindi vorrei assegnare direttamente l'array risultante ad esso: var foundArray = findStuff(inArray); if (foundArray.length > 0) { /* process foundArray */ }. Ciò 1) renderebbe il codice chiamante più leggibile / comprensibile e 2) semplificherà considerevolmente la funzionalità interna (e quindi anche la testabilità) del findStuffmetodo, rendendolo in realtà molto più flessibile in diversi casi / riutilizzo / scenari.
Bart Hofland,

@ElisevanLooij Tuttavia, sono d'accordo sul fatto che le riassegnazioni (come nella mia risposta) sono davvero un "odore di codice" e vorrei davvero evitare anche il più possibile. Penserò a modificare (o persino riformulare) la mia risposta in modo tale da riflettere meglio la mia attuale opinione sull'argomento. Grazie per la tua reazione :)
Bart Hofland,

2

Ho giocato con la sintassi per fare questo genere di cose, ma richiede alcuni aiutanti che sono un po 'insoliti. Inizia con non usare affatto 'var', ma un semplice aiuto 'DECLARE' che crea una variabile locale e ne definisce un ambito tramite un callback anonimo. Controllando come vengono dichiarate le variabili, possiamo scegliere di avvolgerle in oggetti in modo che possano sempre essere passate per riferimento, essenzialmente. Questo è simile a una delle risposte di Eduardo Cuomo sopra, ma la soluzione di seguito non richiede l'uso di stringhe come identificatori di variabili. Ecco un po 'di codice minimo per mostrare il concetto.

function Wrapper(val){
    this.VAL = val;
}
Wrapper.prototype.toString = function(){
    return this.VAL.toString();
}

function DECLARE(val, callback){
    var valWrapped = new Wrapper(val);    
    callback(valWrapped);
}

function INC(ref){
    if(ref && ref.hasOwnProperty('VAL')){
        ref.VAL++; 
    }
    else{
        ref++;//or maybe throw here instead?
    }

    return ref;
}

DECLARE(5, function(five){ //consider this line the same as 'let five = 5'
console.log("five is now " + five);
INC(five); // increment
console.log("five is incremented to " + five);
});

2

in realtà è davvero facile,

il problema è capire che, una volta passati argomenti classici, si passa a un'altra zona di sola lettura.

la soluzione è passare gli argomenti usando il design orientato agli oggetti di JavaScript,

equivale a mettere gli arg in una variabile globale / con ambito, ma meglio ...

function action(){
  /* process this.arg, modification allowed */
}

action.arg = [ ["empty-array"],"some string",0x100,"last argument" ];
action();

puoi anche promettere cose per goderti la famosa catena: ecco il tutto, con una struttura simile a una promessa

function action(){
  /* process this.arg, modification allowed */
  this.arg = ["a","b"];
}

action.setArg = function(){this.arg = arguments; return this;}

action.setArg(["empty-array"],"some string",0x100,"last argument")()

o meglio ancora .. action.setArg(["empty-array"],"some string",0x100,"last argument").call()


this.argfunziona solo con actionistanza. Questo non funziona.
Eduardo Cuomo,

1

Javascript può modificare gli elementi dell'array all'interno di una funzione (viene passato come riferimento all'oggetto / array).

function makeAllPretty(items) {
   for (var x = 0; x < myArray.length; x++){
      //do stuff to the array
      items[x] = makePretty(items[x]);
   }
}

myArray = new Array(var1, var2, var3);
makeAllPretty(myArray);

Ecco un altro esempio:

function inc(items) {
  for (let i=0; i < items.length; i++) {
    items[i]++;
  }
}

let values = [1,2,3];
inc(values);
console.log(values);
// prints [2,3,4]

1

Non essendo il tipo forte, ti consente di risolvere i problemi in molti modi diversi, come sembrano in questo battistrada.

Tuttavia, dal punto di vista della manutenibilità, dovrei essere d'accordo con Bart Hofland. La funzione dovrebbe ottenere args per fare qualcosa con e restituire il risultato. Rendendoli facilmente riutilizzabili.

Se ritieni che le variabili debbano essere passate per riferimento, potresti essere meglio servito Costruendole in oggetti IMHO.


1

Mettendo da parte la discussione pass-by-reference, coloro che stanno ancora cercando una soluzione alla domanda dichiarata potrebbero usare:

const myArray = new Array(var1, var2, var3);
myArray.forEach(var => var = makePretty(var));

0

So esattamente cosa intendi. La stessa cosa in Swift non sarà un problema. La linea di fondo è letnon usare var.

Il fatto che le primitive siano passate per valore, ma il fatto che il valore di var ial punto dell'iterazione non sia copiato nella funzione anonima è abbastanza sorprendente per non dire altro.

for (let i = 0; i < boxArray.length; i++) {
  boxArray[i].onclick = function() { console.log(i) }; // correctly prints the index
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.