Pertanyaan Apakah JavaScript merupakan bahasa pass-by-reference atau pass-by-value?


Tipe primitif (Number, String, dll.) Dilewatkan oleh nilai, tetapi Object tidak diketahui, karena keduanya dapat dilewatkan oleh nilai (dalam hal ini kita menganggap bahwa variabel yang memegang objek sebenarnya adalah referensi ke objek ) dan lewat-oleh-referensi (ketika kita menganggap bahwa variabel ke objek memegang objek itu sendiri).

Meskipun itu tidak terlalu penting pada akhirnya, saya ingin tahu apa cara yang benar untuk menyajikan argumen yang melewati konvensi. Apakah ada kutipan dari spesifikasi JavaScript, yang mendefinisikan semantik apa yang seharusnya mengenai hal ini?


1137
2018-02-05 21:23


asal


Jawaban:


Ini menarik dalam Javascript. Pertimbangkan contoh ini:

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

Ini menghasilkan output:

10
changed
unchanged
  • Jika itu murni melewati nilai, lalu berubah obj1.item tidak akan berpengaruh pada obj1 di luar fungsi.
  • Jika itu murni lewat referensi, maka semuanya akan berubah. num akan menjadi 100, dan obj2.item akan membaca "changed".

Sebaliknya, situasinya adalah bahwa item yang dilewatkan masuk dilewatkan oleh nilai. Tetapi item yang dilewatkan oleh nilai adalah diri referensi. Secara teknis, ini disebut panggilan-dengan-berbagi.

Dalam istilah praktis, ini berarti bahwa jika Anda mengubah parameter itu sendiri (seperti dengan num dan obj2), itu tidak akan mempengaruhi item yang dimasukkan ke dalam parameter. Tetapi jika Anda mengubah INTERNAL dari parameter, yang akan menyebar kembali (seperti dengan obj1).


1359
2018-03-15 16:38



Itu selalu lewat nilai, tetapi untuk objek nilai dari variabel adalah referensi. Karena ini, ketika Anda melewati suatu objek dan mengubahnya anggota, perubahan itu tetap ada di luar fungsi. Ini membuatnya melihat seperti lewat referensi. Tetapi jika Anda benar-benar mengubah nilai variabel objek, Anda akan melihat bahwa perubahan itu tidak bertahan, membuktikan bahwa itu benar-benar melewati nilai.

Contoh:

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

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

var x = {member:"foo"};

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

alert("before changeMember: " + x.member);
changeMember(x);
alert("after changeMember: " + x.member); /* change persists */

Keluaran:

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

before changeMember: foo
in changeMember: bar
after changeMember: bar

376
2018-02-05 21:37



Variabel tidak "menahan" objek, itu memegang referensi. Anda dapat menetapkan referensi ke variabel lain, sekarang keduanya merujuk objek yang sama. Itu selalu melewati nilai (bahkan ketika nilai itu adalah referensi ...).

Tidak ada cara untuk mengubah nilai yang dipegang oleh variabel yang dilewatkan sebagai parameter, yang akan mungkin jika JS didukung lewat referensi.


132
2017-08-04 11:06



My 2 Cents ... Ini adalah cara saya memahaminya. (Jangan ragu untuk mengoreksi saya jika saya salah)

Saatnya membuang semua yang Anda ketahui tentang melewati nilai / referensi.

Karena dalam JavaScript, tidak masalah apakah itu dilewatkan oleh nilai atau dengan referensi atau apa pun. Yang penting adalah mutasi vs penugasan parameter yang diteruskan ke suatu fungsi.

Oke, biarkan saya melakukan yang terbaik untuk menjelaskan apa yang saya maksud. Katakanlah Anda memiliki beberapa objek.

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

Apa yang telah kami lakukan adalah "tugas" ... Kami telah menetapkan 2 objek kosong yang terpisah ke variabel "object1" dan "object2".

Sekarang, mari kita katakan bahwa kita suka object1 lebih baik ... Jadi, kita "menetapkan" variabel baru.

