Passer les variables par référence en Javascript


285

Comment passer des variables par référence en JavaScript? J'ai 3 variables sur lesquelles je veux effectuer plusieurs opérations, donc je veux les mettre dans une boucle for et effectuer les opérations sur chacune.

pseudo code:

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

Quelle est la meilleure façon de procéder?


31
Vous parlez de «passer par référence», mais vous n'avez aucun appel de fonction dans votre exemple, il n'y a donc pas de passage du tout dans votre exemple. Veuillez clarifier ce que vous essayez de faire.
jfriend00

1
Désolé pour la confusion. Je n'avais pas spécifiquement besoin d'écrire une fonction, donc «passer par référence» était un mauvais choix de mots. Je veux juste pouvoir effectuer certaines opérations sur des variables sans écrire makePretty(var1); makePretty(var2); makePretty(var3); ...
BFTrick

en fonction de votre commentaire: arr = [var1, var2, var3]; for (var i = 0, len = arr.length; i < len; i++) { arr[i] = makePretty(arr[i]); }- il vous suffit de stocker la valeur renvoyée par makePrettyback dans l'emplacement du tableau.
dylnmc

Réponses:


415

Il n'y a pas de "passe par référence" disponible en JavaScript. Vous pouvez passer un objet (c'est-à-dire que vous pouvez passer par valeur une référence à un objet) puis demander à une fonction de modifier le contenu de l'objet:

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

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

alterObject(myObj);

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

Vous pouvez parcourir les propriétés d'un tableau avec un index numérique et modifier chaque cellule du tableau, si vous le souhaitez.

var arr = [1, 2, 3];

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

Il est important de noter que «passe-par-référence» est un terme très spécifique. Cela ne signifie pas simplement qu'il est possible de passer une référence à un objet modifiable. Au lieu de cela, cela signifie qu'il est possible de passer une simple variable de manière à permettre à une fonction de modifier cette valeur dans le contexte d' appel . Alors:

 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"

Dans un langage comme C ++, il est possible de le faire parce que cette langue fait (tri-de) ont passe par référence.

edit - cela a récemment (mars 2015) explosé sur Reddit à nouveau sur un blog similaire au mien mentionné ci-dessous, bien que dans ce cas sur Java. Il m'est venu à l'esprit en lisant les allers-retours dans les commentaires de Reddit qu'une grande partie de la confusion provient de la collision malheureuse impliquant le mot "référence". La terminologie «passe par référence» et «passe par valeur» est antérieure au concept de l'utilisation des «objets» dans les langages de programmation. Il ne s'agit vraiment pas d'objets du tout; il s'agit des paramètres de fonction, et plus précisément de la façon dont les paramètres de fonction sont «connectés» (ou non) à l'environnement appelant. En particulier, notez que dans un véritable langage passe-par-référence - celui qui le fait , et il ressemblerait à peu près exactement à ce qu'il fait en JavaScript. en mesure de modifier la référence d'objet dans l'environnement appelant, et c'est l'élément clé que vous ne pouvez pas faire en JavaScript. Un langage passant par référence passerait non pas la référence elle-même, mais une référence à la référence .

