Passar variáveis ​​por referência em Javascript


285

Como passo variáveis ​​por referência em JavaScript? Eu tenho 3 variáveis ​​nas quais quero executar várias operações, então quero colocá-las em um loop for e executar as operações para cada uma.

pseudo-có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

Qual é a melhor maneira de fazer isso?


31
Você está falando de 'passagem por referência', mas não possui chamadas de função no seu exemplo, portanto não há nenhuma passagem no seu exemplo. Por favor, esclareça o que você está tentando fazer.
jfriend00

1
Desculpe pela confusão. Eu não precisava especificamente escrever uma função, então 'passar por referência' era uma má escolha de palavras. Eu só quero ser capaz de executar algumas operações a variáveis sem escrever makePretty(var1); makePretty(var2); makePretty(var3); ...
BFTrick

com base no seu comentário: arr = [var1, var2, var3]; for (var i = 0, len = arr.length; i < len; i++) { arr[i] = makePretty(arr[i]); }- você só precisa armazenar o valor retornado de makePrettyvolta no slot na matriz.
Dylnmc 17/05/19

Respostas:


415

Não há "passagem por referência" disponível em JavaScript. Você pode transmitir um objeto (ou seja, pode passar por valor uma referência a um objeto) e, em seguida, fazer com que uma função modifique o conteúdo do objeto:

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

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

alterObject(myObj);

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

Você pode percorrer as propriedades de uma matriz com um índice numérico e modificar cada célula da matriz, se desejar.

var arr = [1, 2, 3];

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

É importante observar que "passagem por referência" é um termo muito específico. Isso não significa simplesmente que é possível passar uma referência a um objeto modificável. Em vez disso, significa que é possível passar uma variável simples de forma a permitir que uma função modifique esse valor no contexto de chamada . Assim:

 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"

Em uma linguagem como C ++, é possível fazer isso porque essa linguagem tem (mais ou menos) passagem por referência.

edit - isto recentemente (março de 2015) explodiu no Reddit novamente em uma postagem de blog semelhante à minha mencionada abaixo, embora neste caso sobre Java. Ocorreu-me enquanto lia as idas e vindas nos comentários do Reddit que grande parte da confusão decorre da infeliz colisão envolvendo a palavra "referência". A terminologia "passar por referência" e "passar por valor" antecede o conceito de ter "objetos" para trabalhar nas linguagens de programação. Realmente não se trata de objetos; trata-se de parâmetros de função e, especificamente, como os parâmetros de função estão "conectados" (ou não) ao ambiente de chamada. Em particular,, e seria muito parecido com o JavaScript. No entanto, também seria possível modificar a referência do objeto no ambiente de chamada, e é isso que você não pode fazer em JavaScript. Uma linguagem de passagem por referência passaria não a referência em si, mas uma referência à referência .

editar - aqui está uma postagem de blog sobre o tópico. (Observe o comentário dessa postagem que explica que o C ++ realmente não tem passagem por referência. Isso é verdade. O C ++ tem, no entanto, é a capacidade de criar referências a variáveis ​​simples, explicitamente no ponto da função invocação para criar um ponteiro ou implicitamente ao chamar funções cuja assinatura de tipo de argumento exige que isso seja feito. Essas são as principais coisas que o JavaScript não suporta.)


8
Você pode passar uma referência a um objeto ou matriz que permite alterar o objeto ou matriz original que acredito ser o que o OP está realmente perguntando.
jfriend00

2
Bem, o OP usou a terminologia, mas o código real não parece envolver nenhuma "passagem" :-) Não tenho muita certeza do que ele está tentando fazer.
Pointy

5
Passar uma referência por valor não é o mesmo que passar por referência , embora possa parecer em alguns cenários como este.
Travis Webb

5
O seu blogger é errado sobre C ++, que faz passar por referência, e seu cargo não faz sentido. É irrelevante que você não possa alterar o valor de uma referência após a inicialização; isso não tem nada a ver com 'passar por referência'. Sua afirmação de que 'a semântica "passagem por referência" pode ser rastreada através do C #' deveria ter tocado uma campainha de alarme.
EML

7
@ Pointy Esta é uma resposta horrível. Se eu precisasse de ajuda, a última coisa que gostaria seria de alguém me ensinando semântica. "Passagem por referência" significa simplesmente "a função, até certo ponto, pode alterar o valor da variável que foi passada como argumento". (no contexto da pergunta) Realmente não é tão complicado quanto você imagina.
precisa

