JavaScriptオブジェクトと突然変異
JavaScriptオブジェクトにプロパティを追加できます。 オブジェクトのインスタンス化後にこれが行われると、オブジェクトは不可逆的に変更されます。 彼はX-Menのキャラクターの1人のように突然変異します。
次の例では、オブジェクトである定数
egg
は、
isBroken
プロパティが追加された後に
isBroken
ます。 そのようなオブジェクト(
egg
ような)を変更可能(つまり、変更、変更の能力がある)と呼びます。
const egg = { name: "Humpty Dumpty" }; egg.isBroken = false; console.log(egg); // { // name: "Humpty Dumpty", // isBroken: false // }
JavaScriptでは、突然変異は非常に一般的です。 あなたは文字通り常にどこでもそれらに直面することができます。
突然変異の危険性について
egg
オブジェクトが書き込まれる
newEgg
という名前の定数を作成するとします。 次に、
newEgg
name
プロパティを変更する必要があり
newEgg
。
const egg = { name: "Humpty Dumpty" }; const newEgg = egg; newEgg.name = "Errr ... Not Humpty Dumpty";
newEgg
を変更(オブジェクトを変更)
newEgg
、
egg
も自動的に変更されます。 それについてご存知ですか?
console.log(egg); // { // name: "Errr ... Not Humpty Dumpty" // }
上記の例は、突然変異の危険性を示しています。 コード内の何かを変更すると、他の場所にあるものも変更される可能性があり、そのため、あなたもそれを知ることはできません。 その結果、検出および修正が困難なエラー。
これらの奇妙な点はすべて、JavaScriptのオブジェクトが参照によって渡されるという事実の結果です。
JavaScriptのオブジェクトとそれらへの参照
「オブジェクトは参照渡し」というステートメントの意味を理解するには、まずJavaScriptの各オブジェクトに一意の識別子があることを理解する必要があります。 オブジェクトを変数に割り当てる場合、オブジェクトの値を変数に書き込んでコピーする代わりに、変数をそのオブジェクトの識別子にバインドします(つまり、変数はオブジェクトを参照するようになります)。 そのため、2つの異なるオブジェクトを比較し、同じ値を含む(またはまったく含まない)場合でも、
false
になり
false
。
console.log({} === {}); // false
上記の例で、
egg
定数が
newEgg
定数に割り当てられた
newEgg
、
egg
定数によって参照される同じオブジェクトへのリンクが
newEgg
に書き込まれ
newEgg
。
egg
と
newEgg
は同じオブジェクトを参照するため、
newEgg
が変更されると、
egg
は自動的に変更されます。
console.log(egg === newEgg); // true
残念ながら、説明した状況と同様の状況では、1つの変数に書き込まれたものが別の変数にさらされたときに変更する必要は通常ありません。 それでは、オブジェクトの突然変異を防ぐ方法は? この質問に対する答えを見つける前に、まず、JSで不変である、つまり不変であるものを見つけておくとよいでしょう。
不変のプリミティブ
JavaScriptでは、プリミティブ(
String
、
Number
、
Boolean
、
Null
、
Undefined
、および
Symbol
データ型について説明しています)は不変です。 つまり、プリミティブの構造を変更したり、プロパティやメソッドを追加したりすることはできません。 たとえば、プリミティブに新しいプロパティを追加しようとしても、まったく何も起こりません。
const egg = "Humpty Dumpty"; egg.isBroken = false; console.log(egg); // Humpty Dumpty console.log(egg.isBroken); // undefined
キーワードconstと免疫
const
キーワードを使用して宣言された変数(定数)は不変であると多くの人が考えています。 しかし、これはそうではありません。
const
キーワードを使用しても、定数に書き込まれるものは不変になりません。 定数に新しい値を割り当てることはできません。
const myName = "Zell"; myName = "Triceratops"; // ERROR
const
キーワードを使用してオブジェクトを定義すると、その内部構造を完全に変更できます。
egg
オブジェクトを使用した例では、
egg
は
const
キーワードを使用して作成された定数ですが、このオブジェクトは突然変異を防ぎません。
const egg = { name: "Humpty Dumpty" }; egg.isBroken = false; console.log(egg); // { // name: "Humpty Dumpty", // isBroken: false // }
オブジェクト突然変異防止
オブジェクトの突然変異を防ぐために、オブジェクトを操作するときに
Object.assign
メソッドを使用できます
Object.assign
メソッドは、既存のオブジェクトを組み合わせてプロパティを結果オブジェクトに割り当てることにより、新しいオブジェクトを作成する操作を実装します。
▍Object.assignメソッド
Object.assign
コンストラクトを使用
Object.assign
と、2つのオブジェクト(または複数のオブジェクト)を組み合わせて、1つの新しいオブジェクトを出力できます。 次のように使用できます。
const newObject = Object.assign(object1, object2, object3, object4);
newObject
は、
Object.assign
渡されるすべてのオブジェクトのプロパティが含まれます。
const papayaBlender = { canBlendPapaya: true }; const mangoBlender = { canBlendMango: true }; const fruitBlender = Object.assign(papayaBlender, mangoBlender); console.log(fruitBlender); // { // canBlendPapaya: true, // canBlendMango: true // }
競合する2つのプロパティが見つかった場合、
Object.assign
の引数リストの右側にあるオブジェクトのプロパティは、左側のリストにあるオブジェクトのプロパティを上書きします。
const smallCupWithEar = { volume: 300, hasEar: true }; const largeCup = { volume: 500 }; // volume , 300 500 const myIdealCup = Object.assign(smallCupWithEar, largeCup); console.log(myIdealCup); // { // volume: 500, // hasEar: true // }
ただし、注意してください!
Object.assign
を使用して2つのオブジェクトを結合すると、引数リストの最初のオブジェクトが変更されます。 他の人はしません。
console.log(smallCupWithEar); // { // volume: 500, // hasEar: true // } console.log(largeCup); // { // volume: 500 // }
ObjectObject.assignを使用する際の突然変異の問題の解決策
最初の
Object.assign
として、既存のオブジェクトの突然変異を防ぐために新しいオブジェクトを渡すことができます。 ただし、最初のオブジェクト(空の)はまだ変更中ですが、突然変異は重要なものには影響しないため、心配する必要はありません。
const smallCupWithEar = { volume: 300, hasEar: true }; const largeCup = { volume: 500 }; // const myIdealCup = Object.assign({}, smallCupWithEar, largeCup);
この操作を実行した後、必要に応じて新しいオブジェクトを変更できます。 これは以前のオブジェクトには影響しません。
myIdealCup.picture = "Mickey Mouse"; console.log(myIdealCup); // { // volume: 500, // hasEar: true, // picture: "Mickey Mouse" // } // smallCupWithEar console.log(smallCupWithEar); // { volume: 300, hasEar: true } // largeCup console.log(largeCup); // { volume: 500 }
▍Object.assignおよびプロパティオブジェクトへのリンク
Object.assign
1つの問題は、浅いマージを実行することです。つまり、あるオブジェクトから別のオブジェクトにプロパティを直接コピーします。 同時に、処理されたオブジェクトのプロパティであるオブジェクトへのリンクもコピーします。
これを例として考えてください。
新しいサウンドシステムを購入したとします。 パワーを制御し、音量、低音レベル、その他のパラメーターを設定できます。 これが標準のシステム構成です。
const defaultSettings = { power: true, soundSettings: { volume: 50, bass: 20, // } };
友人の中には大音量の音楽が好きな人もいるので、家全体が耳に届くことが保証されたプリセットを作成することにしました。
const loudPreset = { soundSettings: { volume: 100 } };
次に、友達をパーティーに招待します。 システムを稼働状態にし、同時に標準設定と音量を最大にする設定の両方を使用するには、
defaultSettings
と
loudPreset
を組み合わせようとします。
const partyPreset = Object.assign({}, defaultSettings, loudPreset);
ただし、音楽をオンにすると、
partyPreset
システムの音がおかしいことが
partyPreset
ます。 ボリュームは良好ですが、低音はまったくありません。
partyPreset
を探索する
partyPreset
、低音設定がないことに驚かれます!
console.log(partyPreset); // { // power: true, // soundSettings: { // volume: 100 // } // }
これは、JavaScriptが参照によって
soundSettings
プロパティ
soundSettings
コピーするためです。
defaultSettings
と
loudPreset
両方に
soundSettings
オブジェクトがあるため、
Object.assign
引数の右側にあるオブジェクトが新しいオブジェクトにコピーされます。
partyPreset
を変更
partyPreset
、それに応じて
partyPreset
が変化します
soundSettings
から
loudPreset
へのリンクがコピーされた証拠として。
partyPreset.soundSettings.bass = 50; console.log(loudPreset); // { // soundSettings: { // volume: 100, // bass: 50 // } // }
Object.assign
はオブジェクトのサーフェスマージを実行するため、同様の状況で、新しいオブジェクトがプロパティオブジェクトを含むオブジェクトの組み合わせである場合、他のものを使用する必要があります。 なに? たとえば、
assignment
ライブラリ。
▍ライブラリの割り当て
Assignmentは、 Pony Foo (JSに関する貴重な情報源)のNicolas Bevacuaによって作成された小さなライブラリです。 オブジェクトのディープマージ(ディープマージ)を実行し、同時に突然変異を心配しないことが役立ちます。
assignment
使用は、異なるメソッド名を使用することを除いて、
Object.assign
での作業と同じように見えます。
// assignment const partyPreset = assignment({}, defaultSettings, loudPreset); console.log(partyPreset); // { // power: true, // soundSettings: { // volume: 100, // bass: 20 // } // }
ライブラリは、他のオブジェクトに埋め込まれたすべてのオブジェクトの値を新しいオブジェクトにコピーし、既存のオブジェクトを変更から保護します。
partyPreset.soundSettings
プロパティを変更しようとしても、
loudPreset
は変更されないことがわかります。
partyPreset.soundSettings.bass = 50; // loudPreset console.log(loudPreset); // { // soundSettings { // volume: 100 // } // }
assignment
ライブラリは、オブジェクトを深くマージできる多くのツールの1つにすぎません。 lodash.assignやmerge-optionsなどの他のライブラリもこれに役立ちます。 一番好きなものを安全に選択できます。
Object.assignではなく、ディープマージを常に使用する必要がありますか?
これでオブジェクトを突然変異から保護する方法がわかったので、
Object.assign
を有意義に使用
Object.assign
ます。 この標準的な方法を正しく使用する方法を知っていれば、何も問題はありません。
ただし、ネストされたプロパティを持つオブジェクトを使用する必要がある場合は、常に
Object.assign
ではなくオブジェクトのディープマージを使用してください。
オブジェクトの耐性を確保する
上記で説明した方法は、オブジェクトを突然変異から保護するのに役立ちますが、それらの方法で作成されたオブジェクトの耐性を保証するものではありません。 入れ子になったオブジェクトプロパティを持つオブジェクトを
Object.assign
ときに間違えて
Object.assign
を使用すると、後で深刻な問題が発生する可能性があります。
これから身を守るためには、オブジェクトがまったく変化しないことを保証する価値があります。 これにはImmutableJSのようなライブラリを使用できます。 このライブラリは、ヘルプを使用して処理されたオブジェクトを変更しようとするとエラーをスローします。
または、
Object.freeze
メソッドと
deep-freeze
ライブラリを使用できます。 これらの2つのツールはエラーを生成しませんが、オブジェクトの変更も許可しません。
Object.freezeメソッドとディープフリーズライブラリ
Object.freeze
メソッドは、オブジェクト自体のプロパティを変更から保護します。
const egg = { name: "Humpty Dumpty", isBroken: false }; // "" egg Object.freeze(egg); // egg.isBroken = true; console.log(egg); // { name: "Humpty Dumpty", isBroken: false }
ただし、
defaultSettings.soundSettings.base
などの「凍結」オブジェクトのプロパティであるオブジェクトを変更しようとすると、このメソッドは役に立ちません。
const defaultSettings = { power: true, soundSettings: { volume: 50, bass: 20 } }; Object.freeze(defaultSettings); defaultSettings.soundSettings.bass = 100; // soundSettings console.log(defaultSettings); // { // power: true, // soundSettings: { // volume: 50, // bass: 100 // } // }
プロパティオブジェクトの突然変異を防ぐために、オブジェクトである「フリーズ」オブジェクトのすべてのプロパティに対して
Object.freeze
を再帰的に呼び出すディープフリーズライブラリを使用できます。
const defaultSettings = { power: true, soundSettings: { volume: 50, bass: 20 } }; // " " ( deep-freeze) deepFreeze(defaultSettings); // , defaultSettings.soundSettings.bass = 100; // soundSettings console.log(defaultSettings); // { // power: true, // soundSettings: { // volume: 50, // bass: 20 // } // }
値と突然変異の書き換えについて
変数のエントリと、新しい値のオブジェクトのプロパティのエントリを突然変異と混同しないでください。
実際、新しい値が変数に書き込まれると、それが指すものが変わります。 次の例では、
a
の値が
11
から
100
変更されます。
let a = 11; a = 100;
突然変異により、オブジェクト自体が変化します。 変数または定数に書き込まれたオブジェクトへの参照は同じままです。
const egg = { name: "Humpty Dumpty" }; egg.isBroken = false;
まとめ
突然変異は、コードを混乱させる可能性があり、これを行うために、まったく気付かないうちに予測できないため、危険です。 問題の原因が突然変異であると疑われる場合でも、問題のある場所を見つけることは依然として困難です。 したがって、不愉快な驚きからコードを保護する最良の方法は、オブジェクトの作成時から、突然変異からの保護を確実にすることです。
オブジェクトを突然変異から保護するには、 ImmutableJSやMori.jsなどのライブラリを使用するか、標準のJSメソッド
Object.assign
および
Object.freeze
を使用できます。
Object.assign
Object.freeze
と
Object.freeze
は、オブジェクトのネイティブプロパティのみを変更から保護できることに注意してください。 オブジェクトそのものである突然変異やプロパティから保護する必要がある場合は、 割り当てやディープフリーズなどのライブラリが必要になります。
親愛なる読者! オブジェクトの突然変異によってJSアプリケーションで予期しないエラーが発生しましたか?