Pass Variables by Reference dalam Javascript

285

Bagaimana cara saya lulus variabel dengan referensi dalam JavaScript? Saya memiliki 3 variabel yang ingin saya lakukan beberapa operasi, jadi saya ingin meletakkannya dalam for loop dan melakukan operasi untuk masing-masing.

kode semu:

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

Apa cara terbaik untuk melakukan ini?

BFTrick
sumber
31
Anda sedang berbicara tentang 'lewat referensi', tetapi Anda tidak memiliki panggilan fungsi dalam contoh Anda sehingga tidak ada sama sekali lewat dalam contoh Anda. Tolong jelaskan apa yang Anda coba lakukan.
jfriend00
1
Maaf bila membingungkan. Saya tidak secara khusus perlu menulis fungsi sehingga 'lewat referensi' adalah pilihan kata yang buruk. Saya hanya ingin dapat melakukan beberapa operasi untuk variabel tanpa menulis makePretty(var1); makePretty(var2); makePretty(var3); ...
BFTrick
berdasarkan komentar Anda: arr = [var1, var2, var3]; for (var i = 0, len = arr.length; i < len; i++) { arr[i] = makePretty(arr[i]); }- Anda hanya perlu menyimpan nilai yang dikembalikan dengan makePrettykembali ke slot dalam array.
dylnmc

Jawaban:

415

Tidak ada "pass by reference" yang tersedia di JavaScript. Anda dapat melewatkan sebuah objek (artinya, Anda dapat memberikan referensi kepada objek) dengan pass-by-value dan kemudian memiliki fungsi memodifikasi konten objek:

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

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

alterObject(myObj);

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

Anda bisa mengulangi properti array dengan indeks numerik dan memodifikasi setiap sel array, jika Anda mau.

var arr = [1, 2, 3];

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

Penting untuk dicatat bahwa "pass-by-reference" adalah istilah yang sangat spesifik. Ini tidak berarti hanya bahwa dimungkinkan untuk meneruskan referensi ke objek yang dapat dimodifikasi. Sebaliknya, itu berarti bahwa dimungkinkan untuk melewatkan variabel sederhana sedemikian rupa sehingga memungkinkan fungsi untuk mengubah nilai itu dalam konteks panggilan . Begitu:

 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"

Dalam bahasa seperti C ++, dimungkinkan untuk melakukan itu karena bahasa itu (sort-of) memiliki pass-by-reference.

sunting - ini baru-baru ini (Maret 2015) meledak di Reddit lagi atas posting blog yang mirip dengan saya yang disebutkan di bawah, meskipun dalam kasus ini tentang Java. Terpikir oleh saya ketika membaca bolak-balik dalam komentar Reddit bahwa sebagian besar kebingungan berasal dari tabrakan malang yang melibatkan kata "referensi". Terminologi "lulus dengan referensi" dan "lulus dengan nilai" mendahului konsep memiliki "objek" untuk bekerja dengan dalam bahasa pemrograman. Ini benar-benar bukan tentang benda sama sekali; ini tentang parameter fungsi, dan secara khusus bagaimana parameter fungsi "terhubung" (atau tidak) ke lingkungan panggilan. Secara khusus, perhatikan bahwa dalam bahasa referensi-by-referensi yang benar - bahasa yang benar, dan itu akan terlihat persis seperti di JavaScript. Namun, orang jugadapat memodifikasi referensi objek di lingkungan panggilan, dan itulah hal utama yang tidak dapat Anda lakukan dalam JavaScript. Bahasa pass-by-reference tidak akan melewati referensi itu sendiri, tetapi referensi ke referensi .

sunting - di sini ada posting blog tentang topik tersebut. (Catat komentar pada posting itu yang menjelaskan bahwa C ++ tidak benar-benar memiliki pass-by-reference. Itu benar. Apa yang dimiliki C ++, adalah kemampuan untuk membuat referensi ke variabel polos, baik secara eksplisit pada titik fungsi doa untuk membuat pointer, atau secara implisit saat memanggil fungsi yang jenis argumennya meminta hal itu dilakukan. Itulah hal-hal kunci yang tidak didukung JavaScript.)