108
  1. variáveis ​​de tipo primitivo, como strings e números, sempre são passadas por valor.
  2. Matrizes e objetos são passados ​​por referência ou por valor com base nas seguintes condições:

    • se você estiver configurando o valor de um objeto ou matriz, é Passagem por valor.

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

    • se você estiver alterando um valor de propriedade de um objeto ou matriz, será Passagem por referência.

      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


21
Esse não é o significado de "passar por referência". O termo realmente não tem nada a ver com objetos, mas com o relacionamento entre parâmetros em uma função chamada e variáveis ​​no ambiente de chamada.
Pointy 23/03

12
Tudo é sempre passado por valor. Com não primitivos, o valor que é passado é uma referência. A atribuição a uma referência altera essa referência - naturalmente, pois é um valor - e, portanto, não pode alterar o outro objeto que foi originalmente referenciado. A mutação do objeto apontado por uma referência atua exatamente como você esperaria, modificando o objeto apontado. Ambos os cenários são perfeitamente consistentes e nem alteram magicamente como o JavaScript funciona retornando no tempo e alterando como eles foram passados ​​para a função.
Matthew Leia

9
Essa resposta esclareceu a anterior, que, apesar de ter mais votos (287 no momento em que este artigo foi escrito e também o aceito), não era clara o suficiente sobre como transmiti-la por referência em JS. Em suma, o código nesta resposta funcionou, a resposta que obteve mais votos não.
Pap

25

Solução alternativa para passar variáveis ​​como por referência:

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

inc('a');

alert(a); // 2


EDITAR

Sim, na verdade você pode fazê-lo sem acesso global

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

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

    return init;
})();

inc();

@ Philip, é bom ter cuidado com os valores globais / da janela, mas em algum momento tudo o que estamos fazendo no navegador é filho ou descendente do objeto da janela. No nodejs, tudo é descendente da GLOBAL. Nas linguagens de objeto compiladas, esse é um objeto pai implícito, se não explícito, porque, caso contrário, torna o gerenciamento de heap mais complicado (e para quê?).
dkloke

1
@dkloke: Sim, eventualmente, o objeto window precisa ser tocado - como o jQuery usa window. $ / window.jQuery e outros métodos estão sob esse. Eu estava falando sobre a poluição do espaço para nome global, onde você adiciona muitas variáveis ​​a ele, em vez de colocá-las sob um espaço para nome unificador.
31515 Phil

2
Não consigo encontrar boas palavras para expressar o quão ruim essa abordagem é, (
SOReader

Adoro a última solução (versão editada), mesmo que ela não passe variável por referência. Essa é uma vantagem, porque você pode acessar as variáveis ​​diretamente.
John Boe

11

Objeto simples

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

Declaração Variável

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

Ou:

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

Código de teste

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

Resultado do console de teste

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

Ainda outra abordagem para passar quaisquer variáveis ​​(locais, primitivas) por referência é envolver a variável com fechamento "on the fly" por eval. Isso também funciona com "use strict". (Nota: esteja ciente de que evalnão é amigável para otimizadores de JS, também a falta de aspas em torno do nome da variável pode causar resultados imprevisíveis)

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

})()

Amostra ao vivo https://jsfiddle.net/t3k4403w/


Isto é incrível
hard_working_ant

3

Na verdade, há uma solução bastante:

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"]

2

Eu tenho brincado com a sintaxe para fazer esse tipo de coisa, mas requer alguns ajudantes que são um pouco incomuns. Começa com o não uso de 'var', mas com um simples auxiliar 'DECLARE' que cria uma variável local e define um escopo para ela por meio de um retorno de chamada anônimo. Controlando como as variáveis ​​são declaradas, podemos optar por envolvê-las em objetos para que elas sempre possam ser transmitidas por referência, essencialmente. Isso é semelhante a uma das respostas de Eduardo Cuomo acima, mas a solução abaixo não requer o uso de strings como identificadores de variáveis. Aqui está um código mínimo para mostrar o conceito.

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

na verdade é muito fácil,

o problema é entender que, depois de passar argumentos clássicos, você está no escopo de outra zona somente leitura.

soluções é passar os argumentos usando o design orientado a objetos do JavaScript,

é o mesmo que colocar os argumentos em uma variável global / com escopo, mas melhor ...

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

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

você também pode prometer coisas para aproveitar a cadeia conhecida: aqui está a coisa toda, com estrutura semelhante a promessas

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


this.argsó funciona com actioninstância. Isso não funciona.
Eduardo Cuomo

