質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.47%
JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

Q&A

解決済

5回答

2044閲覧

JavaScriptの参照渡しと値渡しについて

momiji0210

総合スコア60

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

0グッド

1クリップ

投稿2019/12/11 08:32

JavaScriptの勉強をしており、参照渡しと値渡しの部分でハマっております。

C++ならポインタを使えば改善できる問題なのですが、
この処理をJavaScriptで書く方法がよく理解できておりません。

関数を2つ用意して、連想配列を代入する処理を記載しております。

用意した関数内で
let data = list; // listは引数で指定した連想配列
のように連想配列を代入してしまうと、dataの中身を
変更することで引数のlistの値が変わってしまいます。

他のサイトを見るとJSONで渡したり、ディープコピーをすれば
改善できると記載があったのですが、どちらの方法でも値が変わってしまうようでした。

こういった場合、JavaScriptではどのように処理を書くのが正しいのでしょうか。

JavaScript

1const listMember = [ 2 { title: '1' }, 3 { title: '2' }, 4 { title: '3' }, 5]; 6 7const listItem = [ 8 9 { 10 title: '1', 11 price: 300, 12 paidId: '1', 13 paidMember: ['1', '2', '3'], 14 }, 15 16 { 17 title: '2', 18 price: 200, 19 paidId: '1', 20 paidMember: ['1', '2'], 21 }, 22 23]; 24 25// メンバーごとにlistを計算用の作る 26function InitList(listMember){ 27 28 let list = []; 29 30 // 計算用の配列に人物名と価格を初期化して格納 31 for (let i = 0; i < listMember.length; i++) { 32 let item = [ 33 { 34 title: '', 35 price: 0, 36 } 37 ] 38 item.title = listMember[i].title; 39 item.price = 0; 40 list.push(item); 41 } 42 43 return list; 44 45} 46 47// 割り勘後の徴収金額 48function CalcBill(list, listItem){ 49 50 //console.log(list.length); 51 52 //let data = list; 53 //let data = list.concat(); 54 //let data = JSON.stringify(list); 55 //data = JSON.parse(data); 56 let data = Object.create(list); 57 console.log('price',data[0].price); 58 59 // メンバーごとに合計価格を計算 60 for (let i = 0; i < data.length; i++) { 61 data[i].price = i*100; 62 } 63 64 return data; 65 66} 67 68function AfterPayment(list, listItem){ 69 70 //let data = list; 71 //let data = list.concat(); 72 let data = Object.create(list); 73 console.log('price',data[0].price); // ここの値が0ではなくCalcBillで計算した値になる 74 75 return data; 76 77} 78 79init() { 80 let dataList = InitList(listMember); 81 CalcBill(dataList, listItem); 82 //dataList = InitList(listMember); // コメントを消すと0にできる 83 AfterPayment(dataList, listItem); 84} 85

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

miyabi_takatsuk

2019/12/11 09:35 編集

何をどうしたいのかイマイチわかりません。 listItemは使っていないし、 CalcBillもAfterPaymentも、returnしてる割には、その値をどこにも代入してないし。 せめて、最終的に得たいオブジェクトを、ベタで構いませんので、掲示してください。
momiji0210

2019/12/11 09:44

失礼しました。わかりやすくするために処理をいくつか消したのですが、それが残ってしまっていたようです。 やりたいこととしては、連想配列を使った場合の値渡しをしたいです。 init関数内で引数用のdataListを作成しております。 こちらの値をCalcBillに渡すと、AfterPayment関数内でみると CalcBillでdataにて計算した値が残ってしまいます。 おそらく参照渡しになっているからだと思います。 連想配列を使わずに list = [1,2,3] のような配列は上記でも値が変わらないのですが、連想配列だと値が変わってしまいます。
miyabi_takatsuk

2019/12/11 10:16 編集