modifier - voici un article de blog sur le sujet. (Notez le commentaire de ce post qui explique que C ++ n'a pas vraiment de passe-par-référence. C'est vrai. Ce que C ++ a, cependant, est la possibilité de créer des références à des variables simples, soit explicitement au point de fonction invocation pour créer un pointeur, ou implicitement lors de l'appel de fonctions dont la signature de type argument demande que cela soit fait. Ce sont les éléments clés que JavaScript ne prend pas en charge.)


8
Vous pouvez transmettre une référence à un objet ou à un tableau qui vous permet de modifier l'objet ou le tableau d'origine, ce que je pense être ce que l'OP demande réellement.
jfriend00

2
Eh bien, l'OP a utilisé la terminologie, mais le code réel ne semble pas impliquer de "passage" du tout :-) Je ne suis pas vraiment sûr de ce qu'il essaie de faire.
Pointy

5
Passer une référence par valeur n'est pas la même chose que passer par référence , même si cela peut apparaître dans certains scénarios comme celui-ci.
Travis Webb

5
Votre blogueur se trompe sur C ++, qui ne passe par référence, et son poste n'a pas de sens. Il n'est pas pertinent que vous ne puissiez pas modifier la valeur d'une référence après l'initialisation; cela n'a rien à voir avec «passer par référence». Sa déclaration selon laquelle «la sémantique« passe par référence »peut être retracée à travers C #» aurait dû sonner l'alarme.
EML

7
@Pointy C'est une réponse horrible. Si j'avais besoin d'aide, la dernière chose que je voudrais, c'est que quelqu'un m'instruise en sémantique. "Pass-by-reference" signifie simplement, "la fonction, dans une certaine mesure, peut changer la valeur de la variable qui a été passée en argument." (dans le contexte de la question) Ce n'est vraiment pas aussi compliqué que vous le croyez.
crownlessking

110
  1. les variables de type primitif comme les chaînes et les nombres sont toujours passées par valeur.
  2. Les tableaux et les objets sont transmis par référence ou par valeur en fonction de ces conditions:

    • si vous définissez la valeur d'un objet ou d'un tableau, c'est Pass by Value.

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

    • si vous modifiez la valeur d'une propriété d'un objet ou d'un tableau, il s'agit de Passer par référence.

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

Code

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
Ce n'est pas le sens de «passer par référence». Le terme n'a vraiment rien à voir avec les objets, mais avec la relation entre les paramètres dans une fonction appelée et les variables dans l'environnement appelant.
Pointy

12
Tout est toujours transmis par valeur. Avec les non primitives, la valeur transmise est une référence. L'affectation à une référence modifie cette référence - naturellement, car il s'agit d'une valeur - et ne peut donc pas modifier l'autre objet référencé à l'origine. La mutation de l'objet pointé par une référence agit exactement comme vous vous y attendez, en mutant l'objet pointé. Les deux scénarios sont parfaitement cohérents, et ni l'un ni l'autre ne modifie comme par magie le fonctionnement de JavaScript en remontant dans le temps et en modifiant la façon dont ils ont été transmis à la fonction.
Matthew Read

9
Cette réponse a clarifié la précédente, qui, même si elle avait plus de votes (287 au moment de la rédaction du présent rapport et elle est également celle acceptée), n'était pas suffisamment claire sur la façon de la transmettre réellement par référence dans JS. En bref, le code dans cette réponse a fonctionné, la réponse qui a recueilli plus de votes n'a pas fonctionné.
Pap

25

Solution de contournement pour passer une variable comme par référence:

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

inc('a');

alert(a); // 2


ÉDITER

yup, en fait, vous pouvez le faire sans accès global

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

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

    return init;
})();

inc();

@Phil, il est bon de faire attention aux valeurs globales / fenêtre, mais à un moment donné, tout ce que nous faisons dans le navigateur est un enfant ou un descendant de l'objet fenêtre. Dans nodejs, tout est un descendant de GLOBAL. Dans les langages d'objets compilés, il s'agit d'un objet parent implicite sinon explicite, car sinon, cela complique la gestion du tas (et pour quoi?).
dkloke

1
@dkloke: Oui, finalement, l'objet window doit être touché, comme jQuery utilise window. $ / window.jQuery et d'autres méthodes sont en dessous. Je parlais de la pollution de l'espace de noms global où vous y ajoutez beaucoup de variables plutôt que de les avoir sous un espace de noms unificateur.
Phil

2
Je ne trouve pas de bons mots pour exprimer à quel point cette approche est mauvaise; (
SOReader

J'adore la dernière solution (version éditée) même qu'elle ne passe pas de variable par référence. C'est un avantage car vous pouvez accéder directement aux variables.
John Boe

11

Objet simple

var ref = { value: 1 };

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

Foo(ref);
Foo(ref);

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

Objet personnalisé

Objet 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];
    }
}

Déclaration variable

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

Ou:

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

Code de 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');

Résultat de la console de 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

Encore une autre approche pour passer toutes les variables (locales, primitives) par référence est d'envelopper la variable avec une fermeture "à la volée" eval. Cela fonctionne également avec "use strict". (Remarque: sachez que ce evaln'est pas convivial pour les optimiseurs JS, également les guillemets manquants autour du nom de la variable peuvent entraîner des résultats imprévisibles)

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

})()

Échantillon en direct https://jsfiddle.net/t3k4403w/


C'est génial
hard_working_ant

3

Il y a en fait une jolie 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

Personnellement, je n'aime pas la fonctionnalité «passer par référence» offerte par divers langages de programmation. C'est peut-être parce que je découvre juste les concepts de la programmation fonctionnelle, mais j'ai toujours la chair de poule quand je vois des fonctions qui provoquent des effets secondaires (comme la manipulation de paramètres passés par référence). Personnellement, j'adhère fermement au principe de la "responsabilité unique".

À mon humble avis, une fonction doit retourner un seul résultat / valeur en utilisant le mot-clé return. Au lieu de modifier un paramètre / argument, je retournerais simplement la valeur du paramètre / argument modifié et laisserais toutes les réaffectations souhaitées au code appelant.

Mais parfois (espérons-le très rarement), il est nécessaire de renvoyer deux ou plusieurs valeurs de résultat de la même fonction. Dans ce cas, je choisirais d'inclure toutes ces valeurs résultantes dans une seule structure ou un seul objet. Encore une fois, le traitement de toute réaffectation devrait être à la hauteur du code appelant.

Exemple:

Supposons que le passage de paramètres soit pris en charge en utilisant un mot clé spécial tel que «ref» dans la liste des arguments. Mon code pourrait ressembler à ceci:

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

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

