ウェブサイト検索

JavaScriptは参照渡しか値渡しかの言語ですか?


プリミティブ型(number、stringなど)は値渡しされます。それでも、オブジェクトは、値渡し(この場合、オブジェクトを保持する変数はそのオブジェクトへの参照と見なされます)および参照渡し(変数がオブジェクト自体を保持していると見なす場合)の両方で渡すことができるため、不明です。

最終的には重要ではありませんが、引数の渡し方の規約を正しく示す方法を知りたいです。この点に関してどういう意味を持つべきかを定義している、JavaScript仕様の抜粋はありますか?

回答 #1

JavaScriptでは面白いです。次の例を考えてみてください:

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

これにより出力が生成されます:

10
changed
unchanged
  • もし obj1 が全く参照でなかった場合、obj1.item を変更しても、関数の外の obj1 には影響を与えません。
  • もし引数が適切な参照であったなら、すべてが変わっていたでしょう。num は 100 になり、obj2.item は "changed" と表示されます。しかし実際には、num は 10 のままで、obj2.item も "unchanged" のままです。

代わりに、状況は渡されたアイテムが値渡しされるというものです。しかし、値渡しされたアイテム自体が参照です。技術的には、これは呼び出し時共有(call-by-sharing)と呼ばれます。

実際的には、これはパラメータ自体(numやobj2のように)を変更しても、そのパラメータに渡された項目には影響しないことを意味します。しかし、パラメータの内部を変更すると、それは上位に伝播します(obj1のように)。

回答 #2

渡されるのは常に値による渡しですが、オブジェクトの場合は変数の値が参照です。このため、オブジェクトを渡してそのメンバーを変更すると、その変更は関数の外でも保持されます。これにより、一見すると参照渡しのように見えるのです。しかし、実際にオブジェクト変数の値を変更すると、その変更は持続しないことがわかり、実際には値による渡しであることが証明されます。

例:

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

出力 :

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

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

回答 #3

変数はオブジェクトを「保持」しているわけではなく、参照を保持しています。その参照を他の変数に代入することができ、すると両方が同じオブジェクトを参照することになります。常に値渡しです(その値が参照であっても…)。

JavaScriptが参照渡しをサポートしていれば可能ですが、パラメータとして渡された変数が保持する値を変更する方法はありません。

回答 #4

私の意見ですが…これは私の理解の仕方です。(もし間違っていたら遠慮なく訂正してください)

値渡し/参照渡しについて知っていることすべてを捨てる時が来た。

なぜなら、JavaScriptでは、値渡しであろうと参照渡しであろうと関係ないからです。重要なのは、関数に渡されたパラメータの変更(ミューテーション)と代入の違いです。

わかりました、私が言いたいことをできるだけ説明してみます。いくつかの物があるとしましょう。

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

私たちが行ったのは「代入」です... 変数「object1」と「object2」に2つの別々の空のオブジェクトを代入しました。

さて、私たちは object1 の方が好きだとしましょう… それで、新しい変数を「割り当て」ます。

var favoriteObject = object1;

次に、なぜか私たちはオブジェクト2の方が好きだと決めます。そこで、少し再割り当てを行います。

favoriteObject = object2;

object1にもobject2にも何も起こりませんでした。私たちはデータを全く変更していません。私たちが行ったのは、お気に入りのオブジェクトを再割り当てしただけです。object2とfavoriteObjectの両方が同じオブジェクトに割り当てられていることを知っておくことは重要です。どちらの変数を使ってもそのオブジェクトを変更することができます。

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

さて、例えば文字列のようなプリミティブを見てみましょう

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

再び、私たちはお気に入りを選びます。

var favoriteString = string1;

私たちのお気に入りの favoriteString 変数と string1 変数の両方に 'Hello world' が代入されています。さて、もし favoriteString を変更したい場合はどうでしょうか??何が起こるでしょうか???

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