なるほど。 おそらく、list[0].priceを出力しているからわかりずらいんだと思います。 list[1].priceの方は確かに、100と出るので、値が初期値ではありませんね ちなみに、JavaScriptには、値渡しも参照渡しも存在しません。 変数は値に対する参照にしかすぎません。 回答に移った時、少しだけ説明入れさせていただきます。
guest

回答5

0

[JavaScript]色々なディープコピー - Qiita

を参考に、ディープコピーメソッドを作った上で、
(上記を発展させ、クラスメソッド(prototype定義のメソッドも)含めてコピーできるようにしている)
下記でいかがでしょうか。

javascript

1const listMember = [ 2 { title: '1' }, 3 { title: '2' }, 4 { title: '3' }, 5]; 6 7const listItem = [ 8 9 { 10 title: '1', 11 price: 300, 12 paidId: '1', 13 paidMember: ['1', '2', '3'], 14 }, 15 16 { 17 title: '2', 18 price: 200, 19 paidId: '1', 20 paidMember: ['1', '2'], 21 }, 22 23]; 24 25// ディープコピー関数を定義 26const deepClone = obj => { 27 const r = (() => { 28 // 一応、余計な処理をいれないために、クラス名がObjectか、Arrayだったらそのまま、配列かオブジェクトかを返す 29 if (obj.constructor.name === 'Array' || obj.constructor.name === 'Object') return Array.isArray(obj) : [] : {}; 30 // クラス名が上記以外なら、(クラス、prototypeがユーザー定義)Object.createをうまく使って、新しくインスタンスを生成して返す 31 return Object.create(obj.constructor.prototype); 32 })(); 33 for(let key in obj){ 34 // プロパティがオブジェクトなら、再起的に自身を実行して潜っていく、値なら、そのまま代入 35 r[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key]; 36 } 37 return r; 38}; 39 40// メンバーごとにlistを計算用の作る 41function InitList(listMember){ 42 43 let list = []; 44 45 // 計算用の配列に人物名と価格を初期化して格納 46 for (let i = 0; i < listMember.length; i++) { 47 let item = [ 48 { 49 title: '', 50 price: 0, 51 } 52 ] 53 item.title = listMember[i].title; 54 item.price = 0; 55 list.push(item); 56 } 57 58 return list; 59 60} 61 62// 割り勘後の徴収金額 63function CalcBill(list, listItem){ 64 let data = deepClone(list); 65 // 値変化がわかりやすいよう、二番目の要素を出力 66 console.log('price',data[1].price); 67 68 // メンバーごとに合計価格を計算 69 for (let i = 0; i < data.length; i++) { 70 data[i].price = i*100; 71 } 72 73 return data; 74} 75 76function AfterPayment(list, listItem){ 77 78 let data = deepClone(list); 79 // 値変化がわかりやすいよう、二番目の要素を出力 80 console.log('price',data[1].price); 81 82 return data; 83 84} 85 86 87 88function init() { 89 const dataList = InitList(listMember); 90 CalcBill(dataList, listItem); 91 AfterPayment(dataList, listItem); 92} 93 94init();

maisumakunさんがおっしゃるとおり、Object.createは、ディープコピーを生成するものではないので、改めて、ディープコピーをさせるメソッドを用意する必要があるのでしょう。
(Object.createは、第一引数のオブジェクトを、"prototype"として、新たにオブジェクトを生成するので、コピーメソッドの中でうまく使えば、完全に同クラスからの別参照インスタンスオブジェクトとなる)

さて、余談になりますが、JavaScriptには、値渡しも参照渡しも存在しません
あくまで、変数は、メモリに格納された値、またはオブジェクトに対する参照にすぎません。

javascript

1const a = {value: 15}; 2const b = a; 3b.value = 20; 4console.log(a.value); // 20