Au lieu de cela, je préférerais réellement faire quelque chose comme ceci:

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

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

Lorsque j'aurais besoin d'écrire une fonction qui renvoie plusieurs valeurs, je n'utiliserais pas non plus les paramètres passés par référence. J'éviterais donc un code comme celui-ci:

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

Au lieu de cela, je préférerais en fait retourner les deux nouvelles valeurs à l'intérieur d'un objet, comme ceci:

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

Ces exemples de code sont assez simplifiés, mais ils montrent à peu près comment je gérerais personnellement ce genre de choses. Cela m'aide à garder diverses responsabilités au bon endroit.

Codage heureux. :)


Assigner et réassigner des trucs (sans aucune vérification de type aussi) est ce qui me donne la chair de poule. En ce qui concerne la responsabilité, je m'attends à ce que doSomething () fasse cette chose, ne fasse pas cette chose plus crée un objet plus et affecte mes variables aux propriétés. Disons que j'ai un tableau qui doit être recherché.Je voudrais que les éléments correspondants soient placés dans un deuxième tableau et je veux savoir combien ont été trouvés. Une solution standard serait d'appeler une fonction comme celle-ci: 'var foundArray; if ((findStuff (inArray, & foundArray))> 1) {// process foundArray} '. Nulle part dans ce scénario un nouvel objet inconnu n'est souhaitable ou nécessaire.
Elise van Looij

@ElisevanLooij Dans votre cas d'exemple, je préférerais findStuffsimplement renvoyer le tableau résultant. Vous avez trop de déclarer une foundArrayvariable donc j'assigner directement le tableau résultant à elle: var foundArray = findStuff(inArray); if (foundArray.length > 0) { /* process foundArray */ }. Cela 1) rendrait le code appelant plus lisible / compréhensible, et 2) simplifierait considérablement la fonctionnalité interne (et donc également la testabilité) de la findStuffméthode, la rendant en réalité beaucoup plus flexible dans différents (ré) cas d'utilisation / scénarios.
Bart Hofland

@ElisevanLooij Cependant, je suis d'accord que les réaffectations (comme dans ma réponse) sont en effet une "odeur de code" et je voudrais en fait éviter autant que possible. Je penserai à éditer (ou même à reformuler) ma réponse de manière à mieux refléter mon opinion réelle sur le sujet. Merci pour votre réaction. :)
Bart Hofland

2

J'ai joué avec la syntaxe pour faire ce genre de choses, mais cela nécessite des aides qui sont un peu inhabituelles. Cela commence par ne pas utiliser du tout 'var', mais un simple assistant 'DECLARE' qui crée une variable locale et définit une portée pour celle-ci via un rappel anonyme. En contrôlant la façon dont les variables sont déclarées, nous pouvons choisir de les encapsuler dans des objets afin qu'elles puissent toujours être transmises par référence, essentiellement. Ceci est similaire à l'une des réponses d'Eduardo Cuomo ci-dessus, mais la solution ci-dessous ne nécessite pas d'utiliser des chaînes comme identificateurs de variables. Voici un code minimal pour montrer le concept.

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

en fait c'est vraiment facile,

le problème est de comprendre qu'une fois les arguments classiques passés, vous êtes limité à une autre zone en lecture seule.

solutions est de passer les arguments en utilisant la conception orientée objet de JavaScript,

c'est la même chose que de mettre les arguments dans une variable globale / portée, mais mieux ...

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

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

vous pouvez également promettre des trucs pour profiter de la chaîne bien connue: voici le tout, avec une structure de type promesse

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

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


this.argne fonctionne qu'avec l' actioninstance. Cela ne fonctionne pas.
Eduardo Cuomo

1

Javascript peut modifier les éléments du tableau à l'intérieur d'une fonction (il est transmis comme référence à l'objet / tableau).

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

Voici un autre exemple:

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

JS n'étant pas de type fort, il vous permet de résoudre les problèmes de différentes manières, comme cela semble dans cette bande de roulement.

Cependant, pour le point de vue de la maintenabilité, je devrais être d'accord avec Bart Hofland. La fonction doit obtenir des arguments pour faire quelque chose et retourner le résultat. Les rendant facilement réutilisables.

Si vous pensez que les variables doivent être transmises par référence, vous pourriez être mieux servi en les construisant en objets à mon humble avis.


1

Mis à part la discussion passe-par-référence, ceux qui recherchent toujours une solution à la question posée pourraient utiliser:

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

0

Je sais exactement ce que vous voulez dire. La même chose dans Swift ne posera aucun problème. L'essentiel est de letne pas l' utiliser var.

Le fait que les primitives soient passées par valeur mais le fait que la valeur de var iau point d'itération ne soit pas copiée dans la fonction anonyme est pour le moins surprenant.

for (let i = 0; i < boxArray.length; i++) {
  boxArray[i].onclick = function() { console.log(i) }; // correctly prints the index
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.