あら......何が起こったのか。お気に入りを変えてstring1を変えることはできなかったString...なぜ??なぜなら、私たちはストリングオブジェクトを変更していないからです。私たちがしたのは、favoriteString変数を新しい文字列に「再割り当て」しただけです。これにより、文字列1から実質的に切り離されました。前の例では、オブジェクトの名前を変更した際に何も割り当てていませんでした。(まあ、変数自体には、...ただし、名前プロパティは新しい文字列に割り当てました。)代わりに、2つの変数と基礎となるオブジェクト間の接続を維持するオブジェクトをミューテーションしました。(たとえ文字列オブジェクト自体を変更・変異させたかったとしても、JavaScriptでは文字列は実際には不変であるため、それはできません。)

さて、次は関数とパラメータの渡し方です…。関数を呼び出してパラメータを渡すとき、基本的には新しい変数への「代入」を行っていることになり、これはイコール(=)を使って代入した場合とまったく同じように機能します。

これらの例を取りなさい。

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'

さて、同じことですが、関数を使って

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'

さて、今度はオブジェクトを使っていくつか例を示してみましょう…まずは、関数を使わずに。

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

では、同じことを、関数呼び出しで行います

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

OK、この投稿を最後まで読んだなら、おそらく今、JavaScriptでの関数呼び出しの仕組みについてよりよく理解できたでしょう。何かが参照渡しされるか値渡しされるかは問題ではありません…重要なのは代入と変更です。

変数を関数に渡すたびに、ちょうどイコール(=)を使った場合と同じように、パラメータ変数の名前に「代入」していることになります。

イコール記号(=)は代入を意味することを常に覚えておいてください。また、JavaScriptで関数にパラメータを渡すことも代入を意味することを常に覚えておいてください。それらは同じであり、2つの変数は全く同じ方法で接続されています(つまり、同じオブジェクトに代入されている場合を除き、接続されていないということです)。

「変数を変更する」ことが別の変数に影響を与える唯一の時は、基になるオブジェクトが変更された場合です(その場合、変数を変更したのではなく、オブジェクト自体が変更されたことになります)。

オブジェクトとプリミティブを区別する意味はありません。なぜなら、関数を使わずに単に等号で新しい変数に代入した場合と全く同じ方法で動作するからです。

唯一の注意点は、関数に渡す変数の名前が関数のパラメータの名前と同じ場合です。この場合、関数の内部では、そのパラメータを関数専用のまったく新しい変数として扱わなければなりません(実際にそうだからです)

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'

回答 #5

これらのフレーズや概念は、JavaScript が作られるずっと前に元々定義されており、JavaScript の意味論を正確に表しているわけではありません。これらを JS に当てはめようとすると、当てはめないよりも混乱を招くと思います。

だから「参照渡し/値渡し」にこだわらないでください。

次のことを考えてみてください。

  1. 変数は値へのポインタです。
  2. 変数に再代入することは、そのポインタを新しい値に向けるだけです。
  3. 変数を再代入しても、各変数がそれぞれ独自のポインタを持っているため、同じオブジェクトを指していた他の変数には決して影響しません。

だから、もし名前を付けるとすれば 「ポインタ渡し」 と言うでしょう -- JSではポインタを扱いませんが、基盤となるエンジンでは扱われます。

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

いくつかの最後のコメント:

  • 「値渡し/参照渡し」というフレーズは、言語の挙動を説明するためだけに使用され、必ずしも実際の基礎的な実装を示すものではありません。この抽象化の結果、適切な説明に不可欠な重要な詳細が失われ、その結果として、追加情報なしでは単一の用語だけで実際の挙動を十分に説明できないという現在の状況が生じています。
  • プリミティブは特別なルールによって強制され、オブジェクトはそうではないと考えたくなるかもしれませんが、プリミティブは単にポインタチェーンの終点にすぎません。
  • 最後の例として、配列をクリアしようとする一般的な試みが期待通りに動作しない理由を考えてみましょう。

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

回答 #6

関数の外にあるオブジェクトは、関数に外部オブジェクトへの参照を渡すことによって関数に渡されます。

その参照を使ってそのオブジェクトを操作すると、外側のオブジェクトも影響を受けます。しかし、関数内で参照を別のものに向けることにした場合、外側のオブジェクトにはまったく影響を与えません。なぜなら、参照を別のものに向け直しただけだからです。

回答 #7

こう考えてみてください:常に値渡しです。しかし、オブジェクトの値はオブジェクト自体ではなく、そのオブジェクトへの参照です。