Runcing
sumber
8
Anda dapat memberikan referensi ke objek atau array yang memungkinkan Anda untuk mengubah objek atau array asli yang saya percaya adalah apa yang sebenarnya ditanyakan oleh OP.
jfriend00
2
Yah, OP menggunakan terminologi tetapi kode yang sebenarnya tampaknya tidak melibatkan "passing" sama sekali :-) Saya tidak begitu yakin apa yang dia coba lakukan.
Runcing
5
Melewati referensi berdasarkan nilai tidak sama dengan melewati referensi , meskipun mungkin muncul demikian dalam beberapa skenario seperti ini.
Travis Webb
5
Blogger Anda salah tentang C ++, yang tidak lulus referensi, dan posnya tidak masuk akal. Tidak relevan bahwa Anda tidak dapat mengubah nilai referensi setelah inisialisasi; itu tidak ada hubungannya dengan 'lewat referensi'. Pernyataannya bahwa semantik "pass by reference" dapat ditelusuri kembali melalui C # 'seharusnya membunyikan bel alarm.
EML
7
@Pointy Ini adalah jawaban yang mengerikan. Jika saya butuh bantuan, hal terakhir yang saya inginkan adalah seseorang yang mendidik saya di semantik. "Pass-by-reference" hanya berarti, "fungsinya, sampai taraf tertentu, dapat mengubah nilai variabel yang diteruskan sebagai argumen." (dalam konteks pertanyaan) Ini benar-benar tidak rumit seperti yang Anda lakukan.
crownlessking
108
  1. variabel tipe primitif seperti string dan angka selalu dilewati oleh nilai.
  2. Array dan Objek diteruskan dengan referensi atau berdasarkan nilai berdasarkan kondisi ini:

    • jika Anda menetapkan nilai suatu objek atau array, nilai Pass by Value.

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

    • jika Anda mengubah nilai properti dari suatu objek atau array maka Pass by Reference.

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

Kode

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
sumber
21
Itu bukan arti dari "melewati referensi". Istilah ini benar-benar tidak ada hubungannya dengan objek, tetapi dengan hubungan antara parameter dalam fungsi yang disebut dan variabel dalam lingkungan pemanggilan.
Pointy
12
Semuanya selalu dilewati dengan nilai. Dengan non-primitif, nilai yang diberikan adalah referensi. Menetapkan referensi perubahan referensi itu - secara alami, karena itu adalah nilai - dan dengan demikian tidak dapat mengubah objek lain yang awalnya dirujuk. Memutasi objek yang ditunjuk oleh referensi akan bertindak persis seperti yang Anda harapkan, dengan memutasi objek yang ditunjuk ke. Kedua skenario tersebut sangat konsisten, dan tidak satu pun yang secara ajaib mengubah cara kerja JavaScript dengan kembali ke masa lalu dan mengubah cara mereka diteruskan ke dalam fungsi.
Matius Baca
9
Jawaban ini mengklarifikasi jawaban sebelumnya, yang, meskipun memiliki lebih banyak suara (287 pada saat penulisan ini dan juga yang diterima) tidak cukup jelas tentang bagaimana cara meloloskannya dengan referensi di JS. Singkatnya, kode dalam jawaban ini berfungsi, jawaban yang mengumpulkan lebih banyak suara tidak.
Pap
25

Solusi untuk lulus variabel seperti dengan referensi:

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

inc('a');

alert(a); // 2


EDIT

yup, sebenarnya Anda bisa melakukannya tanpa akses global

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

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

    return init;
})();

