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

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

ただいまの
回答率

87.34%

JavaScriptのmap()がもとのオブジェクトにまで影響してしまうのは何故ですか?

解決済

回答 3

投稿

  • 評価
  • クリップ 2
  • VIEW 623

score 15

let responseJson = {
data: {
children:[
{data:{url:"example.com"}},
{data:{url:"example.com"}},
{data:{url:"example.com"}},
{data:{url:"example.com"}},
{data:{url:"example.com"}},
]
}
};

console.log(responseJson);

let threads = responseJson.data.children.slice(0, 2);

threads.map(i => i.key = i.data.url);


このようなコードでresponseJsonを出力すると、以下のような結果が出力されます。(抜粋)

data: {…}
children: (5) […]
0: Object { data: {…}, key: "example.com" }
1: Object { data: {…}, key: "example.com" }
2: Object { data: {…} }
3: Object { data: {…} }
4: Object { data: {…} }
length: 5

なぜresponseJsonに影響するのでしょうか?また、responseJsonを保持したい場合どのような工夫ができますか?

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

+3

let threads = responseJson.data.children.slice(0, 2);


は元のresponseJson.data.childrenの(先頭2つの)要素への参照をコピーした新しい配列を返しています。
つまり、responseJson.data.childrenの(先頭2つの)要素とthreadsの各要素は同じオブジェクトを参照しています。

  • responseJson.data.children[0]threads[0]は同じオブジェクトを参照している。
  • responseJson.data.children[1]threads[1]は同じオブジェクトを参照している。

そのため、threads[0]を操作した場合、responseJson.data.children[0]を操作したのと同じことになります。


また、なにを意図しているのか不明ですが、(普通、Array.prototype.map()は新しい配列を作るために使います。)

threads.map(i => i.key = i.data.url);


threads.map(function(i) {
    i.key = i.data.url; // <- オブジェクトを変更している。
    return i.key;
});


と同じ意味なので、threads[0]threads[1]が変更されます。


また、responseJsonを保持したい場合どのような工夫ができますか?

値をコピーしたオブジェクトを作るとか。

threads.map(i => {data:{url:i.data.url}, key:i.data.url});

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

checkベストアンサー

+1

本質問は、JavaScriptにおける、値の扱いの仕様、
値の参照の置き換えが関係していきます。

非公式でないので、正確性に欠ける可能性がありますが、
読んでいる限りは、合っていると思ったので、
参考記事を記載します。

JavaScriptに参照渡し/値渡しなど存在しない - Qiita

まず、プリミティブな値(数値や文字列など)が基本とすると理解できる、かと思いますので、その解説から。

let a = 10; // 値10が生成され、aは、10という値への参照となる。
console.log(a) // 10

基本、変数は、メモリに格納された値の参照であり、変数自体には値が入っていないということです。

では、変数に変数を代入した時はどうなるかというと、

let a = 10;
let b = a; // 変数bは、値10への参照になる。aもbも10という値を"参照"している状態
console.log(a) // 10
console.log(b) // 10

となります。
ですが、プリミティブな値の場合、bを変更しても、a自体はなんら変化がありません。
それはなぜかというと、

let a = 10;
let b = a; // 変数bは、値10への参照になる。aもbも10という値を参照している状態
b = 100; // 値100が新たに生成され、bはその値への参照となる。aとは独立している(aは値10への参照を失わない)
console.log(a); // 10
console.log(b); // 100

となり、ようは、参照の置き換えが発生します。

では、本質問のようなことがなぜ起こるのか。
配列やオブジェクトではそもそも参照している段階が違うと言いますか、
プリミティブなものとは、参照している値が違うからです。
※今回の質問は配列ですが、オブジェクトの方が説明がしやすいので、オブジェクトで解説していきます。

const a = {key: 0, value: 15}; // aは{}の参照となる。そして、各プロパティは、各値の参照となる

という状態になり、a自体は、{}というオブジェクト自体の参照となります。
そして、各プロパティは、各値の参照となります。
よって、このオブジェクト参照の変数を、他の変数に代入するとどうなるか。