ここに例があります、数値(プリミティブ型)を渡す場合

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

これを物体で繰り返すと、異なる結果が得られます:

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 }

もう一つの例:

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}

回答 #8

値渡しと参照渡しによるコピー、渡し方、比較についての非常に詳細な説明は、「JavaScript: The Definitive Guide」 のこの章にあります。

オブジェクトや配列を参照によって操作する話題を離れる前に、用語の一点を明確にする必要があります。

「参照による渡し」というフレーズは、いくつかの意味を持つことがあります。一部の読者にとって、このフレーズは関数の呼び出し技法を指し、関数が引数に新しい値を割り当て、その変更された値が関数の外部でも見えるようにすることを可能にします。しかし、本書ではこの用語はそのようには使われません。

ここで私たちが意味するのは、単にオブジェクトや配列そのものではなく、それへの参照が関数に渡されるということです。関数はその参照を使ってオブジェクトのプロパティや配列の要素を変更することができます。しかし、もし関数が参照を新しいオブジェクトや配列への参照で上書きした場合、その変更は関数の外からは見えません。

この用語の別の意味に詳しい読者は、オブジェクトや配列は値渡しされると言うことを好むかもしれませんが、実際に渡される値はオブジェクトそのものではなく、参照です。

回答 #9

JavaScriptは常に値渡しです。すべては値の型です。

オブジェクトは値であり、オブジェクトのメンバ関数もそれ自体が値です(関数がJavaScriptでは第一級オブジェクトであることを覚えておいてください)。また、JavaScriptではすべてがオブジェクトであるという概念についてですが、これは間違いです。文字列、シンボル、数値、ブール値、null、およびundefinedはプリミティブです。

時折、彼らは基本プロトタイプから継承された一部のメンバ関数やプロパティを活用することができますが、これはあくまで便利さのためだけです。それが彼ら自身がオブジェクトであることを意味するわけではありません。参考のために以下を試してみてください:

x = "test";
console.log(x.foo);
x.foo = 12;
console.log(x.foo);

両方の console.log では、値は undefined であることがわかります。

回答 #10

JavaScriptでは、値の型がその値が値のコピーで代入されるか、参照のコピーで代入されるかを単独で制御します。

プリミティブ値は常に値のコピーによって代入/渡されます:

  • null
  • undefined
  • 文字列
  • 番号
  • ブール値
  • ES6のシンボル

複合値は常に参照コピーによって割り当てられ/渡される

  • オブジェクト
  • 配列
  • 関数

例えば

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]

上記のスニペットでは、2 がスカラーのプリミティブであるため、a はその値の1つの初期コピーを保持し、b にはその値の別のコピーが割り当てられます。b を変更しても、a の値を変えることはまったくありません。

しかし、c と d はどちらも同じ共有値 [1,2,3] への別々の参照であり、これは複合値です。重要なのは、c も d も [1,2,3] の値を「所有」しているわけではなく、両方ともその値への同等の参照であるということです。したがって、どちらかの参照を使って実際の共有 array の値自体を変更(.push(4))すると、それは単に共有されている1つの値に影響を与えるだけであり、両方の参照は新しく変更された値 [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]

私たちが代入 b=[4,5,6] を行うとき、a が依然として参照している場所([1,2,3])に影響を与えることは全くありません。それを行うには、b は 配列 への参照ではなく a へのポインタである必要があります -- しかし、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]

引数 a を渡すと、a の参照のコピーが x に割り当てられます。x と a は同じ [1,2,3] の値を指す別々の参照です。関数内では、その参照を使って値自体を変更することができます (push(4)) 。しかし、x=[4,5,6] という代入を行っても、最初の参照 a が指している場所には何の影響も与えません──依然として(今変更された) [1,2,3,4] の値を指しています。

複合値(例えば array)を値コピーで効果的に渡すには、参照が元のオブジェクトを指したままにならないよう、手動でコピーを作成する必要があります。例えば:

foo( a.slice() );

参照コピーで渡すことができる複合値(オブジェクト、配列など)

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

var obj = {
    a: 2
};

foo( obj );

obj.a; // 42