var favoriteObject = object1;

Selanjutnya, untuk alasan apa pun, kami memutuskan bahwa kami menyukai objek 2 lebih baik. Jadi, kita cukup melakukan sedikit penugasan ulang.

favoriteObject = object2;

Tidak ada yang terjadi pada object1 atau object2. Kami belum mengubah data sama sekali. Yang kami lakukan hanyalah menetapkan kembali objek favorit kami. Penting untuk mengetahui bahwa object2 dan favoriteObject keduanya ditugaskan ke objek yang sama. Kita dapat mengubah objek itu melalui salah satu dari variabel tersebut.

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

OK, sekarang mari kita lihat pada primitif seperti string misalnya

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

Sekali lagi, kami memilih favorit.

var favoriteString = string1;

Kedua variabel favoriteString dan string1 kami ditetapkan ke 'Hello world'. Sekarang, bagaimana jika kita ingin mengubah favoriteString ??? Apa yang akan terjadi???

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

Uh oh .... Apa yang terjadi. Kami tidak dapat mengubah string1 dengan mengubah favoriteString ... Mengapa ?? karena string tidak dapat diubah dan kami tidak memutasikannya. Yang kami lakukan adalah "RE ASSIGN" favoriteString ke string baru. Ini pada dasarnya memutusnya dari string1. Dalam contoh sebelumnya, ketika kami mengganti nama objek kami, kami tidak menetapkan apa pun. (Yah, sebenarnya ... kami lakukan, kami menugaskan properti nama ke string baru.) Sebaliknya, kami hanya bermutasi objek yang membuat koneksi antara 2 variabel dan objek yang mendasarinya.

Sekarang, ke fungsi dan melewatkan parameter .... Ketika Anda memanggil fungsi, dan meneruskan parameter, apa yang pada dasarnya Anda lakukan adalah "tugas" ke variabel baru, dan berfungsi sama seperti jika Anda hanya ditugaskan menggunakan tanda sama dengan (=).

Ambil contoh-contoh ini.

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'

Sekarang, hal yang sama, tetapi dengan fungsi

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'

OK, sekarang mari kita berikan beberapa contoh menggunakan objek sebagai gantinya ... pertama, tanpa fungsi.

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
otherObj = {
    firstName: 'Jack',
    lastName: 'Frost'
};

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

Sekarang, hal yang sama, tetapi dengan panggilan fungsi

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 no longer mutates 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

Oke, jika Anda membaca seluruh pos ini, mungkin Anda sekarang memiliki pemahaman yang lebih baik tentang bagaimana fungsi panggilan berfungsi di javascript. Tidak masalah apakah sesuatu dilewatkan oleh referensi atau nilai ... Yang penting adalah tugas vs mutasi.

Setiap kali Anda mengirimkan variabel ke fungsi, Anda "Menetapkan" untuk apa pun nama variabel parameter, sama seperti jika Anda menggunakan tanda sama dengan (=).

Selalu ingat bahwa tanda sama dengan (=) berarti tugas. Selalu ingat bahwa melewatkan parameter ke fungsi juga berarti tugas. Mereka sama dan 2 variabel terhubung dengan cara yang persis sama.

Satu-satunya saat memodifikasi variabel mempengaruhi variabel yang berbeda adalah ketika objek yang mendasarinya dimutasi.

Tidak ada gunanya membuat perbedaan antara objek dan primitif, karena ia bekerja dengan cara yang sama persis seolah-olah Anda tidak memiliki fungsi dan hanya menggunakan tanda sama untuk menetapkan ke variabel baru.

Satu-satunya cara adalah ketika nama variabel yang Anda masukkan ke fungsi sama dengan nama parameter fungsi. Ketika ini terjadi, Anda harus memperlakukan parameter di dalam fungsi seolah-olah itu adalah variabel baru pribadi untuk fungsi (karena itu)

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'

84
2017-09-26 04:06



