web-dev-qa-db-ja.com

配列上のjsマップが元の配列を変更する理由

私はmap()の振る舞いにかなり混乱しています。

私はこのようなオブジェクトの配列を持っています:

const products = [{
    ...,
    'productType' = 'premium',
    ...
}, ...]

そして、私はこの配列を、同じ配列を返すはずの関数に渡しますが、すべての製品を無料にします:

[{
    ...,
    'productType' = 'free',
    ...
}, ...]

機能は次のとおりです。

const freeProduct = function(products){
    return products.map(x => x.productType = "free")
}

次の配列を返します:

["free", "free", ...]

だから私は私の機能を書き直しました:

const freeProduct = function(products){
    return products.map(x => {x.productType = "free"; return x})
}

意図したとおりに配列を返します。

しかし!そして、それが私の心を失った瞬間です。どちらの場合も、元の製品の配列が変更されます。

Map()の周りのドキュメントは、そうすべきではないと述べています( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map )。

配列のクローンを作成して、機能を次のようにしようとさえしました。

const freeProduct = function(products){
    p = products.splice()
    return p.map(x => {x.productType = "free"; return x})
}

しかし、私はまだ同じ結果を取得します(それは私を夢中にさせ始めます)。

私が間違っていることを私に説明できる人にはとても感謝しています!

ありがとうございました

32
Edwin Joassart

元の配列を変更していません。配列内のオブジェクトを変更しています。配列内のオブジェクトの変更を避けたい場合は、 Object.assign を使用して、元のプロパティに加えて必要な変更を加えた新しいオブジェクトを作成できます。

const freeProduct = function(products) {
  return products.map(x => {
    return Object.assign({}, x, {
      productType: "free"
    });
  });
};

2018編集:

ほとんどのブラウザ では、Object.assignの代わりにオブジェクトスプレッド構文を使用してこれを実現できます。

const freeProduct = function(products) {
  return products.map(x => {
    return {
      ...x,
      productType: "free"
    };
  });
};
60
SimpleJ

SimpleJの答えを詳しく説明すると、2つの配列を===にすると、マッピングされた配列が実際には新しい配列であることを確認して、それらが等しくない(メモリ内の同じアドレスではない)ことがわかります。問題は、元の配列の同じオブジェクトへの参照でいっぱいの新しい配列を返すことです(新しいオブジェクトリテラルを返すのではなく、同じオブジェクトへの参照を返します)。したがって、古いオブジェクトのコピーである新しいオブジェクトを作成する必要があります。つまり、SimpleJが提供するObject.assignサンプルを使用する必要があります。

12

残念ながら、スプレッド演算子でもオブジェクト割り当て演算子でもディープコピーを実行します。..関数のような関数を使用して、単なる参照コピーではなく面コピーを取得する必要があります。

const util = require('util');
const print = (...val) => {
    console.log(util.inspect(val, false, null, false /* enable colors */));
};
const _ = require('lodash');

const obj1 =     {foo:{bar:[{foo:3}]}};
const obj2 =     {foo:{bar:[{foo:3}]}};

const array = [obj1, obj2];

const objAssignCopy = x => { return Object.assign({}, x, {})};
const spreadCopy = x => { return {...x}};
const _Copy = x => _.cloneDeep(x);

const map1 = array.map(objAssignCopy);
const map2 = array.map(spreadCopy);
const map3 = array.map(_Copy);

print('map1', map1);
print('map2', map2);
print('map3', map3);
obj2.foo.bar[0].foo = "foobar";
print('map1 after manipulation of obj2', map1); // value changed 
print('map2 after manipulation of obj2', map2); // value changed
print('map3 after manipulation of obj2', map3); // value hasn't changed!
1
Patrick W.

Array Iterator Array.map()は、同じ要素数で新しい配列を作成するか、元の配列を変更しません。同じ参照をコピーするときに配列内にオブジェクトがある場合、参照に問題がある可能性があります。そのため、オブジェクトのプロパティを変更すると、同じ参照を保持する要素の元の値が変更されます。

解決策は、オブジェクトをコピーすることです。まあ、array.Splice()[...array](spread Operator)はこの場合は役に立ちません。 Loadash のようなJavaScriptユーティリティライブラリを使用するか、下記のコードを使用できます。

const newList = JSON.parse(JSON.stringify(orinalArr))
0
Kushal Atreya