¿Es JavaScript un lenguaje de paso por referencia o de paso por valor?

Resuelto Danail Nachev asked hace 15 años • 0 respuestas

Los tipos primitivos (número, cadena, etc.) se pasan por valor, pero los objetos son desconocidos, porque ambos pueden pasarse por valor (en cuyo caso consideramos que una variable que contiene un objeto es de hecho una referencia al objeto) y pasado por referencia (cuando consideramos que la variable del objeto contiene el objeto mismo).

Aunque al final realmente no importa, quiero saber cuál es la forma correcta de presentar los argumentos pasando las convenciones. ¿Existe un extracto de la especificación de JavaScript que defina cuál debería ser la semántica al respecto?

Danail Nachev avatar Feb 06 '09 04:02 Danail Nachev
Aceptado

Es interesante en JavaScript. Considere este ejemplo:

function changeStuff(a, b, c)
{
  a = a * 10;
  b.item = "changed";
  c = {item: "changed"};
}

var num = 10;
var obj1 = {item: "unchanged"};
var obj2 = {item: "unchanged"};

changeStuff(num, obj1, obj2);

console.log(num);
console.log(obj1.item);
console.log(obj2.item);
Expandir fragmento

Esto produce la salida:

10
changed
unchanged
  • Si obj1no fuera una referencia en absoluto, el cambio obj1.itemno tendría ningún efecto en el obj1exterior de la función.
  • Si el argumento fuera una referencia adecuada, entonces todo habría cambiado. numsería 100, y obj2.itemleería "changed". En cambio, numse queda 10y obj2.itempermanece "unchanged".

En cambio, la situación es que el elemento pasado se pasa por valor. Pero el elemento que se pasa por valor es en sí mismo una referencia. Técnicamente, esto se llama llamada compartida .

En términos prácticos, esto significa que si cambia el parámetro en sí (como con numy obj2), eso no afectará el elemento que se introdujo en el parámetro. Pero si cambia los aspectos internos del parámetro, se propagará de nuevo (como ocurre con obj1).

deworde avatar Sep 03 '2010 17:09 deworde

Siempre se pasa por valor, pero para los objetos el valor de la variable es una referencia. Debido a esto, cuando pasas un objeto y cambias sus miembros , esos cambios persisten fuera de la función. Esto hace que parezca pasar por referencia. Pero si realmente cambia el valor de la variable del objeto, verá que el cambio no persiste, lo que demuestra que realmente se pasa por valor.

Ejemplo:

function changeObject(x) {
  x = { member: "bar" };
  console.log("in changeObject: " + x.member);
}

function changeMember(x) {
  x.member = "bar";
  console.log("in changeMember: " + x.member);
}

var x = { member: "foo" };

console.log("before changeObject: " + x.member);
changeObject(x);
console.log("after changeObject: " + x.member); /* change did not persist */

console.log("before changeMember: " + x.member);
changeMember(x);
console.log("after changeMember: " + x.member); /* change persists */
Expandir fragmento

Producción:

before changeObject: foo
in changeObject: bar
after changeObject: foo

before changeMember: foo
in changeMember: bar
after changeMember: bar
Tim Goodman avatar Mar 15 '2011 16:03 Tim Goodman

La variable no "retiene" el objeto; tiene una referencia. Puedes asignar esa referencia a otra variable y ahora ambas hacen referencia al mismo objeto. Siempre se pasa por valor (incluso cuando ese valor es una referencia...).

No hay forma de alterar el valor mantenido por una variable pasada como parámetro, lo cual sería posible si JavaScript admitiera el paso por referencia.

Shog9 avatar Feb 05 '2009 21:02 Shog9

Mi granito de arena... Así es como yo lo entiendo. (No dudes en corregirme si me equivoco)

Es hora de deshacerse de todo lo que sabe sobre el paso por valor/referencia.

Porque en JavaScript, no importa si se pasa por valor o por referencia o lo que sea. Lo que importa es la mutación versus la asignación de los parámetros pasados ​​a una función.

Bien, déjame hacer lo mejor que pueda para explicar lo que quiero decir. Digamos que tienes algunos objetos.

var object1 = {};
var object2 = {};

Lo que hemos hecho es "asignación"... Hemos asignado 2 objetos vacíos separados a las variables "objeto1" y "objeto2".

