Przekaż zmienne przez odniesienie w JavaScript

285

Jak przekazać zmienne przez referencję w JavaScript? Mam 3 zmienne, dla których chcę wykonać kilka operacji, więc chcę umieścić je w pętli for i wykonać operacje na każdej z nich.

pseudo kod:

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

Jak najlepiej to zrobić?

BFTrick
źródło
31
Mówisz o „przekazywaniu przez odniesienie”, ale w twoim przykładzie nie ma wywołań funkcji, więc w twoim przykładzie nie ma żadnego przekazywania. Wyjaśnij, co próbujesz zrobić.
jfriend00
1
Przepraszam za zamieszanie. Nie musiałem specjalnie pisać funkcji, więc słowo „pass by reference” było złym wyborem słów. Chcę tylko móc wykonywać niektóre operacje na zmiennych bez pisania makePretty(var1); makePretty(var2); makePretty(var3); ...
BFTrick,
na podstawie twojego komentarza: arr = [var1, var2, var3]; for (var i = 0, len = arr.length; i < len; i++) { arr[i] = makePretty(arr[i]); }- po prostu musisz zapisać wartość zwróconą z makePrettypowrotem w gnieździe w tablicy.
dylnmc

Odpowiedzi:

415

W JavaScript nie ma opcji „pass by reference”. Możesz przekazać obiekt (co oznacza, że ​​możesz przekazać wartość przez odwołanie do obiektu), a następnie funkcja może zmodyfikować zawartość obiektu:

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

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

alterObject(myObj);

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

Możesz iterować właściwości tablicy z indeksem numerycznym i modyfikować każdą komórkę tablicy, jeśli chcesz.

var arr = [1, 2, 3];

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

Należy zauważyć, że „podanie przez odniesienie” jest bardzo specyficznym terminem. Nie oznacza to po prostu, że można przekazać odwołanie do modyfikowalnego obiektu. Zamiast tego oznacza to, że możliwe jest przekazanie prostej zmiennej w taki sposób, aby umożliwić funkcji zmodyfikowanie tej wartości w kontekście wywołania . Więc:

 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"

W języku takim jak C ++ można to zrobić, ponieważ język ten działa (sortowanie) przechodzący przez odniesienie.

edytuj - to ostatnio (marzec 2015) ponownie wysadziło Reddita na blogu podobnym do mojego wspomnianego poniżej, choć w tym przypadku o Javie. Przyszło mi do głowy, czytając w komentarzach Reddita tam iz powrotem, że duża część zamieszania wynika z niefortunnej kolizji obejmującej słowo „odniesienie”. Terminologia „pass by reference” i „pass by value” poprzedza koncepcję posiadania „obiektów” do pracy w językach programowania. Tak naprawdę wcale nie chodzi o przedmioty; chodzi o parametry funkcji, a konkretnie o to, jak parametry funkcji są „podłączone” (lub nie) do środowiska wywołującego. W szczególności,i wyglądałby dokładnie tak, jak w JavaScript. Jednak można również .móc modyfikować odwołanie do obiektu w środowisku wywołującym, a to jest kluczowa rzecz, której nie można zrobić w JavaScript. Język pass-by-referencyjny nie przekazywałby samego odwołania, ale odniesienie do odwołania

edycja - tutaj jest post na blogu na ten temat. (Zwróć uwagę na komentarz do tego postu, który wyjaśnia, że ​​C ++ tak naprawdę nie ma przekazu referencyjnego. To prawda. C ++ ma jednak możliwość tworzenia odwołań do zmiennych zwykłych, albo jawnie w punkcie funkcji wywołanie w celu utworzenia wskaźnika lub pośrednio podczas wywoływania funkcji, których podpis typu argumentu wymaga, aby to zrobić. Są to kluczowe rzeczy, których JavaScript nie obsługuje).