ここで、obj はスカラー原始プロパティ a のラッパーとして機能します。foo(..) に渡されると、obj の参照のコピーが渡され、wrapper パラメータに設定されます。これで wrapper 参照を使って共有オブジェクトにアクセスし、そのプロパティを更新できます。関数が終了した後、obj.a は更新された値 42 を反映します。

ソース

回答 #11

ええと、それはプログラミング言語における「パフォーマンス」と「速度」、そして簡単に言うと「メモリ管理」に関することです。

JavaScriptでは、値を二つの層に分けることができます:type1-オブジェクトとtype2-文字列やブール値など他のすべてのタイプの値

もし記憶を以下の四角のように想像すると、各四角にはタイプ2の値が1つだけ保存できる:

すべてのtype2値(緑)は単一の四角であり、type1値(青)はそれらのグループです:

要点は、もし type2 の値を示したい場合、アドレスは単純ですが、同じことを type1 の値で行いたい場合、それは全く簡単ではないということです!:

そしてより複雑な話では:

だからここで参照が私たちを救うことができる:

ここでの緑の矢印は典型的な変数ですが、紫の矢印はオブジェクト変数です。なので、緑の矢印(典型的な変数)は持つタスクが1つだけ(それは典型的な値を示すことです)ため、その値を分ける必要はなく、緑の矢印はその値と一緒にどこに行く場合でも、すべての代入や関数などで移動させます...

しかし、紫の矢印では同じことはできません。ここに「ジョン」のセルを移動したり、他の多くのことをしたいかもしれません。そのため、紫の矢印はその場所に固定され、割り当てられた通常の矢印だけが移動します。

非常に混乱する状況とは、参照している変数がどのように変化するかを理解できない場合のことです。非常に良い例を見てみましょう:

let arr = [1, 2, 3, 4, 5]; //arr is an object now and a purple arrow is indicating it
let obj2 = arr; // now, obj2 is another purple arrow that is indicating the value of arr obj
let obj3 = ['a', 'b', 'c'];
obj2.push(6); // first pic below - making a new hand for the blue circle to point the 6
//obj2 = [1, 2, 3, 4, 5, 6]
//arr = [1, 2, 3, 4, 5, 6]
//we changed the blue circle object value (type1-value) and due to arr and obj2 are indicating that so both of them changed
obj2 = obj3; //next pic below - changing the direction of obj2 array from blue circle to orange circle so obj2 is no more [1,2,3,4,5,6] and it's no more about changing anything in it but we completely changed its direction and now obj2 is pointing to obj3
//obj2 = ['a', 'b', 'c'];
//obj3 = ['a', 'b', 'c'];

回答 #12

これは値渡しと参照渡し(JavaScript)についての少し詳しい説明です。この概念では、変数を参照で渡すことと変数を参照で渡すことについて話しています。

値による渡し(プリミティブ型)

var a = 3;
var b = a;

console.log(a); // a = 3
console.log(b); // b = 3

a=4;
console.log(a); // a = 4
console.log(b); // b = 3
  • JavaScript のすべてのプリミティブ型(string、number、Boolean、undefined、null)に適用されます。
  • a はメモリ(例えば 0x001)を割り当てられ、b はメモリの値のコピー(例えば 0x002)を作成します。
  • つまり、変数の値を変更してももう一方には影響しません。なぜなら、両方とも異なる場所に存在しているからです。

参照による渡し(オブジェクト)

var c = { "name" : "john" };
var d = c;

console.log(c); // { "name" : "john" }
console.log(d); // { "name" : "john" }

c.name = "doe";

console.log(c); // { "name" : "doe" }
console.log(d); // { "name" : "doe" }
  • JavaScriptエンジンはオブジェクトを変数cに割り当て、例えば(0x012)のようないくつかのメモリを指します。
  • d=c のとき、このステップでは d は同じ場所 (0x012) を指します。
  • どちらかの変数の値を変更すると、両方の変数の値が変わります。
  • 関数はオブジェクトです

特殊な場合、参照渡し(オブジェクト)

c = {"name" : "jane"};
console.log(c); // { "name" : "jane" }
console.log(d); // { "name" : "doe" }
  • 等号(=)演算子は新しいメモリ空間またはアドレスを設定します

関連記事