Pasar variables por referencia en Javascript

285

¿Cómo paso variables por referencia en JavaScript? Tengo 3 variables para las que quiero realizar varias operaciones, por lo que quiero ponerlas en un bucle for y realizar las operaciones para cada una.

pseudocódigo:

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

¿Cuál es la mejor manera de hacer esto?

BFTrick
fuente
31
Estás hablando de 'pasar por referencia', pero no tienes llamadas de función en tu ejemplo, por lo que no hay ningún pase en tu ejemplo. Por favor, aclare lo que está tratando de hacer.
jfriend00
1
Perdón por la confusion. No necesitaba específicamente escribir una función, por lo que 'pasar por referencia' era una mala elección de palabras. Solo quiero poder realizar algunas operaciones a variables sin escribir makePretty(var1); makePretty(var2); makePretty(var3); ...
BFTrick
en función de su comentario: arr = [var1, var2, var3]; for (var i = 0, len = arr.length; i < len; i++) { arr[i] = makePretty(arr[i]); }solo necesita almacenar el valor devuelto por makePrettyla ranura en la matriz.
dylnmc

Respuestas:

415

No hay "pasar por referencia" disponible en JavaScript. Puede pasar un objeto (es decir, puede pasar por valor una referencia a un objeto) y luego hacer que una función modifique el contenido del objeto:

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

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

alterObject(myObj);

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

Puede iterar sobre las propiedades de una matriz con un índice numérico y modificar cada celda de la matriz, si lo desea.

var arr = [1, 2, 3];

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

Es importante tener en cuenta que "pasar por referencia" es un término muy específico. No significa simplemente que es posible pasar una referencia a un objeto modificable. En cambio, significa que es posible pasar una variable simple de tal manera que permita que una función modifique ese valor en el contexto de la llamada . Entonces:

 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"

En un lenguaje como C ++, es posible hacer eso porque ese lenguaje tiene (tipo de) paso por referencia.

editar : esto recientemente (marzo de 2015) explotó en Reddit nuevamente en una publicación de blog similar a la mía mencionada a continuación, aunque en este caso sobre Java. Se me ocurrió mientras leía los comentarios de Reddit que una gran parte de la confusión se deriva de la desafortunada colisión que implica la palabra "referencia". La terminología "pasar por referencia" y "pasar por valor" es anterior al concepto de tener "objetos" para trabajar en lenguajes de programación. Realmente no se trata de objetos en absoluto; se trata de parámetros de función, y específicamente cómo los parámetros de función están "conectados" (o no) al entorno de llamada. En particular,, y se vería exactamente igual que en JavaScript. Sin embargo, uno también podría modificar la referencia de objeto en el entorno de llamada, y eso es lo clave que no puede hacer en JavaScript. Un lenguaje de paso por referencia no pasaría la referencia en sí, sino una referencia a la referencia .

editar : aquí hay una publicación de blog sobre el tema. (Tenga en cuenta el comentario a esa publicación que explica que C ++ realmente no tiene paso por referencia. Eso es cierto. Sin embargo, lo que sí tiene C ++ es la capacidad de crear referencias a variables simples, ya sea explícitamente en el punto de función invocación para crear un puntero, o implícitamente al llamar a funciones cuya firma de tipo de argumento requiere que se haga. Esas son las cosas clave que JavaScript no admite).

Puntiagudo
fuente
8
Puede pasar una referencia a un objeto o una matriz que le permite cambiar el objeto o matriz original, que creo que es lo que realmente está preguntando el OP.
jfriend00
2
Bueno, el OP utilizó la terminología, pero el código real no parece implicar ningún "paso" en absoluto :-) No estoy realmente seguro de lo que está tratando de hacer.
Puntiagudo
55
Pasar una referencia por valor no es lo mismo que pasar por referencia , aunque puede parecerlo en algunos escenarios como este.
Travis Webb
55
Tu blogger está equivocado acerca de C ++, que hace referencia por referencia, y su publicación no tiene sentido. Es irrelevante que no pueda cambiar el valor de una referencia después de la inicialización; eso no tiene nada que ver con 'pasar por referencia'. Su afirmación de que 'La semántica de "pasar por referencia" puede rastrearse a través de C #' debería haber sonado una alarma.
EML
77
@Pointy Esta es una respuesta horrible. Si necesito ayuda, lo último que quiero es que alguien me enseñe semántica. "Pasar por referencia" simplemente significa, "la función, hasta cierto punto, puede cambiar el valor de la variable que se pasó como argumento". (en el contexto de la pregunta) Realmente no es tan complicado como parece.
sin corona
108
  1. Las variables de tipo primitivo como cadenas y números siempre se pasan por valor.
  2. Las matrices y los objetos se pasan por referencia o por valor en función de estas condiciones:

    • si está configurando el valor de un objeto o matriz, es Pasar por valor.

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

    • si está cambiando el valor de una propiedad de un objeto o matriz, entonces es Pasar por referencia.

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