と見ると、
abは不可分の関係に見えますが、
aを代入されたbは、{value: 15}というオブジェクトに対する参照となり、
その時点で、abの関係は代入した時点で断たれています。
ただし、メモリに格納された同じオブジェクト自体を参照しているので、bによって、プロパティの値を変えると、同じオブジェクトを参照しているaの方も変化があったように見えます。
(各プロパティもまた、値に対する参照のため。オブジェクトは、参照をプロパティとして複数保持できるもの)
よって、下記の場合は、aの変化をbでさせることはできなくなります。

javascript

1const a = {value: 15}; 2let b = a; 3// bは、新たに生成されたオブジェクトの参照となるため、aとは参照元さえも変わってしまう 4b = {value: 15}; 5b.value = 20; 6console.log(a.value); // 15

オブジェクトと同様に、値に対しても同じ参照の挙動となります。

javascript

1const a = 15; 2let b = a; // bは、同じ参照元の15という値の参照となる 3b = 20; // bは、新たな値20の参照となる 4 5console.log(a); // 15

javascript

1const a = 15; 2let b = a; // bは、同じ参照元の15という値の参照となる 3// だが、オブジェクトと違い、裸の値であるため、どう代入し直しても、新しい値が生成される。(上記の通り新しいオブジェクトの代入は新たにメモリに格納されるので、裸の値と参照の挙動自体は同じ) 4b = 15; // bは、値的には同じでも、参照元が変わってしまう。 5 6console.log(a); // 15

つまり、変数に対して、値を渡している、参照を渡しているのではなく、
常に変数は、値(オブジェクト)に対しての参照そのものとなります。
値が入っている、参照が入っている、それを渡している、ではないというわけです。

以上です。

投稿2019/12/11 10:57

編集2020/02/07 03:33
miyabi_takatsuk

総合スコア9528

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

AkitoshiManabe

2019/12/11 11:02

> あくまで、変数は、メモリに格納された値に対する参照にすぎません この解釈が不十分でした。感謝!!
momiji0210

2019/12/12 03:38

ご回答ありがとうございます!詳細な説明助かりました。 まだまだフワッとですが、少し理解できました。 アドバイスありがとうございます!
guest

0

JavaScript の 代入演算子 や 引数指定 は特殊ですね。

プリミティブだと値が代入されますが、インスタンスだと相互に参照となります。

オブジェクトのプロパティについて

javascript