Ahora, digamos que nos gusta más el objeto1... Entonces, "asignamos" una nueva variable.

var favoriteObject = object1;

A continuación, por cualquier motivo, decidimos que nos gusta más el objeto 2. Entonces, hacemos una pequeña reasignación.

favoriteObject = object2;

No pasó nada con el objeto1 ni con el objeto2. No hemos cambiado ningún dato en absoluto. Todo lo que hicimos fue reasignar cuál es nuestro objeto favorito. Es importante saber que el objeto2 y el objeto favorito están asignados al mismo objeto. Podemos cambiar ese objeto a través de cualquiera de esas variables.

object2.name = 'Fred';
console.log(favoriteObject.name) // Logs Fred
favoriteObject.name = 'Joe';
console.log(object2.name); // Logs Joe

Bien, ahora veamos primitivas como cadenas, por ejemplo.

var string1 = 'Hello world';
var string2 = 'Goodbye world';

De nuevo, elegimos un favorito.

var favoriteString = string1;

Nuestras variables cadena favorita y cadena1 están asignadas a 'Hola mundo'. Ahora bien, ¿y si queremos cambiar nuestra cadena favorita? ¿¿¿Lo que sucederá???

favoriteString = 'Hello everyone';
console.log(favoriteString); // Logs 'Hello everyone'
console.log(string1); // Logs 'Hello world'

Uh oh.... ¿Qué ha pasado? No pudimos cambiar la cadena1 cambiando la cadena favorita... ¿Por qué? Porque no cambiamos nuestro objeto de cadena . Todo lo que hicimos fue "RE ASIGNAR" la variable cadena favorita a una nueva cadena. Básicamente, esto lo desconectó de string1. En el ejemplo anterior, cuando cambiamos el nombre de nuestro objeto, no asignamos nada. (Bueno, no a la variable en sí ... sin embargo, asignamos la propiedad de nombre a una nueva cadena). En su lugar, mutamos el objeto que mantiene las conexiones entre las 2 variables y los objetos subyacentes. (Incluso si hubiéramos querido modificar o mutar el objeto de cadena en sí , no podríamos haberlo hecho, porque las cadenas son en realidad inmutables en JavaScript).

Ahora, pasemos a las funciones y a pasar parámetros... Cuando llamas a una función y pasas un parámetro, lo que básicamente estás haciendo es una "asignación" a una nueva variable, y funciona exactamente igual que si la asignases usando el signo igual (=).

Tomemos estos ejemplos.

var myString = 'hello';

// Assign to a new variable (just like when you pass to a function)
var param1 = myString;
param1 = 'world'; // Re assignment

console.log(myString); // Logs 'hello'
console.log(param1);   // Logs 'world'

Ahora lo mismo pero con una función

function myFunc(param1) {
    param1 = 'world';

    console.log(param1);   // Logs 'world'
}

var myString = 'hello';
// Calls myFunc and assigns param1 to myString just like param1 = myString
myFunc(myString);

console.log(myString); // logs 'hello'

Bien, ahora demos algunos ejemplos usando objetos... primero, sin la función.

var myObject = {
    firstName: 'Joe',
    lastName: 'Smith'
};

// Assign to a new variable (just like when you pass to a function)
var otherObj = myObject;

// Let's mutate our object
otherObj.firstName = 'Sue'; // I guess Joe decided to be a girl

console.log(myObject.firstName); // Logs 'Sue'
console.log(otherObj.firstName); // Logs 'Sue'

// Now, let's reassign the variable
otherObj = {
    firstName: 'Jack',
    lastName: 'Frost'
};

// Now, otherObj and myObject are assigned to 2 very different objects
// And mutating one object has no influence on the other
console.log(myObject.firstName); // Logs 'Sue'
console.log(otherObj.firstName); // Logs 'Jack';

Ahora lo mismo pero con una llamada a función.

function myFunc(otherObj) {

    // Let's mutate our object
    otherObj.firstName = 'Sue';
    console.log(otherObj.firstName); // Logs 'Sue'

    // Now let's re-assign
    otherObj = {
        firstName: 'Jack',
        lastName: 'Frost'
    };
    console.log(otherObj.firstName); // Logs 'Jack'

    // Again, otherObj and myObject are assigned to 2 very different objects
    // And mutating one object doesn't magically mutate the other
}

