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アプリケーションで予期しないエラーが発生しましたか?