1var oj = { 2 hog: true 3, hoga: "anystring" 4, hogi: 1 5, hogu: { prop:"any" } // オブジェクトリテラルは Object のインスタンス 6, hoge: [1,2,3] // 配列リテラルは Array のインスタンス 7} 8// プリミティブ 9var dstFlg = oj.hog; 10console.log(oj.hog, dstFlg); // true true 11oj.hog = false; 12console.log(oj.hog, dstFlg); // false true 13 14var dstStr = oj.hoga; 15console.log(oj.hoga, dstStr); // "anyString" "anyString" 16oj.hoga = "anyString2"; 17console.log(oj.hoga, dstStr); // "anyString2" "anyString" 18 19var dstNum = oj.hogi; 20console.log(oj.hogi, dstNum); // 1 1 21oj.hogi = 2; 22console.log(oj.hogi, dstNum); // 2 1 23 24// インスタンス 25var dstOj = oj.hogu; 26console.log(oj.hogu, dstOj); // Object{ prop:"any" } Object{ prop:"any" } 27oj.prop = "any0"; // プリミティブなので変わらない 28console.log(oj.hogu, dstOj); // Object{ prop:"any" } Object{ prop:"any" } 29oj.hogu.prop = "any0"; // deep コピー必須と言われる理由はこのあたり?(更に追記 30console.log(oj.hogu, dstOj); // Object{ prop:"any0" } Object{ prop:"any0" } 31dstOj.prop = "any2"; // dstOj 自体は インスタンスの代入 32console.log(oj.hogu, dstOj); // Object{ prop:"any2" } Object{ prop:"any2" } 33 34var dstAry = oj.hoge; 35console.log(oj.hoge, dstAry); // [1,2,3] [1,2,3] 36oj.hoge.push(4); 37console.log(oj.hoge, dstAry); // [1,2,3,4] [1,2,3,4] 38dstAry.shift(); 39console.log(oj.hoge, dstAry); // [2,3,4] [2,3,4]

※コードにコメント追記しました。

配列要素についても同様です。
関数の引数については、ちょっと失念。(同様だったと思います)

上記のように、値の変化をチェックして感覚的に覚えたのが、
「プリミティブ と インスタンス とで異なる挙動を示す」というものでした。

これでは、まともな回答になっていませんけど…。

投稿2019/12/11 10:34

編集2019/12/11 10:48
AkitoshiManabe

総合スコア5432

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

momiji0210

2019/12/12 03:42

サンプルソースありがとうございます。 JSの仕様がまだまだ理解できておらず申し訳ないです。 プリミティブ と インスタンスでやはり動きが違うのですね・・・。 全パターンテストできていたわけではないので、サンプルソース助かりました!
guest

0

ここの値が0ではなくCalcBillで計算した値になる

そもそも、0になっているのですが(jsFiddle)。

そして、Object.create(オブジェクト)は、オブジェクトをプロトタイプとして別なオブジェクトを作る関数です。コピーはされません

投稿2019/12/11 08:47

maisumakun

総合スコア145184

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

ベストアンサー

JSONで渡したり、ディープコピーをすれば

改善できると記載があったのですが、どちらの方法でも値が変わってしまう

解釈がおかしいのでは?

javascript

1const listMember = [ 2 { title: '1' }, 3 { title: '2' }, 4 { title: '3' }, 5]; 6const l1=listMember[0]; 7l1.title="100"; 8console.dir(listMember);//listMemberにさかのぼって変更 9 10const l2=JSON.parse(JSON.sringify(listMember))[1]; 11l2.title="200"; 12console.dir(listMember);//listMemberに影響はない

再確認用

javascript

1const listMember = [ 2 { title: '1' }, 3 { title: '2' }, 4 { title: '3' }, 5]; 6const l1=listMember[0]; 7const l2=JSON.parse(JSON.stringify(listMember)); 8l1.title="100"; 9console.dir(listMember);//listMemberにさかのぼって変更 10l2[1].title="200"; 11console.dir(l2);//listMemberに影響はない

投稿2019/12/11 08:39

編集2019/12/11 09:43
yambejp

総合スコア114850

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

momiji0210

2019/12/11 08:53

ご回答ありがとうございます。わかりにくい説明で申し訳ないです。 いただきましたコードを一部変更して試してみたのですが値が変わってしまいました。 やりたいこととしましては、l2ではl1で変更した値を反映させたくありません。 この場合、l2[0].title = '1'のままの状態にしたいです。 const lm = [ { title: '1' }, { title: '2' }, { title: '3' }, ]; const l1 = lm[0]; l1.title = "100"; console.log(lm);//listMemberにさかのぼって変更 const l2 = JSON.parse(JSON.stringify(lm)); l2[1].title="200"; console.log(l2); ●ログ Array [ Object { "title": "100", }, Object { "title": "2", }, Object { "title": "3", }, ] Array [ Object { "title": "100", }, Object { "title": "200", }, Object { "title": "3", }, ]
yambejp

2019/12/11 09:02

> l2[1].title="200"; > console.log(l2); そりゃl2をみれば当然1番目の要素のtitleは変わるでしょう (だってl2の要素いじってるんですから・・・) console.log(l2)→console.log(lm)で確認してください
momiji0210

2019/12/11 09:35

Object.createでもダメでした。 l2[0].titleの値を変更するとlm[0].titleの値まで200に変わってしまいます。 連想配列を使うことで参照渡しになっているのでと思います。 const lm = [ { title: '1' }, { title: '2' }, { title: '3' }, ]; const l2 = Object.create(lm); l2[0].title="200"; console.log('aaa', l2[0].title, lm[0].title); また下記のように連想配列を使わない場合は、値は変わらないようです。 const lm = [1,2,3];
yambejp

2019/12/11 09:37

> Object.createでもダメでした。 Object.createの話は私はしていませんが・・・
momiji0210

2019/12/11 09:38 編集

度々失礼します。 l2[0].titleの値も変わってしまっていることが問題です。 l2[1].titleの値が変わることは当然ですが、[0]も変わるのは参照渡し担っているからだと思います。 lmはconstのため、値の変更はないかと思います。
yambejp

2019/12/11 09:45 編集

再確認用のサンプル上げておきました > [0]も変わるのは参照渡し担っているからだと思います。 l2を作る前に参照渡しをしたl1をいじればlmに訴求されます すでに変更されたlmをベースにl2をイジれば[0]は変更されるのは 当然です。よくよく考えを整理されたほうがよいでしょう
momiji0210

2019/12/11 10:05 編集

何度もご回答ありがとうございます・・・。 サンプルのご修正もありがとうございました。 const l2=JSON.parse(JSON.stringify(listMember)); を記載する位置で値が変わるのですね。 console.dir(listMember);//listMemberにさかのぼって変更 の下に書いたところ、元の値が変わることが確認できました。 頂いたサンプルコードだと値が変化しないことも確認しました。 この辺、まだなれておらず勉強になりました。もう少し自分でもいじってみます。 質問ばかりで大変申し訳ないです。 JSON形式にパースすると値は入っているのですが、取得時にundefinedエラーが出てしまいます。 listの中身がtitle1つなら問題ないのですが、 2つ以上の連想配列だとこの現象が起きてしまいます。 こちらはなぜなのかお分かりになりませんでしょうか。 ● 処理 console.log('list[0].price',list[0].price); let data = JSON.parse(JSON.stringify(list)); console.log('list',list); console.log('data',data); console.log('data[0].price',data[0].price); ● log list[0].price 0 list Array [ Array [ Object { "price": 0, "title": "", }, ], Array [ Object { "price": 0, "title": "", }, ], Array [ Object { "price": 0, "title": "", }, ], ] data Array [ Array [ Object { "price": 0, "title": "", }, ], Array [ Object { "price": 0, "title": "", }, ], Array [ Object { "price": 0, "title": "", }, ], ] data[0].price undefined
yambejp

2019/12/11 10:09

arrayを二重に指定してobjectをおいているので list[0].priceは配列のなかからpriceを取り出そうとしています つまりはundefined const list=[ [{"price": 0,"title": "",},], [{"price": 0,"title": "",},], [{"price": 0,"title": "",},], ] list[0][0].priceを参照してください
momiji0210

2019/12/12 03:47

何度もご回答ありがとうございます。アドバイスのおかげで取得できました! これはJSON形式の方法にしてしまうと、元のようにdata[0].priceと アクセスできなくなってしまうのでしょうか。 yambejp様や他の方からJSの仕様を細かくご教示頂いて助かります・・・。 周りに聞ける方がいないので、本当にありがたいです。
yambejp

2019/12/12 03:52

> これはJSON形式の方法にしてしまうと、元のようにdata[0].priceと > アクセスできなくなってしまうのでしょうか。 ちょっと意図がわからないのですが、JSON形式にしたのが問題ではなく 二重配列を書いたのがいけないだけです データ構造についてはきちんと自分なりに仕様を決めて その方針に基づいた設定と参照をしないと正しい運用はできません
guest

0

JSONで渡しても参照渡しになってしまうということですが、
下記の書き方で再定義しておけば、値渡しになりませんか?

JSON.parse(JSON.stringify(/*任意のオブジェクト*/));

投稿2019/12/11 08:44

H40831

総合スコア975

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

miyabi_takatsuk

2019/12/11 09:37

(JavaScriptには値渡しも参照渡しも存在しませんよー)
AkitoshiManabe

2019/12/11 10:57

相互に参照しあっている状態を切るのには手っ取り早いコードですね
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.47%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問