var myObject = {
    firstName: 'Joe',
    lastName: 'Smith'
};

// Calls myFunc and assigns otherObj to myObject just like otherObj = myObject
myFunc(myObject);

console.log(myObject.firstName); // Logs 'Sue', just like before

Bien, si lees esta publicación completa, quizás ahora comprendas mejor cómo funcionan las llamadas a funciones en JavaScript. No importa si algo se pasa por referencia o por valor... Lo que importa es la asignación frente a la mutación.

Cada vez que pasas una variable a una función, estás "Asignando" el nombre de la variable del parámetro, como si usaras el signo igual (=).

Recuerde siempre que el signo igual (=) significa asignación. Recuerde siempre que pasar un parámetro a una función en JavaScript también significa asignación. Son iguales y las 2 variables están conectadas exactamente de la misma manera (es decir, no lo están, a menos que cuentes que están asignadas al mismo objeto).

La única vez que "modificar una variable" afecta a una variable diferente es cuando el objeto subyacente está mutado (en cuyo caso no has modificado la variable, sino el objeto mismo.

No tiene sentido hacer una distinción entre objetos y primitivas, porque funciona exactamente de la misma manera que si no tuviera una función y simplemente usara el signo igual para asignar una nueva variable.

El único problema es cuando el nombre de la variable que pasa a la función es el mismo que el nombre del parámetro de la función. Cuando esto sucede, debe tratar el parámetro dentro de la función como si fuera una variable completamente nueva y privada de la función (porque lo es).

function myFunc(myString) {
    // myString is private and does not affect the outer variable
    myString = 'hello';
}

var myString = 'test';
myString = myString; // Does nothing, myString is still 'test';

myFunc(myString);
console.log(myString); // Logs 'test'
Ray Perea avatar Aug 04 '2014 11:08 Ray Perea

Estas frases/conceptos se definieron originalmente mucho antes de que se creara JS y no describen con precisión la semántica de JavaScript. Creo que intentar aplicarlos a JS causa más confusión que no.

Así que no se obsesione con "pasar por referencia/valor".

Considera lo siguiente:

  1. Las variables son punteros a valores.
  2. Reasignar una variable simplemente apunta ese puntero a un nuevo valor.
  3. Reasignar una variable nunca afectará a otras variables que apuntaban a ese mismo objeto porque cada variable tiene su propio puntero.

Entonces, si tuviera que darle un nombre, diría "pasar por puntero" : no tratamos con punteros en JS, pero el motor subyacente sí.


// code
var obj = {
    name: 'Fred',
    num: 1
};

// illustration
               'Fred'
              /
             /
(obj) ---- {}
             \
              \
               1

// code
obj.name = 'George';


// illustration
                 'Fred'


(obj) ---- {} ----- 'George'
             \
              \
               1

// code
obj = {};

// illustration
                 'Fred'


(obj)      {} ----- 'George'
  |          \
  |           \
 { }            1

// code
var obj = {
    text: 'Hello world!'
};

/* function parameters get their own pointer to 
 * the arguments that are passed in, just like any other variable */
someFunc(obj);


// illustration
(caller scope)        (someFunc scope)
           \             /
            \           /
             \         /
              \       /
               \     /
                 { }
                  |
                  |
                  |
            'Hello world'

Algunos comentarios finales:

  • Las frases "pasar por valor/referencia" solo se utilizan para describir el comportamiento de un lenguaje, no necesariamente la implementación subyacente real. Como resultado de esta abstracción, se pierden detalles críticos que son esenciales para una explicación decente, lo que inevitablemente conduce a la situación actual en la que un solo término no describe adecuadamente el comportamiento real sin información adicional.
  • Es tentador pensar que las primitivas se aplican mediante reglas especiales, mientras que los objetos no, pero las primitivas son simplemente el final de la cadena de punteros.
  • Como ejemplo final, considere por qué un intento común de borrar una matriz no funciona como se esperaba.

var a = [1, 2];
var b = a;

a = [];
console.log(b); // [1,2]
// doesn't work because `b` is still pointing at the original array
Expandir fragmento

geg avatar Sep 26 '2015 04:09 geg