Pointy
źródło
8
Możesz przekazać odwołanie do obiektu lub tablicy, co pozwala zmienić oryginalny obiekt lub tablicę, co moim zdaniem jest tym, o co OP właściwie pyta.
jfriend00
2
Cóż, OP użył terminologii, ale rzeczywisty kod wydaje się w ogóle nie obejmować „przekazywania” :-) Naprawdę nie jestem pewien, co on próbuje zrobić.
Pointy,
5
Przekazując odwołanie przez wartość nie jest taka sama jak przechodzącej przez odniesienie , choć może się wydawać, więc w niektórych sytuacjach, takich jak ten.
Travis Webb,
5
Twój blogger jest źle o C ++, który dokłada przekazywany przez referencję, a jego stanowisko nie ma sensu. Nie ma znaczenia, że ​​nie można zmienić wartości referencji po inicjalizacji; nie ma to nic wspólnego z „przekazywaniem przez referencję”. Jego stwierdzenie, że semantię „pass by reference” można prześledzić przez C # ”powinno było zadzwonić dzwonkiem alarmowym.
EML
7
@Pointy To okropna odpowiedź. Gdybym potrzebował pomocy, ostatnią rzeczą, jakiej bym chciał, był ktoś, kto uczy mnie semantycznego. „Przekazywanie przez odniesienie” oznacza po prostu „funkcja może do pewnego stopnia zmienić wartość zmiennej, która została przekazana jako argument”. (w kontekście pytania) To naprawdę nie jest tak skomplikowane, jak się wydaje.
bez koronowania
108
  1. zmienne typu pierwotnego, takie jak łańcuchy i liczby, są zawsze przekazywane przez wartość.
  2. Tablice i obiekty są przekazywane przez odniesienie lub wartość na podstawie następujących warunków:

    • jeśli ustawiasz wartość obiektu lub tablicy, jest to Pass by Value.

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

    • jeśli zmieniasz wartość właściwości obiektu lub tablicy, to jest to Pass by Reference.

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

Kod

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
źródło
21
To nie znaczy „przekazać przez odniesienie”. Termin tak naprawdę nie ma nic wspólnego z obiektami, ale z relacją między parametrami w wywoływanej funkcji i zmiennymi w środowisku wywołującym.
Pointy
12
Wszystko zawsze przechodzi przez wartość. W przypadku obiektów innych niż pierwotne przekazywana wartość jest odwołaniem. Przypisanie do odwołania zmienia to odniesienie - naturalnie, ponieważ jest to wartość - i dlatego nie może zmienić innego obiektu, do którego pierwotnie się odwołano. Mutowanie obiektu wskazywanego przez odwołanie działa dokładnie tak, jak można się tego spodziewać, poprzez mutowanie obiektu wskazanego przez. Oba scenariusze są idealnie spójne i żadne z nich nie zmienia magicznie sposobu działania JavaScript, cofając się w czasie i zmieniając sposób, w jaki zostały przekazane do funkcji.
Matthew
9
Ta odpowiedź wyjaśniła poprzednią, która, mimo że ma więcej głosów (287 w tym momencie pisania i jest również zaakceptowana), nie była wystarczająco jasna, jak faktycznie przekazać ją przez odniesienie w JS. Krótko mówiąc, kod w tej odpowiedzi zadziałał, a odpowiedź, która zgromadziła więcej głosów, nie zadziałała.
Pap.
25

Obejście problemu polegające na przekazywaniu zmiennej jak przez odwołanie:

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

inc('a');

alert(a); // 2


EDYTOWAĆ

tak, właściwie możesz to zrobić bez dostępu do globalnego

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

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

    return init;
})();