const a = {key: 0, value: 15}; // aは{}の参照となる。そして、各プロパティは、各値の参照となる
const b = a; // bは{key,value}というオブジェクト自体の参照となる
b.value = 35; // {key,value}というオブジェクトの、valueの値の参照が置き換わる。そのため、a、b自体の参照は置き換わらない
// よって、下記のような結果となる
console.log(a.value); // 35
console.log(b.value); // 35

つまり、変数に、オブジェクト変数を代入すると、
その代入先は、代入元のオブジェクト自体の参照となります。
そして、そのオブジェクトのプロパティに値を代入し直すと、
そのプロパティ自体の値の参照が置き換わるが、
オブジェクト自体への参照が置き換わるわけではないため、
a、b自体はなんら変わるわけではないわけです。
a、bは同じオブジェクトを参照しているため、bの方でプロパティの値参照を変えようと、aの方で変えようと、
同じオブジェクトを参照しているため、プロパティは共通の値となるのです。

例えば、一つのコップをAさんBさんが同時に手にもっているとします。
コップには水が入っています。
Bさんが、コップの水を捨て、お茶に置き換えれば、コップの中身は変わっても
コップ自体は変わらないのと同じです。
(二人の人間が手に持ちながら、中身を入れ直す絵面は、想像すると微妙ですが)

これは、配列においても同じことがいえます。
(というかJavaScriptにおいては配列もオブジェクト)

const a = ['まさひこ', 'たかひこ']; // aは[0, 1]の参照となる。そして、要素(インデックス)は、各値の参照となる
const b = a; // bは[0, 1]という配列自体の参照となる
b[0] = 'ひこざぶろう'; // [0, 1]という配列の、0インデックスの値の参照が置き換わる。そのため、a、b自体の参照は置き換わらない
// よって、下記のような結果となる
console.log(a[0]); // ひこざぶろう
console.log(b[0]); // ひこざぶろう

よって、今回の配列のような参照元の配列の値の参照が置き換わるので、今回の質問のような結果となります。

前回答状態では、誤解と、間違った回答となっていたため、
大変に失礼しました。

また、間違っている部分があれば、ご指摘いただければ幸いです。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/10/28 07:32

    渡してないのに◯◯渡しという言葉で説明することに違和感を覚えます。

    キャンセル

  • 2019/10/28 07:34 編集

    C# にも値型と参照型はあります。つまりこの回答で言う C# で言う値渡しと参照渡しのようなものを参照渡しを使わずにすることができます。このコメントの意味がわかりますでしょうか?
    用語が適切に使われていないために、このコメントのように回答がややこしくなっています。

    リンク先にも書いてある通り、JavaScript には参照渡しの概念は無いので、わざわざ誤解を招く用語を使う必要はありません。「似ているからいいじゃないか」と主張する人がよくいますが、それはその人が参照渡しを理解できず参照型と勝手に混同しただけで、全く似ていません。

    たとえば国民主権について「戦時中でいう天皇主権のようなもの」と説明する人がいたら嫌でしょう? 違う意味で使われているものをわざわざ持ち出さなくても説明できるはずです。

    参照型と参照渡しが似ているのは主に名前というだけなので、同一視する意味はありません。

    キャンセル

  • 2019/10/28 08:52

    hayataka2049さん > ご指摘ありがとうございます。

    Zuishinさん > ご指摘ありがとうございます。
    回答を修正していきます。

    キャンセル

0

MDNのArray.prototype.map() (構文)を最初に示しますね。

  • コールバックの処理結果は代入式の左辺にある変数(new_array)で結果を受け取ります。
  • コールバック関数は、新しい配列要素となる値の返却が必要

draq さんの回答に重複しますが、ループの中で元のオブジェクトの値を書き換えるだけの処理になっています。

// threads.map(i => i.key = i.data.url); は以下のループに等しい
for( let idx=0; idx<threads.length; ++idx ) {
  let i = threads[idx];
  (void 0).push(i.key = i.data.url);
  // threads[idx].key = threads[idx].data.url; 実行しているが、
  // new_array に該当する新しいArray用変数がない
}

単にurlを抽出するだけなら

let rslt = threads.map( function(i){
  return i.data.url
});
console.log(rslt) // ["example.com", "example.com"]

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

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

  • ただいまの回答率 87.34%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る