2

Pessoalmente, não gosto da funcionalidade "passar por referência" oferecida por várias linguagens de programação. Talvez seja porque estou apenas descobrindo os conceitos de programação funcional, mas sempre fico arrepiado quando vejo funções que causam efeitos colaterais (como manipular parâmetros passados ​​por referência). Pessoalmente, abraço fortemente o princípio da "responsabilidade única".

IMHO, uma função deve retornar apenas um resultado / valor usando a palavra-chave return. Em vez de modificar um parâmetro / argumento, eu retornaria o valor do parâmetro / argumento modificado e deixaria as reatribuições desejadas para o código de chamada.

Mas, às vezes (espero que muito raramente), é necessário retornar dois ou mais valores de resultado da mesma função. Nesse caso, eu optaria por incluir todos esses valores resultantes em uma única estrutura ou objeto. Novamente, o processamento de quaisquer reatribuições deve estar de acordo com o código de chamada.

Exemplo:

Suponha que a passagem de parâmetros seja suportada usando uma palavra-chave especial como 'ref' na lista de argumentos. Meu código pode ser algo como isto:

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

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

Em vez disso, eu preferiria fazer algo assim:

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

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

Quando eu precisaria escrever uma função que retorne vários valores, também não usaria parâmetros passados ​​por referência. Então, eu evitaria códigos 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

Em vez disso, eu preferiria retornar os dois novos valores dentro de um objeto, assim:

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

Esses exemplos de código são bastante simplificados, mas demonstram aproximadamente como eu pessoalmente lidaria com essas coisas. Isso me ajuda a manter várias responsabilidades no lugar correto.

Feliz codificação. :)


Atribuir e reatribuir coisas (sem verificação de tipo também) é o que me dá arrepios. Quanto à responsabilidade, espero que doSomething () faça isso, não faça isso e faça um objeto mais e atribua minhas variáveis ​​às propriedades. Diga que eu tenho uma matriz que precisa ser pesquisada. Gostaria que os itens correspondentes fossem colocados em uma segunda matriz e quero saber quantos foram encontrados. Uma solução padrão seria chamar uma função como esta: 'var foundArray; if ((findStuff (inArray, & foundArray))> 1) {// processo foundArray} '. Em nenhum lugar nesse cenário é desejável ou necessário um objeto novo e desconhecido.
Elise van Looij 13/08/19

@ElisevanLooij No seu exemplo, eu preferiria findStuffsimplesmente retornar a matriz resultante. Você também tem que declarar uma foundArrayvariável, por isso gostaria de atribuir diretamente a matriz resultante para ele: var foundArray = findStuff(inArray); if (foundArray.length > 0) { /* process foundArray */ }. Isso 1) tornaria o código de chamada mais legível / compreensível e 2) simplificaria consideravelmente a funcionalidade interna (e, portanto, também a testabilidade) do findStuffmétodo, tornando-o muito mais flexível em diferentes (re) casos de uso / cenários.
Bart Hofland

@ElisevanLooij No entanto, eu concordo que as reatribuições (como na minha resposta) são realmente um "cheiro de código" e eu realmente gostaria de evitá-las o máximo possível. Pensarei em editar (ou mesmo reformular) minha resposta de modo a refletir melhor minha opinião real sobre o assunto. Obrigado pela sua reação. :)
Bart Hofland

1

Javascript pode modificar itens de matriz dentro de uma função (é passado como uma referência ao 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);

Aqui está outro exemplo:

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

Como o JS não é do tipo forte, ele permite que você resolva problemas de muitas maneiras diferentes, como parece neste passo.

No entanto, para o ponto de vista da sustentabilidade, eu teria que concordar com Bart Hofland. A função deve obter args para fazer algo e retornar o resultado. Tornando-os facilmente reutilizáveis.

Se você achar que as variáveis ​​precisam ser passadas por referência, poderá ser melhor atendido Criando-as em objetos IMHO.


1

Deixando de lado a discussão de passagem por referência, aqueles que ainda procuram uma solução para a pergunta declarada podem usar:

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

0

Sei exatamente o que você quer dizer. A mesma coisa em Swift não será problema. Bottom line é usar letnão var.

O fato de as primitivas serem passadas por valor, mas o fato de que o valor var ino ponto da iteração não é copiado para a função anônima é bastante surpreendente, para dizer o mínimo.

for (let i = 0; i < boxArray.length; i++) {
  boxArray[i].onclick = function() { console.log(i) }; // correctly prints the index
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.