inc();
pengguna2410595
sumber
@Phil Sebaiknya berhati-hati dengan nilai global / jendela, tetapi pada titik tertentu semua yang kita lakukan di browser adalah anak atau keturunan objek jendela. Dalam nodejs, semuanya adalah keturunan GLOBAL. Dalam bahasa objek yang dikompilasi, yang merupakan objek induk implisit jika tidak eksplisit, karena melakukannya sebaliknya membuat manajemen tumpukan lebih rumit (dan untuk apa?).
dkloke
1
@ Gdkoke: Ya, pada akhirnya objek jendela perlu disentuh - seperti jQuery menggunakan jendela. $ / window.jQuery dan metode lainnya ada di bawah ini. Saya berbicara tentang polusi namespace global di mana Anda menambahkan banyak variabel daripada memilikinya di bawah namespace pemersatu.
Phil
2
Saya tidak dapat menemukan kata-kata yang bagus untuk mengungkapkan betapa buruknya pendekatan itu; (
SOReader
Saya suka solusi terakhir (versi yang diedit) meskipun tidak lulus variabel dengan referensi. Itu keuntungan karena Anda dapat mengakses variabel secara langsung.
John Boe
11

Obyek Sederhana

var ref = { value: 1 };

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

Foo(ref);
Foo(ref);

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

Objek Khusus

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

Deklarasi Variabel

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

Atau:

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

Kode Uji

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

Hasil Konsol Tes

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
sumber
5

Namun pendekatan lain untuk melewatkan variabel (lokal, primitif) dengan referensi adalah dengan membungkus variabel dengan penutupan "on the fly" oleh eval. Ini juga berfungsi dengan "gunakan ketat". (Catatan: perlu diketahui bahwa evaltidak ramah untuk pengoptimal JS, juga kutipan yang hilang di sekitar nama variabel dapat menyebabkan hasil yang tidak terduga)

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

})()

Sampel langsung https://jsfiddle.net/t3k4403w/

Pavlo Mur
sumber
Ini luar biasa
hard_working_ant
3

Sebenarnya ada solusi yang cukup:

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
sumber
2

Saya telah bermain-main dengan sintaks untuk melakukan hal semacam ini, tetapi membutuhkan beberapa pembantu yang sedikit tidak biasa. Itu dimulai dengan tidak menggunakan 'var' sama sekali, tetapi pembantu 'DECLARE' sederhana yang membuat variabel lokal dan mendefinisikan ruang lingkup untuk itu melalui panggilan balik anonim. Dengan mengendalikan bagaimana variabel dideklarasikan, kita dapat memilih untuk membungkusnya menjadi objek sehingga mereka selalu dapat dilewatkan dengan referensi, pada dasarnya. Ini mirip dengan salah satu jawaban Eduardo Cuomo di atas, tetapi solusi di bawah ini tidak memerlukan penggunaan string sebagai pengidentifikasi variabel. Berikut adalah beberapa kode minimal untuk menunjukkan konsep.

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
sumber
2

sebenarnya sangat mudah,

masalahnya adalah memahami bahwa setelah melewati argumen klasik, Anda dimasukkan ke argumen lain, zona read-only.

solusi adalah untuk melewati argumen menggunakan desain berorientasi objek JavaScript,

itu sama dengan menempatkan args dalam variabel global / cakupan, tetapi lebih baik ...

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

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

Anda juga dapat menjanjikan hal-hal untuk menikmati rantai terkenal: di sini semuanya, dengan struktur seperti janji

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

atau lebih baik lagi .. action.setArg(["empty-array"],"some string",0x100,"last argument").call()


sumber
this.arghanya bekerja dengan actioninstance. Ini tidak bekerja.
Eduardo Cuomo
2

Saya pribadi tidak menyukai fungsi "pass by reference" yang ditawarkan oleh berbagai bahasa pemrograman. Mungkin itu karena saya hanya menemukan konsep pemrograman fungsional, tetapi saya selalu merinding ketika saya melihat fungsi yang menyebabkan efek samping (seperti memanipulasi parameter yang dilewatkan oleh referensi). Saya pribadi sangat menganut prinsip "tanggung jawab tunggal".

IMHO, suatu fungsi harus mengembalikan hanya satu hasil / nilai menggunakan kata kunci kembali. Alih-alih memodifikasi parameter / argumen, saya hanya akan mengembalikan nilai parameter / argumen yang dimodifikasi dan membiarkan penugasan kembali yang diinginkan ke kode panggilan.

Tapi kadang-kadang (mudah-mudahan sangat jarang), perlu mengembalikan dua atau lebih nilai hasil dari fungsi yang sama. Dalam hal ini, saya akan memilih untuk memasukkan semua nilai yang dihasilkan dalam struktur atau objek tunggal. Sekali lagi, memproses setiap penugasan kembali harus sesuai dengan kode panggilan.

Contoh:

Misalkan parameter lewat akan didukung dengan menggunakan kata kunci khusus seperti 'ref' dalam daftar argumen. Kode saya mungkin terlihat seperti ini:

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

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

Sebaliknya, saya sebenarnya lebih suka melakukan sesuatu seperti ini:

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

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

Ketika saya perlu menulis fungsi yang mengembalikan beberapa nilai, saya juga tidak akan menggunakan parameter yang dilewati oleh referensi. Jadi saya akan menghindari kode seperti ini:

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

Sebaliknya, saya sebenarnya lebih suka mengembalikan kedua nilai baru di dalam objek, seperti ini:

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

Contoh kode ini cukup disederhanakan, tetapi secara kasar menunjukkan bagaimana saya secara pribadi akan menangani hal-hal seperti itu. Ini membantu saya untuk menjaga berbagai tanggung jawab di tempat yang benar.

Selamat coding. :)

Bart Hofland
sumber
Menugaskan dan menugaskan kembali barang-barang (tanpa pengecekan tipe apa pun) adalah yang membuat saya merinding. Adapun tanggung jawab, saya berharap doSomething (), untuk melakukan hal itu, tidak melakukan hal itu ditambah membuat objek plus dan menetapkan variabel saya ke properti. Katakanlah saya memiliki array yang perlu dicari. Saya ingin item yang cocok ditempatkan dalam array kedua dan saya ingin tahu berapa banyak yang ditemukan. Solusi standar akan memanggil fungsi seperti ini: 'var foundArray; if ((findStuff (inArray, & foundArray))> 1) {// proses foundArray} '. Dalam skenario itu tidak ada objek baru yang tidak diketahui yang diinginkan atau dibutuhkan.
Elise van Looij
@ ElisevanLooij Dalam contoh kasus Anda, saya lebih suka findStuffmengembalikan array yang dihasilkan. Anda juga harus mendeklarasikan foundArrayvariabel, jadi saya akan langsung menetapkan array yang dihasilkan untuk itu: var foundArray = findStuff(inArray); if (foundArray.length > 0) { /* process foundArray */ }. Ini akan 1) membuat kode panggilan lebih mudah dibaca / dimengerti, dan 2) menyederhanakan fungsi internal (dan juga testabilitas) dari findStuffmetode tersebut, menjadikannya jauh lebih fleksibel dalam berbagai skenario penggunaan kembali kasus / skenario.
Bart Hofland
@ ElisevanLooij Namun, saya setuju bahwa penugasan kembali (seperti dalam jawaban saya) memang "bau kode" dan saya benar-benar ingin menghindari mereka sebanyak mungkin juga. Saya akan berpikir tentang mengedit (atau bahkan merumuskan kembali) jawaban saya sedemikian rupa sehingga akan lebih mencerminkan pendapat saya yang sebenarnya tentang masalah ini. Terima kasih atas reaksimu. :)
Bart Hofland
1

Javascript dapat mengubah item array di dalam suatu fungsi (diteruskan sebagai referensi ke objek / array).

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

Ini contoh lain:

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
sumber
1

JS bukan tipe yang kuat, ini memungkinkan Anda untuk menyelesaikan masalah dengan berbagai cara, seperti yang terlihat dalam tapak ini.

Namun, untuk sudut pandang rawatan, saya harus setuju dengan Bart Hofland. Fungsi harus mendapatkan argumen untuk melakukan sesuatu dan mengembalikan hasilnya. Membuatnya mudah digunakan kembali.

Jika Anda merasa bahwa variabel perlu diteruskan dengan referensi, Anda mungkin lebih baik dilayani. Membangunnya menjadi objek IMHO.

Michel S
sumber
1

Mengesampingkan diskusi pass-by-reference, mereka yang masih mencari solusi untuk pertanyaan yang disebutkan dapat menggunakan:

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

Saya tahu persis apa yang Anda maksud. Hal yang sama di Swift tidak akan menjadi masalah. Intinya adalah gunakan lettidakvar .

Fakta bahwa primitif disahkan oleh nilai tetapi fakta bahwa nilai var ipada titik iterasi tidak disalin ke fungsi anonim cukup mengejutkan untuk sedikitnya.

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