Código

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

Mukund Kumar
fuente
21
Ese no es el significado de "pasar por referencia". El término realmente no tiene nada que ver con los objetos, sino con la relación entre los parámetros en una función llamada y las variables en el entorno de llamada.
Pointy
12
Todo siempre se pasa por valor. Con los no primitivos, el valor que se pasa es una referencia. La asignación a una referencia cambia esa referencia, naturalmente, ya que es un valor, y por lo tanto no puede cambiar el otro objeto al que se hizo referencia originalmente. La mutación del objeto señalado por una referencia actúa exactamente como cabría esperar, al mutar el objeto señalado. Ambos escenarios son perfectamente consistentes, y ninguno de ellos altera mágicamente cómo funciona JavaScript al retroceder en el tiempo y cambiar la forma en que se pasaron a la función.
Matthew leyó el
9
Esta respuesta aclaró la anterior, que, a pesar de que tiene más votos (287 en este momento de la redacción y también es la aceptada) no era lo suficientemente clara en cuanto a cómo pasarla por referencia en JS. En resumen, el código en esta respuesta funcionó, la respuesta que reunió más votos no.
Pap
25

Solución alternativa para pasar variables como referencia:

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

inc('a');

alert(a); // 2


EDITAR

sí, en realidad puedes hacerlo sin acceso global

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

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

    return init;
})();

inc();
usuario2410595
fuente
@ Phil es bueno tener cuidado con los valores globales / de ventana, pero en algún momento todo lo que estamos haciendo en el navegador es un elemento secundario o descendiente del objeto de ventana. En nodejs, todo es un descendiente de GLOBAL. En los lenguajes de objetos compilados, ese es un objeto padre implícito, si no explícito, porque hacerlo de otra manera hace que la gestión de montón sea más complicada (¿y para qué?).
dkloke
1
@dkloke: Sí, eventualmente el objeto de ventana necesita ser tocado, como jQuery usa window. $ / window.jQuery y otros métodos están debajo de esto. Estaba hablando de la contaminación del espacio de nombres global donde se le agregan muchas variables en lugar de tenerlas bajo un espacio de nombres unificador.
Phil
2
No puedo encontrar buenas palabras para expresar cuán malo es ese enfoque; (
SOReader
Me encanta la última solución (versión editada) incluso que no pasa variable por referencia. Eso es una ventaja porque puede acceder a las variables directamente.
John Boe
11

Objeto simple

var ref = { value: 1 };

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

Foo(ref);
Foo(ref);

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

Objeto personalizado

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

Declaración Variable

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

O:

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

Código de prueba

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

Test Console Result

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 
Eduardo Cuomo
fuente
5

Otro enfoque para pasar cualquier variable (local, primitiva) por referencia es envolviendo la variable con cierre "sobre la marcha" eval. Esto también funciona con "uso estricto". (Nota: tenga en cuenta que evalno es amigable con los optimizadores JS, también las comillas faltantes alrededor del nombre de la variable pueden causar resultados impredecibles)

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

})()

Muestra en vivo https://jsfiddle.net/t3k4403w/

Pavlo Mur
fuente
Esto es increíble
hard_working_ant 01 de
3

En realidad hay una bonita solución:

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"]
Mateus Araújo
fuente
2

He estado jugando con la sintaxis para hacer este tipo de cosas, pero requiere algunos ayudantes que son un poco inusuales. Comienza sin usar 'var' en absoluto, sino un simple ayudante 'DECLARE' que crea una variable local y define un alcance para ello a través de una devolución de llamada anónima. Al controlar cómo se declaran las variables, podemos elegir envolverlas en objetos para que siempre puedan pasarse por referencia, esencialmente. Esto es similar a una de las respuestas de Eduardo Cuomo anteriores, pero la solución a continuación no requiere el uso de cadenas como identificadores variables. Aquí hay un código mínimo para mostrar el concepto.

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);
});
Adam Wise
fuente
2

en realidad es muy fácil

El problema es comprender que una vez que pasa los argumentos clásicos, está dentro de otra zona de solo lectura.

soluciones es pasar los argumentos usando el diseño orientado a objetos de JavaScript,

es lo mismo que poner los argumentos en una variable global / de ámbito, pero mejor ...

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

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

También puede prometer cosas para disfrutar de la conocida cadena: aquí está todo, con una estructura prometedora

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 mejor aún.. action.setArg(["empty-array"],"some string",0x100,"last argument").call()