Pertimbangkan yang berikut ini:

  1. Variabel-variabelnya petunjukke nilai-nilai dalam memori.
  2. Menugaskan kembali variabel hanya menunjukkan penunjuk pada nilai baru.
  3. Menugaskan kembali variabel tidak akan pernah mempengaruhi variabel lain yang menunjuk pada objek yang sama

Begitu, melupakan tentang "lewat referensi / nilai" jangan terpaku pada "lewat referensi / nilai" karena:

  1. Istilah ini hanya digunakan untuk mendeskripsikan tingkah laku suatu bahasa, belum tentu implementasi dasarnya yang sebenarnya. Sebagai hasil dari abstraksi ini, rincian penting yang penting untuk penjelasan yang layak hilang, yang pasti mengarah pada situasi saat ini di mana satu istilah tidak cukup menggambarkan perilaku yang sebenarnya dan info tambahan harus disediakan
  2. Konsep-konsep ini awalnya tidak didefinisikan dengan maksud menggambarkan javascript pada khususnya dan jadi saya tidak merasa terpaksa menggunakannya ketika mereka hanya menambah kebingungan.

Untuk menjawab pertanyaan Anda: petunjuk diteruskan.


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

Beberapa komentar akhir:

  • Sangat menggoda untuk berpikir seperti itu primitif diberlakukan oleh aturan khusus sementara objek tidak, tetapi primitif hanyalah akhir dari rantai penunjuk.
  • Sebagai contoh terakhir, pertimbangkan mengapa upaya umum untuk menghapus array tidak berfungsi seperti yang diharapkan.


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

57
2018-06-02 23:04



Objek di luar fungsi dilewatkan ke fungsi dengan memberikan referensi ke obejct luar. Ketika Anda menggunakan referensi itu untuk memanipulasi objeknya, objek di luar begitu terpengaruh. Namun, jika di dalam fungsi Anda memutuskan untuk menunjuk referensi ke sesuatu yang lain, Anda tidak mempengaruhi objek di luar sama sekali, karena semua yang Anda lakukan adalah mengarahkan kembali referensi ke sesuatu yang lain.


23
2018-02-13 11:00



Anggap saja seperti ini: Selalu melewati nilai. Namun, nilai suatu objek bukanlah objek itu sendiri, tetapi referensi ke objek itu.

Berikut ini contoh, memberikan nomor (tipe primitif)

function changePrimitive(val) {
    // At this point there are two '10's in memory.
    // Changing one won't affect the other
    val = val * 10;
}
var x = 10;
changePrimitive(x);
// x === 10

Mengulangi ini dengan objek menghasilkan hasil yang berbeda:

function changeObject(obj) {
    // At this point there are two references (x and obj) in memory,
    // but these both point to the same object.
    // changing the object will change the underlying object that
    // x and obj both hold a reference to.
    obj.val = obj.val * 10;
}
var x = { val: 10 };
changeObject(x);
// x === { val: 100 }

Satu lagi contoh:

function changeObject(obj) {
    // Again there are two references (x and obj) in memory,
    // these both point to the same object.
    // now we create a completely new object and assign it.
    // obj's reference now points to the new object.
    // x's reference doesn't change.
    obj = { val: 100 };
}
var x = { val: 10 };
changeObject(x);
// x === { val: 10}

16
2018-02-05 21:49



Javascript selalu pass-by-value, semuanya adalah tipe nilai. Objek adalah nilai, fungsi anggota objek adalah nilai itu sendiri (ingat bahwa fungsi adalah objek kelas pertama di Javascript). Juga, mengenai konsep bahwa segala sesuatu di Javascript adalah sebuah obyek, ini salah. String, simbol, angka, boolean, null dan undefined adalah primitif. Kadang-kadang mereka dapat memanfaatkan beberapa fungsi anggota dan properti yang diwarisi dari prototipe dasar mereka, tetapi ini hanya untuk kenyamanan, itu tidak berarti bahwa mereka adalah objek itu sendiri. Coba yang berikut ini untuk referensi

x = "test";
alert(x.foo);
x.foo = 12;
alert(x.foo);