inc();
użytkownik2410595
źródło
@Pilno dobrze jest uważać na wartości globalne / okna, ale w pewnym momencie wszystko, co robimy w przeglądarce, jest dzieckiem lub potomkiem obiektu okna. W nodejs wszystko jest potomkiem GLOBAL. W skompilowanych językach obiektowych jest to domyślny, jeśli nie jawny obiekt nadrzędny, ponieważ w przeciwnym razie zarządzanie stertami jest bardziej skomplikowane (i po co?).
dkloke
1
@dkloke: Tak, ostatecznie obiekt okna musi zostać dotknięty - tak jak jQuery używa okna. $ / window.jQuery i inne metody są pod tym. Mówiłem o zanieczyszczeniu globalnej przestrzeni nazw, w której dodajesz do niej wiele zmiennych, zamiast mieć je pod jednolitą przestrzenią nazw.
Phil
2
Nie mogę znaleźć dobrych słów, aby wyrazić, jak złe jest to podejście; (
SOReader
Uwielbiam ostatnie rozwiązanie (wersja edytowana), nawet jeśli nie przekazuje zmiennej przez odniesienie. Jest to zaleta, ponieważ można uzyskać bezpośredni dostęp do zmiennych.
John Boe,
11

Prosty przedmiot

var ref = { value: 1 };

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

Foo(ref);
Foo(ref);

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

Obiekt niestandardowy

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

Deklaracja zmienna

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

Lub:

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

Kod testowy

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

Wynik konsoli testowej

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
źródło
5

Jeszcze innym podejściem do przekazywania dowolnych (lokalnych, prymitywnych) zmiennych przez odniesienie jest zawijanie zmiennej zamykaniem „w locie” przez eval. Działa to również z „use strict”. (Uwaga: należy pamiętać, że evalnie jest przyjazny dla optymalizatorów JS, również brak cudzysłowów wokół nazwy zmiennej może powodować nieprzewidywalne wyniki)

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

})()

Próbka na żywo https://jsfiddle.net/t3k4403w/

Pavlo Mur
źródło
To jest niesamowite
hard_working_ant
3

W rzeczywistości jest całkiem niezłe rozwiązanie:

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
źródło
3

Osobiście nie podoba mi się funkcja „pass by reference” oferowana przez różne języki programowania. Być może dlatego, że właśnie odkrywam koncepcje programowania funkcjonalnego, ale zawsze dostaję gęsiej skórki, gdy widzę funkcje wywołujące skutki uboczne (takie jak manipulowanie parametrami przekazywanymi przez referencję). Osobiście zdecydowanie popieram zasadę „pojedynczej odpowiedzialności”.

IMHO, funkcja powinna zwrócić tylko jeden wynik / wartość za pomocą słowa kluczowego return. Zamiast modyfikować parametr / argument, po prostu zwrócę zmodyfikowaną wartość parametru / argumentu i pozostawiam wszelkie pożądane zmiany przypisania do kodu wywołującego.

Ale czasami (mam nadzieję, że bardzo rzadko) konieczne jest zwrócenie dwóch lub więcej wartości wyników z tej samej funkcji. W takim przypadku wolałbym uwzględnić wszystkie uzyskane wartości w jednej strukturze lub obiekcie. Ponownie przetwarzanie ponownych przypisań powinno zależeć od kodu wywołującego.

Przykład:

Załóżmy, że przekazywanie parametrów byłoby obsługiwane przy użyciu specjalnego słowa kluczowego, takiego jak „ref” na liście argumentów. Mój kod może wyglądać mniej więcej tak:

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

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

Zamiast tego wolałbym zrobić coś takiego:

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

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

Gdybym musiał napisać funkcję, która zwraca wiele wartości, nie używałbym również parametrów przekazywanych przez referencję. Chciałbym więc uniknąć takiego kodu:

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

Zamiast tego wolałbym zwrócić obie nowe wartości wewnątrz obiektu, takie jak to:

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

Te przykłady kodu są dość uproszczone, ale z grubsza pokazuje, jak osobiście poradziłbym sobie z takimi rzeczami. Pomaga mi utrzymać różne obowiązki we właściwym miejscu.

Szczęśliwego kodowania. :)