fuente
this.argsolo funciona con actioninstancia. Esto no funciona.
Eduardo Cuomo
2

Personalmente no me gusta la funcionalidad "pasar por referencia" que ofrecen varios lenguajes de programación. Quizás es porque estoy descubriendo los conceptos de programación funcional, pero siempre me pone la piel de gallina cuando veo funciones que causan efectos secundarios (como la manipulación de parámetros pasados ​​por referencia). Yo personalmente abrazo firmemente el principio de "responsabilidad única".

En mi humilde opinión, una función debe devolver solo un resultado / valor utilizando la palabra clave return. En lugar de modificar un parámetro / argumento, simplemente devolvería el valor modificado del parámetro / argumento y dejaría cualquier reasignación deseada al código de llamada.

Pero a veces (con suerte muy raramente), es necesario devolver dos o más valores de resultado de la misma función. En ese caso, optaría por incluir todos esos valores resultantes en una sola estructura u objeto. Nuevamente, el procesamiento de cualquier reasignación debe corresponder al código de llamada.

Ejemplo:

Supongamos que se admiten parámetros de paso mediante el uso de una palabra clave especial como 'ref' en la lista de argumentos. Mi código podría verse así:

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

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

En cambio, preferiría hacer algo como esto:

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

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

Cuando necesito escribir una función que devuelve múltiples valores, tampoco usaría parámetros pasados ​​por referencia. Entonces evitaría un código como este:

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

En cambio, preferiría devolver ambos valores nuevos dentro de un objeto, como este:

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

Estos ejemplos de código están bastante simplificados, pero demuestran más o menos cómo yo personalmente manejaría tales cosas. Me ayuda a mantener varias responsabilidades en el lugar correcto.

Feliz codificación :)

Bart Hofland
fuente
Asignar y reasignar cosas (sin verificación de tipo también) es lo que me pone la piel de gallina. En cuanto a la responsabilidad, espero que doSomething (), haga esa cosa, no haga esa cosa más hacer un objeto más y asignar mis variables a las propiedades. Digamos que tengo una matriz que debe buscarse. Me gustaría colocar los elementos coincidentes en una segunda matriz y quiero saber cuántos se encontraron. Una solución estándar sería llamar a una función como esta: 'var foundArray; if ((findStuff (inArray, & foundArray))> 1) {// process foundArray} '. En ninguna parte de ese escenario es deseable o necesario un nuevo objeto desconocido.
Elise van Looij
@ElisevanLooij En su caso de ejemplo, preferiría haber findStuffdevuelto simplemente la matriz resultante. Usted también tiene que declarar una foundArrayvariable, por lo que iba a asignar directamente la matriz resultante a la misma: var foundArray = findStuff(inArray); if (foundArray.length > 0) { /* process foundArray */ }. Esto 1) haría que el código de llamada sea más legible / comprensible, y 2) simplifique considerablemente la funcionalidad interna (y, por lo tanto, también la capacidad de prueba) del findStuffmétodo, haciéndolo mucho más flexible en diferentes (re) casos / escenarios de uso.
Bart Hofland
@ElisevanLooij Sin embargo, estoy de acuerdo en que las reasignaciones (como en mi respuesta) son de hecho un "olor a código" y en realidad me gustaría evitarlas tanto como sea posible. Pensaré en editar (o incluso reformular) mi respuesta de tal manera que refleje mejor mi opinión real sobre el tema. Gracias por tu reacción. :)
Bart Hofland
1

Javascript puede modificar elementos de la matriz dentro de una función (se pasa como referencia al objeto / matriz).

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

Aquí hay otro ejemplo:

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]
Michiel van der Blonk
fuente
1

JS no es de tipo fuerte, le permite resolver problemas de muchas maneras diferentes, como parece en este dibujo.

Sin embargo, desde el punto de vista del mantenimiento, tendría que estar de acuerdo con Bart Hofland. La función debería hacer que args haga algo y devuelva el resultado. Haciéndolos fácilmente reutilizables.

Si cree que las variables deben pasarse por referencia, es mejor que las convierta en objetos en mi humilde opinión.

Michel S
fuente
1

Dejando a un lado la discusión de paso por referencia, aquellos que todavía buscan una solución a la pregunta planteada podrían usar:

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

Sé exactamente a que te refieres. Lo mismo en Swift no será un problema. La conclusión es el uso letno var.

El hecho de que las primitivas pasen por valor, pero el hecho de que el valor de var ien el punto de iteración no se copia en la función anónima es bastante sorprendente, por decir lo menos.

for (let i = 0; i < boxArray.length; i++) {
  boxArray[i].onclick = function() { console.log(i) }; // correctly prints the index
}
funct7
fuente