Di kedua lansiran, Anda akan menemukan nilai tidak terdefinisi.


14
2017-12-30 02:20



Dalam JavaScript, jenis nilainya hanya mengontrol apakah nilai tersebut akan ditetapkan oleh nilai-copy atau dengan referensi-copy.

Nilai primitif selalu ditetapkan / dilewatkan oleh nilai-copy:

  • null
  • undefined
  • tali
  • jumlah
  • boolean
  • simbol dalam ES6

Nilai gabungan selalu ditetapkan / dilewatkan oleh copy referensi

  • objek
  • array
  • fungsi

Sebagai contoh

var a = 2;
var b = a; // `b` is always a copy of the value in `a`
b++;
a; // 2
b; // 3

var c = [1,2,3];
var d = c; // `d` is a reference to the shared `[1,2,3]` value
d.push( 4 );
c; // [1,2,3,4]
d; // [1,2,3,4]

Dalam cuplikan di atas, karena 2 adalah primitif skalar, a memegang satu salinan awal dari nilai itu, dan b ditugaskan salinan lain dari nilai. Saat berubah b, Anda sama sekali tidak mengubah nilai dalam a.

Tapi keduanya c dan d adalah referensi terpisah untuk nilai bersama yang sama [1,2,3], yang merupakan nilai gabungan. Penting untuk dicatat bahwa keduanya c maupun d lebih "memiliki" [1,2,3] nilai - keduanya hanya referensi rekan yang sama dengan nilai. Jadi, ketika menggunakan referensi untuk memodifikasi (.push(4)) yang sebenarnya dibagikan array nilai itu sendiri, itu mempengaruhi hanya satu nilai bersama, dan kedua referensi akan mereferensikan nilai yang baru dimodifikasi [1,2,3,4].

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

// later
b = [4,5,6];
a; // [1,2,3]
b; // [4,5,6]

Saat kami melakukan tugas b = [4,5,6], kami sama sekali tidak melakukan apa-apa untuk mempengaruhi di mana a masih mengacu ([1,2,3]). Untuk melakukannya, b harus menjadi penunjuk a daripada referensi ke array - tetapi tidak ada kemampuan seperti itu di JS!

function foo(x) {
    x.push( 4 );
    x; // [1,2,3,4]

    // later
    x = [4,5,6];
    x.push( 7 );
    x; // [4,5,6,7]
}

var a = [1,2,3];

foo( a );

a; // [1,2,3,4]  not  [4,5,6,7]

Ketika kita melewati argumen a, itu memberikan salinan a mengacu kepada x. x dan a adalah referensi terpisah yang menunjuk pada hal yang sama [1,2,3] nilai. Sekarang, di dalam fungsi, kita dapat menggunakan referensi itu untuk memutasi nilai itu sendiri (push(4)). Tetapi ketika kita membuat tugas x = [4,5,6], ini sama sekali tidak mempengaruhi di mana referensi awal a menunjuk - masih menunjuk pada (sekarang dimodifikasi) [1,2,3,4] nilai.

Untuk secara efektif lulus nilai gabungan (seperti array) dengan nilai-copy, Anda perlu secara manual membuat salinannya, sehingga referensi yang dilewatkan tidak tetap menunjuk ke aslinya. Sebagai contoh:

foo( a.slice() );

Nilai compound (objek, array, dll) yang dapat dilewatkan oleh copy referensi

function foo(wrapper) {
    wrapper.a = 42;
}

var obj = {
    a: 2
};

foo( obj );

obj.a; // 42

Sini, obj bertindak sebagai pembungkus untuk properti primitif skalar a. Ketika diteruskan ke foo(..), salinan dari obj referensi dilewatkan dan diatur ke wrapperparameter. Kami sekarang dapat menggunakan wrapper referensi untuk mengakses objek bersama, dan memperbarui propertinya. Setelah fungsi selesai, obj.a akan melihat nilai yang diperbarui 42.

Sumber


12
2018-05-12 22:20