JavaScriptと突然変異の恐怖

突然変異は変化です。 形の変化または本質の変化。 変異するものは変更される可能性があります。 突然変異の性質をよりよく理解するために、映画「X-メン」のヒーローについて考えてください。 彼らは突然素晴らしい機会を得ることができました。 ただし、問題は、これらの可能性がいつ現れるかが正確にわからないことです。 あなたの同志が理由もなく青くなり、ウールで生い茂ったと想像してください。 怖いですよね? JavaScriptにも同じ問題があります。 コードが突然変異の影響を受ける場合、これは非常に予想外に、何かを変更したり壊したりできることを意味します。







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.assignmerge-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;
      
      





まとめ



突然変異は、コードを混乱させる可能性があり、これを行うために、まったく気付かないうちに予測できないため、危険です。 問題の原因が突然変異であると疑われる場合でも、問題のある場所を見つけることは依然として困難です。 したがって、不愉快な驚きからコードを保護する最良の方法は、オブジェクトの作成時から、突然変異からの保護を確実にすることです。



オブジェクトを突然変異から保護するには、 ImmutableJSMori.jsなどのライブラリを使用するか、標準のJSメソッドObject.assign



およびObject.freeze



を使用できます。



Object.assign



Object.freeze



Object.freeze



は、オブジェクトのネイティブプロパティのみを変更から保護できることに注意してください。 オブジェクトそのものである突然変異やプロパティから保護する必要がある場合は、 割り当てディープフリーズなどのライブラリが必要になります。



親愛なる読者! オブジェクトの突然変異によってJSアプリケーションで予期しないエラーが発生しましたか?






All Articles