Bart Hofland
źródło
Przypisywanie i ponowne przypisywanie rzeczy (bez sprawdzania typu) daje mi gęsią skórkę. Jeśli chodzi o odpowiedzialność, oczekuję, że doSomething () to zrobi, a nie zrobi tego plus stworzy obiekt plus i przypisze moje zmienne do właściwości. Powiedzmy, że mam tablicę, którą należy przeszukać. Chciałbym, aby pasujące elementy znalazły się w drugiej tablicy i chcę wiedzieć, ile zostało znalezionych. Standardowym rozwiązaniem byłoby wywołanie takiej funkcji: 'var foundArray; if ((findStuff (inArray i & foundArray))> 1) {// process foundArray} '. Nigdzie w tym scenariuszu nowy, nieznany obiekt nie jest pożądany ani potrzebny.
Elise van Looij
@ElisevanLooij W twoim przypadku wolałbym findStuffpo prostu zwrócić wynikową tablicę. Ty też musisz zadeklarować foundArrayzmienną, więc chciałbym się bezpośrednio przypisać do niego wynikającej tablicę: var foundArray = findStuff(inArray); if (foundArray.length > 0) { /* process foundArray */ }. To 1) sprawi, że kod wywołujący będzie bardziej czytelny / zrozumiały, oraz 2) znacznie uprości wewnętrzną funkcjonalność (a tym samym także testowalność) findStuffmetody, czyniąc ją znacznie bardziej elastyczną w różnych (ponownie) przypadkach użycia / scenariuszach.
Bart Hofland,
@ElisevanLooij Zgadzam się jednak, że zmiany przypisań (jak w mojej odpowiedzi) są rzeczywiście „zapachem kodu” i tak naprawdę chciałbym ich uniknąć. Zastanowię się nad edytowaniem (a nawet przeformułowaniem) mojej odpowiedzi w taki sposób, aby lepiej odzwierciedlała moją rzeczywistą opinię na ten temat. Dziękuję za twoją reakcję. :)
Bart Hofland,
2

Bawiłem się składnią, aby robić takie rzeczy, ale wymaga to trochę pomocników, które są trochę niezwykłe. Zaczyna się w ogóle od nieużywania „var”, ale prostego pomocnika „DECLARE”, który tworzy zmienną lokalną i definiuje jej zakres poprzez anonimowe wywołanie zwrotne. Kontrolując sposób deklarowania zmiennych, możemy zawijać je w obiekty, aby zasadniczo zawsze mogły być przekazywane przez referencję. Jest to podobne do powyższej odpowiedzi Eduardo Cuomo, ale poniższe rozwiązanie nie wymaga użycia ciągów jako identyfikatorów zmiennych. Oto minimalny kod pokazujący tę koncepcję.

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
źródło
2

właściwie to jest naprawdę łatwe,

problemem jest zrozumienie, że po przekazaniu klasycznych argumentów można znaleźć się w innej strefie tylko do odczytu.

rozwiązaniem jest przekazywanie argumentów za pomocą obiektowego projektu JavaScript,

jest to to samo, co umieszczenie argumentów w zmiennej globalnej / o zasięgu, ale lepiej ...

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

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

możesz także obiecać coś, co pozwoli cieszyć się znanym łańcuchem: oto cała rzecz o strukturze przypominającej obietnicę

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

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


źródło
this.argdziała tylko z actioninstancją. To nie działa.
Eduardo Cuomo,
1

JavaScript może modyfikować elementy tablicy wewnątrz funkcji (jest przekazywana jako odwołanie do obiektu / tablicy).

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

Oto inny przykład:

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
źródło
1

JS nie jest silnym typem, pozwala rozwiązywać problemy na wiele różnych sposobów, jak się wydaje w tym bieżniku.

Jednak z punktu widzenia łatwości konserwacji musiałbym się zgodzić z Bartem Hoflandem. Funkcja powinna uzyskać argumenty do zrobienia czegoś i zwrócić wynik. Dzięki temu łatwo można je ponownie wykorzystać.

Jeśli uważasz, że zmienne muszą być przekazywane przez referencję, możesz lepiej skorzystać z ich wbudowania w obiekty IMHO.

Michel S.
źródło
1

Odkładając na bok dyskusję na zasadzie odniesienia, ci, którzy wciąż szukają rozwiązania zadanego pytania, mogą użyć:

const myArray = new Array(var1, var2, var3);
myArray.forEach(var => var = makePretty(var));
Shira Joseph
źródło
0

Wiem dokładnie, co masz na myśli. To samo w Swift nie będzie problemu. Najważniejsze jest letnie używaćvar .

Fakt, że prymitywy są przekazywane przez wartość, ale fakt, że wartość var iw punkcie iteracji nie jest kopiowana do funkcji anonimowej, jest co najmniej dość zaskakujący.

for (let i = 0; i < boxArray.length; i++) {
  boxArray[i].onclick = function() { console.log(i) }; // correctly prints the index
}
funk7
źródło